├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ ├── nightly.yml │ ├── pr.yml │ └── release.yml ├── Dockerfile ├── LICENSE ├── README.md ├── server ├── .env.example ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Procfile ├── build.rs ├── migrations │ ├── 20220215080801_init.sql │ └── 20220303081032_microsoft.sql ├── rust-toolchain ├── src │ ├── captcha.rs │ ├── config.rs │ ├── data.rs │ ├── error.rs │ ├── github.rs │ ├── main.rs │ ├── microsoft.rs │ ├── models.rs │ ├── request_id.rs │ ├── send.rs │ ├── user.rs │ ├── util.rs │ └── wechat.rs └── static │ └── .well-known │ └── microsoft-identity-association.json ├── tests ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── usecases ├── Notify Me.flo └── Notify Me.pdf └── web ├── .gitignore ├── package.json ├── public └── index.html ├── src ├── App.css ├── App.tsx ├── Constants.ts ├── Faq.tsx ├── GetStarted.tsx ├── Home.tsx ├── Navs.tsx ├── Routes.tsx ├── Send.tsx ├── User.tsx ├── index.css ├── index.tsx ├── react-app-env.d.ts └── serviceWorker.ts ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | web/node_modules/ 2 | server/target/ 3 | tests/target 4 | usecases/ -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | IMAGE_NAME: pipehub 10 | DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/postgres 11 | 12 | jobs: 13 | nightly: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2.4.0 18 | 19 | - name: Log into Github registry 20 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin 21 | 22 | - name: Build image 23 | run: docker build --network=host . --file Dockerfile --tag image 24 | 25 | - name: Push image to Github registry 26 | run: | 27 | IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME 28 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 29 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 30 | [ "$VERSION" == "master" ] && VERSION=latest 31 | echo IMAGE_ID=$IMAGE_ID 32 | echo VERSION=$VERSION 33 | docker tag image $IMAGE_ID:$VERSION 34 | docker push $IMAGE_ID:$VERSION 35 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | env: 9 | DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/postgres 10 | 11 | jobs: 12 | pr: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2.4.0 17 | 18 | - name: Replace .env 19 | run: mv ./server/.env.example ./server/.env 20 | 21 | - name: Build 22 | run: | 23 | cd web 24 | yarn && yarn build 25 | cd .. 26 | cp -r web/build/ server/static 27 | cd server 28 | cargo build 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | env: 9 | IMAGE_NAME: pipehub 10 | DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/postgres 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2.4.0 18 | 19 | - name: Log into Github registry 20 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin 21 | 22 | - name: Build image 23 | run: docker build --network=host . --file Dockerfile --tag image 24 | 25 | - name: Push image to Github registry 26 | run: | 27 | IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME 28 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 29 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 30 | [ "$VERSION" == "master" ] && VERSION=latest 31 | echo IMAGE_ID=$IMAGE_ID 32 | echo VERSION=$VERSION 33 | docker tag image $IMAGE_ID:$VERSION 34 | docker push $IMAGE_ID:$VERSION 35 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts AS web-builder 2 | ADD ./web /root/web 3 | WORKDIR /root/web 4 | RUN yarn && yarn build 5 | 6 | FROM ekidd/rust-musl-builder:latest AS server-builder 7 | ARG DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres 8 | ENV DATABASE_URL ${DATABASE_URL} 9 | ADD --chown=rust:rust ./server /home/rust/server 10 | WORKDIR /home/rust/server 11 | RUN cargo build --release 12 | 13 | FROM alpine:latest 14 | RUN apk --no-cache add ca-certificates 15 | WORKDIR / 16 | COPY --from=server-builder \ 17 | /home/rust/server/target/x86_64-unknown-linux-musl/release/server \ 18 | /usr/local/bin/ 19 | COPY --from=web-builder \ 20 | /root/web/build \ 21 | /static 22 | CMD /usr/local/bin/server -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Zhiyuan Zheng 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PipeHub 2 | === 3 | ![Build](https://github.com/zhzy0077/pipehub/workflows/Build/badge.svg) 4 | ![MIT Licensed](https://img.shields.io/github/license/zhzy0077/pipehub) 5 | 6 | ## Get started 7 | It's a service that pipelines web request to your WeChat Account. 8 | 9 | ### Heroku no longer supports free dynos, so no more services. 10 | 11 | ## Availability 12 | According to our statistics, the overall availability during 6/18 and 7/2 is **99.67%**. 13 | 14 | The percentiles of latencies are: 15 | - Avg: 338ms 16 | - p50: 192ms 17 | - p80: 386ms 18 | - p90: 875ms 19 | 20 | ## Use cases 21 | - Automate: [Automate](https://llamalab.com/automate/) on `Android` can redirect short messages, FCM notifications to PipeHub. Try `Notify me.flo` under `usecases` folder. 22 | 23 | ## Deploy your own server 24 | 1. Prerequisites: 25 | - A PostgreSQL database. 26 | - A GitHub OAuth App. 27 | 2. Prepare the dotenv file(replace the placeholders): 28 | ```bash 29 | pipehub_database_url=postgres://root:123456@localhost/pipehub 30 | pipehub_host=0.0.0.0 31 | pipehub_port=8080 32 | pipehub_domain=http://localhost:8080 33 | pipehub_https=false 34 | pipehub_log__level=INFO 35 | pipehub_github__client_id=${GITHUB.OAUTH_CLIENTID} 36 | pipehub_github__client_secret=${GITHUB.OAUTH_SECRET} 37 | pipehub_github__auth_url=https://github.com/login/oauth/authorize 38 | pipehub_github__token_url=https://github.com/login/oauth/access_token 39 | pipehub_github__callback_url=http://localhost:8080/callback 40 | ``` 41 | - Use docker image: 42 | 43 | At this point, you need to login your github account before pulling a docker image as of [docker pull from public GitHub Package Registry fail with “no basic auth credentials” error](https://github.community/t/docker-pull-from-public-github-package-registry-fail-with-no-basic-auth-credentials-error/16358). 44 | ```bash 45 | docker login docker.pkg.github.com -u ${github name} 46 | docker pull docker.pkg.github.com/zhzy0077/pipehub/pipehub:latest 47 | docker run --env-file ${your dotenv file} -d docker.pkg.github.com/zhzy0077/pipehub/pipehub:latest 48 | ``` 49 | - Build from sratch: 50 | ```bash 51 | # Clone the repository. 52 | git clone https://github.com/zhzy0077/pipehub 53 | # Build the web. 54 | cd web && yarn && yarn build 55 | # Copy web assets. 56 | cd .. && cp -r web/static/* server/static/ 57 | # Copy dotenv file. 58 | cp ${your dotenv file} server 59 | # Run the server. 60 | cd server && cargo run 61 | ``` 62 | 63 | ## Feedback 64 | All kinds of feedback is welcomed. Just feel free to get in touch with me by creating an issue or emailing zhzy0077@hotmail.com. 65 | 66 | ## License 67 | This project is open-sourced under MIT license. 68 | -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | pipehub_database_url=postgres://postgres:postgres@127.0.0.1:5432/postgres 2 | pipehub_host=0.0.0.0 3 | pipehub_port=8080 4 | pipehub_domain=http://localhost:8080 5 | pipehub_https=false 6 | pipehub_log__level=DEBUG 7 | pipehub_github__client_id=0e5bc8d93270451ebd73ec5de12446a6 8 | pipehub_github__client_secret=0e5bc8d93270451ebd73ec5de12446a6 9 | pipehub_github__auth_url=https://github.com/login/oauth/authorize 10 | pipehub_github__token_url=https://github.com/login/oauth/access_token 11 | pipehub_github__callback_url=http://localhost:8080/callback -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | .env -------------------------------------------------------------------------------- /server/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 = "actix-codec" 7 | version = "0.5.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" 10 | dependencies = [ 11 | "bitflags", 12 | "bytes", 13 | "futures-core", 14 | "futures-sink", 15 | "log", 16 | "memchr", 17 | "pin-project-lite", 18 | "tokio", 19 | "tokio-util 0.7.0", 20 | ] 21 | 22 | [[package]] 23 | name = "actix-cors" 24 | version = "0.6.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "30dbd116ef7532f56e2f6d7c511736ea0b124d914ee8820a5271247bf89f06aa" 27 | dependencies = [ 28 | "actix-service", 29 | "actix-utils", 30 | "actix-web", 31 | "derive_more", 32 | "futures-util", 33 | "log", 34 | "once_cell", 35 | "smallvec", 36 | ] 37 | 38 | [[package]] 39 | name = "actix-files" 40 | version = "0.6.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d81bde9a79336aa51ebed236e91fc1a0528ff67cfdf4f68ca4c61ede9fd26fb5" 43 | dependencies = [ 44 | "actix-http", 45 | "actix-service", 46 | "actix-utils", 47 | "actix-web", 48 | "askama_escape", 49 | "bitflags", 50 | "bytes", 51 | "derive_more", 52 | "futures-core", 53 | "http-range", 54 | "log", 55 | "mime", 56 | "mime_guess", 57 | "percent-encoding", 58 | "pin-project-lite", 59 | ] 60 | 61 | [[package]] 62 | name = "actix-http" 63 | version = "3.0.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "0f3fdd63b9cfeaf92eeeece719dabbddddb420a57d3fd171ce1490ecfb7086b1" 66 | dependencies = [ 67 | "actix-codec", 68 | "actix-rt", 69 | "actix-service", 70 | "actix-utils", 71 | "ahash", 72 | "base64", 73 | "bitflags", 74 | "bytes", 75 | "bytestring", 76 | "derive_more", 77 | "encoding_rs", 78 | "futures-core", 79 | "h2", 80 | "http", 81 | "httparse", 82 | "httpdate", 83 | "itoa", 84 | "language-tags", 85 | "local-channel", 86 | "log", 87 | "mime", 88 | "percent-encoding", 89 | "pin-project-lite", 90 | "rand", 91 | "sha-1 0.10.0", 92 | "smallvec", 93 | ] 94 | 95 | [[package]] 96 | name = "actix-macros" 97 | version = "0.2.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" 100 | dependencies = [ 101 | "quote", 102 | "syn", 103 | ] 104 | 105 | [[package]] 106 | name = "actix-router" 107 | version = "0.5.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "eb60846b52c118f2f04a56cc90880a274271c489b2498623d58176f8ca21fa80" 110 | dependencies = [ 111 | "bytestring", 112 | "firestorm", 113 | "http", 114 | "log", 115 | "regex", 116 | "serde 1.0.136", 117 | ] 118 | 119 | [[package]] 120 | name = "actix-rt" 121 | version = "2.6.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "cdf3f2183be1241ed4dd22611850b85d38de0b08a09f1f7bcccbd0809084b359" 124 | dependencies = [ 125 | "futures-core", 126 | "tokio", 127 | ] 128 | 129 | [[package]] 130 | name = "actix-server" 131 | version = "2.0.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "d9e7472ac180abb0a8e592b653744345983a7a14f44691c8394a799d0df4dbbf" 134 | dependencies = [ 135 | "actix-rt", 136 | "actix-service", 137 | "actix-utils", 138 | "futures-core", 139 | "futures-util", 140 | "log", 141 | "mio", 142 | "num_cpus", 143 | "socket2", 144 | "tokio", 145 | ] 146 | 147 | [[package]] 148 | name = "actix-service" 149 | version = "2.0.2" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" 152 | dependencies = [ 153 | "futures-core", 154 | "paste", 155 | "pin-project-lite", 156 | ] 157 | 158 | [[package]] 159 | name = "actix-session" 160 | version = "0.5.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "53b253be4da7f0a778831d0c8b0c2de4ce8ea30f3b1d14d11843a296e53d21db" 163 | dependencies = [ 164 | "actix-service", 165 | "actix-utils", 166 | "actix-web", 167 | "derive_more", 168 | "futures-util", 169 | "log", 170 | "serde 1.0.136", 171 | "serde_json", 172 | "time", 173 | ] 174 | 175 | [[package]] 176 | name = "actix-utils" 177 | version = "3.0.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" 180 | dependencies = [ 181 | "local-waker", 182 | "pin-project-lite", 183 | ] 184 | 185 | [[package]] 186 | name = "actix-web" 187 | version = "4.0.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "f4e5ebffd51d50df56a3ae0de0e59487340ca456f05dd0b90c0a7a6dd6a74d31" 190 | dependencies = [ 191 | "actix-codec", 192 | "actix-http", 193 | "actix-macros", 194 | "actix-router", 195 | "actix-rt", 196 | "actix-server", 197 | "actix-service", 198 | "actix-utils", 199 | "actix-web-codegen", 200 | "ahash", 201 | "bytes", 202 | "bytestring", 203 | "cfg-if", 204 | "cookie", 205 | "derive_more", 206 | "encoding_rs", 207 | "futures-core", 208 | "futures-util", 209 | "itoa", 210 | "language-tags", 211 | "log", 212 | "mime", 213 | "once_cell", 214 | "pin-project-lite", 215 | "regex", 216 | "serde 1.0.136", 217 | "serde_json", 218 | "serde_urlencoded", 219 | "smallvec", 220 | "socket2", 221 | "time", 222 | "url", 223 | ] 224 | 225 | [[package]] 226 | name = "actix-web-codegen" 227 | version = "4.0.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc" 230 | dependencies = [ 231 | "actix-router", 232 | "proc-macro2", 233 | "quote", 234 | "syn", 235 | ] 236 | 237 | [[package]] 238 | name = "aead" 239 | version = "0.4.3" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" 242 | dependencies = [ 243 | "generic-array", 244 | ] 245 | 246 | [[package]] 247 | name = "aes" 248 | version = "0.7.5" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 251 | dependencies = [ 252 | "cfg-if", 253 | "cipher", 254 | "cpufeatures", 255 | "opaque-debug", 256 | ] 257 | 258 | [[package]] 259 | name = "aes-gcm" 260 | version = "0.9.4" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" 263 | dependencies = [ 264 | "aead", 265 | "aes", 266 | "cipher", 267 | "ctr", 268 | "ghash", 269 | "subtle", 270 | ] 271 | 272 | [[package]] 273 | name = "ahash" 274 | version = "0.7.6" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 277 | dependencies = [ 278 | "getrandom", 279 | "once_cell", 280 | "version_check", 281 | ] 282 | 283 | [[package]] 284 | name = "aho-corasick" 285 | version = "0.7.18" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 288 | dependencies = [ 289 | "memchr", 290 | ] 291 | 292 | [[package]] 293 | name = "arrayvec" 294 | version = "0.5.2" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 297 | 298 | [[package]] 299 | name = "askama_escape" 300 | version = "0.10.3" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" 303 | 304 | [[package]] 305 | name = "atoi" 306 | version = "0.4.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" 309 | dependencies = [ 310 | "num-traits 0.2.14", 311 | ] 312 | 313 | [[package]] 314 | name = "atty" 315 | version = "0.2.14" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 318 | dependencies = [ 319 | "hermit-abi", 320 | "libc", 321 | "winapi", 322 | ] 323 | 324 | [[package]] 325 | name = "autocfg" 326 | version = "1.1.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 329 | 330 | [[package]] 331 | name = "base58" 332 | version = "0.2.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" 335 | 336 | [[package]] 337 | name = "base64" 338 | version = "0.13.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 341 | 342 | [[package]] 343 | name = "bitflags" 344 | version = "1.3.2" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 347 | 348 | [[package]] 349 | name = "block-buffer" 350 | version = "0.9.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 353 | dependencies = [ 354 | "generic-array", 355 | ] 356 | 357 | [[package]] 358 | name = "block-buffer" 359 | version = "0.10.2" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" 362 | dependencies = [ 363 | "generic-array", 364 | ] 365 | 366 | [[package]] 367 | name = "bumpalo" 368 | version = "3.9.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 371 | 372 | [[package]] 373 | name = "byteorder" 374 | version = "1.4.3" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 377 | 378 | [[package]] 379 | name = "bytes" 380 | version = "1.1.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 383 | 384 | [[package]] 385 | name = "bytestring" 386 | version = "1.0.0" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" 389 | dependencies = [ 390 | "bytes", 391 | ] 392 | 393 | [[package]] 394 | name = "cc" 395 | version = "1.0.72" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 398 | 399 | [[package]] 400 | name = "cfg-if" 401 | version = "1.0.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 404 | 405 | [[package]] 406 | name = "cipher" 407 | version = "0.3.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 410 | dependencies = [ 411 | "generic-array", 412 | ] 413 | 414 | [[package]] 415 | name = "config" 416 | version = "0.10.1" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" 419 | dependencies = [ 420 | "lazy_static", 421 | "nom 5.1.2", 422 | "rust-ini", 423 | "serde 1.0.136", 424 | "serde-hjson", 425 | "serde_json", 426 | "toml", 427 | "yaml-rust", 428 | ] 429 | 430 | [[package]] 431 | name = "convert_case" 432 | version = "0.4.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 435 | 436 | [[package]] 437 | name = "cookie" 438 | version = "0.16.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" 441 | dependencies = [ 442 | "aes-gcm", 443 | "base64", 444 | "hkdf", 445 | "hmac 0.12.1", 446 | "percent-encoding", 447 | "rand", 448 | "sha2 0.10.2", 449 | "subtle", 450 | "time", 451 | "version_check", 452 | ] 453 | 454 | [[package]] 455 | name = "core-foundation" 456 | version = "0.9.3" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 459 | dependencies = [ 460 | "core-foundation-sys", 461 | "libc", 462 | ] 463 | 464 | [[package]] 465 | name = "core-foundation-sys" 466 | version = "0.8.3" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 469 | 470 | [[package]] 471 | name = "cpufeatures" 472 | version = "0.2.1" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 475 | dependencies = [ 476 | "libc", 477 | ] 478 | 479 | [[package]] 480 | name = "crc" 481 | version = "2.1.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" 484 | dependencies = [ 485 | "crc-catalog", 486 | ] 487 | 488 | [[package]] 489 | name = "crc-catalog" 490 | version = "1.1.1" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" 493 | 494 | [[package]] 495 | name = "crossbeam-queue" 496 | version = "0.3.4" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "4dd435b205a4842da59efd07628f921c096bc1cc0a156835b4fa0bcb9a19bcce" 499 | dependencies = [ 500 | "cfg-if", 501 | "crossbeam-utils", 502 | ] 503 | 504 | [[package]] 505 | name = "crossbeam-utils" 506 | version = "0.8.7" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" 509 | dependencies = [ 510 | "cfg-if", 511 | "lazy_static", 512 | ] 513 | 514 | [[package]] 515 | name = "crypto-common" 516 | version = "0.1.3" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" 519 | dependencies = [ 520 | "generic-array", 521 | "typenum", 522 | ] 523 | 524 | [[package]] 525 | name = "crypto-mac" 526 | version = "0.11.1" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" 529 | dependencies = [ 530 | "generic-array", 531 | "subtle", 532 | ] 533 | 534 | [[package]] 535 | name = "ctr" 536 | version = "0.8.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" 539 | dependencies = [ 540 | "cipher", 541 | ] 542 | 543 | [[package]] 544 | name = "dashmap" 545 | version = "5.1.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "c0834a35a3fce649144119e18da2a4d8ed12ef3862f47183fd46f625d072d96c" 548 | dependencies = [ 549 | "cfg-if", 550 | "num_cpus", 551 | "parking_lot 0.12.0", 552 | ] 553 | 554 | [[package]] 555 | name = "derive_more" 556 | version = "0.99.17" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 559 | dependencies = [ 560 | "convert_case", 561 | "proc-macro2", 562 | "quote", 563 | "rustc_version", 564 | "syn", 565 | ] 566 | 567 | [[package]] 568 | name = "digest" 569 | version = "0.9.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 572 | dependencies = [ 573 | "generic-array", 574 | ] 575 | 576 | [[package]] 577 | name = "digest" 578 | version = "0.10.3" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" 581 | dependencies = [ 582 | "block-buffer 0.10.2", 583 | "crypto-common", 584 | "subtle", 585 | ] 586 | 587 | [[package]] 588 | name = "dirs" 589 | version = "4.0.0" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 592 | dependencies = [ 593 | "dirs-sys", 594 | ] 595 | 596 | [[package]] 597 | name = "dirs-sys" 598 | version = "0.3.6" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" 601 | dependencies = [ 602 | "libc", 603 | "redox_users", 604 | "winapi", 605 | ] 606 | 607 | [[package]] 608 | name = "dotenv" 609 | version = "0.15.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 612 | 613 | [[package]] 614 | name = "either" 615 | version = "1.6.1" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 618 | 619 | [[package]] 620 | name = "encoding_rs" 621 | version = "0.8.30" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" 624 | dependencies = [ 625 | "cfg-if", 626 | ] 627 | 628 | [[package]] 629 | name = "env_logger" 630 | version = "0.9.0" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 633 | dependencies = [ 634 | "atty", 635 | "humantime", 636 | "log", 637 | "regex", 638 | "termcolor", 639 | ] 640 | 641 | [[package]] 642 | name = "fastrand" 643 | version = "1.7.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" 646 | dependencies = [ 647 | "instant", 648 | ] 649 | 650 | [[package]] 651 | name = "firestorm" 652 | version = "0.5.0" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "4d3d6188b8804df28032815ea256b6955c9625c24da7525f387a7af02fbb8f01" 655 | 656 | [[package]] 657 | name = "fnv" 658 | version = "1.0.7" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 661 | 662 | [[package]] 663 | name = "foreign-types" 664 | version = "0.3.2" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 667 | dependencies = [ 668 | "foreign-types-shared", 669 | ] 670 | 671 | [[package]] 672 | name = "foreign-types-shared" 673 | version = "0.1.1" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 676 | 677 | [[package]] 678 | name = "form_urlencoded" 679 | version = "1.0.1" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 682 | dependencies = [ 683 | "matches", 684 | "percent-encoding", 685 | ] 686 | 687 | [[package]] 688 | name = "futures-channel" 689 | version = "0.3.21" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 692 | dependencies = [ 693 | "futures-core", 694 | "futures-sink", 695 | ] 696 | 697 | [[package]] 698 | name = "futures-core" 699 | version = "0.3.21" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 702 | 703 | [[package]] 704 | name = "futures-intrusive" 705 | version = "0.4.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" 708 | dependencies = [ 709 | "futures-core", 710 | "lock_api", 711 | "parking_lot 0.11.2", 712 | ] 713 | 714 | [[package]] 715 | name = "futures-macro" 716 | version = "0.3.21" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 719 | dependencies = [ 720 | "proc-macro2", 721 | "quote", 722 | "syn", 723 | ] 724 | 725 | [[package]] 726 | name = "futures-sink" 727 | version = "0.3.21" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 730 | 731 | [[package]] 732 | name = "futures-task" 733 | version = "0.3.21" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 736 | 737 | [[package]] 738 | name = "futures-util" 739 | version = "0.3.21" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 742 | dependencies = [ 743 | "futures-core", 744 | "futures-macro", 745 | "futures-sink", 746 | "futures-task", 747 | "pin-project-lite", 748 | "pin-utils", 749 | "slab", 750 | ] 751 | 752 | [[package]] 753 | name = "generic-array" 754 | version = "0.14.5" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" 757 | dependencies = [ 758 | "typenum", 759 | "version_check", 760 | ] 761 | 762 | [[package]] 763 | name = "getrandom" 764 | version = "0.2.4" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" 767 | dependencies = [ 768 | "cfg-if", 769 | "libc", 770 | "wasi", 771 | ] 772 | 773 | [[package]] 774 | name = "ghash" 775 | version = "0.4.4" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" 778 | dependencies = [ 779 | "opaque-debug", 780 | "polyval", 781 | ] 782 | 783 | [[package]] 784 | name = "h2" 785 | version = "0.3.11" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" 788 | dependencies = [ 789 | "bytes", 790 | "fnv", 791 | "futures-core", 792 | "futures-sink", 793 | "futures-util", 794 | "http", 795 | "indexmap", 796 | "slab", 797 | "tokio", 798 | "tokio-util 0.6.9", 799 | "tracing", 800 | ] 801 | 802 | [[package]] 803 | name = "hashbrown" 804 | version = "0.11.2" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 807 | dependencies = [ 808 | "ahash", 809 | ] 810 | 811 | [[package]] 812 | name = "hashlink" 813 | version = "0.7.0" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" 816 | dependencies = [ 817 | "hashbrown", 818 | ] 819 | 820 | [[package]] 821 | name = "heck" 822 | version = "0.3.3" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 825 | dependencies = [ 826 | "unicode-segmentation", 827 | ] 828 | 829 | [[package]] 830 | name = "hermit-abi" 831 | version = "0.1.19" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 834 | dependencies = [ 835 | "libc", 836 | ] 837 | 838 | [[package]] 839 | name = "hex" 840 | version = "0.4.3" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 843 | 844 | [[package]] 845 | name = "hkdf" 846 | version = "0.12.3" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" 849 | dependencies = [ 850 | "hmac 0.12.1", 851 | ] 852 | 853 | [[package]] 854 | name = "hmac" 855 | version = "0.11.0" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" 858 | dependencies = [ 859 | "crypto-mac", 860 | "digest 0.9.0", 861 | ] 862 | 863 | [[package]] 864 | name = "hmac" 865 | version = "0.12.1" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 868 | dependencies = [ 869 | "digest 0.10.3", 870 | ] 871 | 872 | [[package]] 873 | name = "http" 874 | version = "0.2.6" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" 877 | dependencies = [ 878 | "bytes", 879 | "fnv", 880 | "itoa", 881 | ] 882 | 883 | [[package]] 884 | name = "http-body" 885 | version = "0.4.4" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" 888 | dependencies = [ 889 | "bytes", 890 | "http", 891 | "pin-project-lite", 892 | ] 893 | 894 | [[package]] 895 | name = "http-range" 896 | version = "0.1.5" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" 899 | 900 | [[package]] 901 | name = "httparse" 902 | version = "1.6.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" 905 | 906 | [[package]] 907 | name = "httpdate" 908 | version = "1.0.2" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 911 | 912 | [[package]] 913 | name = "humantime" 914 | version = "2.1.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 917 | 918 | [[package]] 919 | name = "hyper" 920 | version = "0.14.17" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" 923 | dependencies = [ 924 | "bytes", 925 | "futures-channel", 926 | "futures-core", 927 | "futures-util", 928 | "h2", 929 | "http", 930 | "http-body", 931 | "httparse", 932 | "httpdate", 933 | "itoa", 934 | "pin-project-lite", 935 | "socket2", 936 | "tokio", 937 | "tower-service", 938 | "tracing", 939 | "want", 940 | ] 941 | 942 | [[package]] 943 | name = "hyper-tls" 944 | version = "0.5.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 947 | dependencies = [ 948 | "bytes", 949 | "hyper", 950 | "native-tls", 951 | "tokio", 952 | "tokio-native-tls", 953 | ] 954 | 955 | [[package]] 956 | name = "idna" 957 | version = "0.2.3" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 960 | dependencies = [ 961 | "matches", 962 | "unicode-bidi", 963 | "unicode-normalization", 964 | ] 965 | 966 | [[package]] 967 | name = "indexmap" 968 | version = "1.8.0" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 971 | dependencies = [ 972 | "autocfg", 973 | "hashbrown", 974 | ] 975 | 976 | [[package]] 977 | name = "instant" 978 | version = "0.1.12" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 981 | dependencies = [ 982 | "cfg-if", 983 | ] 984 | 985 | [[package]] 986 | name = "ipnet" 987 | version = "2.3.1" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" 990 | 991 | [[package]] 992 | name = "itertools" 993 | version = "0.10.3" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 996 | dependencies = [ 997 | "either", 998 | ] 999 | 1000 | [[package]] 1001 | name = "itoa" 1002 | version = "1.0.1" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 1005 | 1006 | [[package]] 1007 | name = "js-sys" 1008 | version = "0.3.56" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" 1011 | dependencies = [ 1012 | "wasm-bindgen", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "language-tags" 1017 | version = "0.3.2" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" 1020 | 1021 | [[package]] 1022 | name = "lazy_static" 1023 | version = "1.4.0" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1026 | 1027 | [[package]] 1028 | name = "lexical-core" 1029 | version = "0.7.6" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" 1032 | dependencies = [ 1033 | "arrayvec", 1034 | "bitflags", 1035 | "cfg-if", 1036 | "ryu", 1037 | "static_assertions", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "libc" 1042 | version = "0.2.117" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" 1045 | 1046 | [[package]] 1047 | name = "linked-hash-map" 1048 | version = "0.3.0" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" 1051 | dependencies = [ 1052 | "serde 0.8.23", 1053 | "serde_test", 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "linked-hash-map" 1058 | version = "0.5.4" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 1061 | 1062 | [[package]] 1063 | name = "local-channel" 1064 | version = "0.1.2" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "6246c68cf195087205a0512559c97e15eaf95198bf0e206d662092cdcb03fe9f" 1067 | dependencies = [ 1068 | "futures-core", 1069 | "futures-sink", 1070 | "futures-util", 1071 | "local-waker", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "local-waker" 1076 | version = "0.1.2" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "902eb695eb0591864543cbfbf6d742510642a605a61fc5e97fe6ceb5a30ac4fb" 1079 | 1080 | [[package]] 1081 | name = "lock_api" 1082 | version = "0.4.6" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" 1085 | dependencies = [ 1086 | "scopeguard", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "log" 1091 | version = "0.4.14" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 1094 | dependencies = [ 1095 | "cfg-if", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "matches" 1100 | version = "0.1.9" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 1103 | 1104 | [[package]] 1105 | name = "md-5" 1106 | version = "0.9.1" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" 1109 | dependencies = [ 1110 | "block-buffer 0.9.0", 1111 | "digest 0.9.0", 1112 | "opaque-debug", 1113 | ] 1114 | 1115 | [[package]] 1116 | name = "memchr" 1117 | version = "2.4.1" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 1120 | 1121 | [[package]] 1122 | name = "mime" 1123 | version = "0.3.16" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 1126 | 1127 | [[package]] 1128 | name = "mime_guess" 1129 | version = "2.0.3" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" 1132 | dependencies = [ 1133 | "mime", 1134 | "unicase", 1135 | ] 1136 | 1137 | [[package]] 1138 | name = "minimal-lexical" 1139 | version = "0.2.1" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1142 | 1143 | [[package]] 1144 | name = "mio" 1145 | version = "0.8.0" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" 1148 | dependencies = [ 1149 | "libc", 1150 | "log", 1151 | "miow", 1152 | "ntapi", 1153 | "winapi", 1154 | ] 1155 | 1156 | [[package]] 1157 | name = "miow" 1158 | version = "0.3.7" 1159 | source = "registry+https://github.com/rust-lang/crates.io-index" 1160 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 1161 | dependencies = [ 1162 | "winapi", 1163 | ] 1164 | 1165 | [[package]] 1166 | name = "native-tls" 1167 | version = "0.2.8" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" 1170 | dependencies = [ 1171 | "lazy_static", 1172 | "libc", 1173 | "log", 1174 | "openssl", 1175 | "openssl-probe", 1176 | "openssl-sys", 1177 | "schannel", 1178 | "security-framework", 1179 | "security-framework-sys", 1180 | "tempfile", 1181 | ] 1182 | 1183 | [[package]] 1184 | name = "nom" 1185 | version = "5.1.2" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 1188 | dependencies = [ 1189 | "lexical-core", 1190 | "memchr", 1191 | "version_check", 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "nom" 1196 | version = "7.1.0" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" 1199 | dependencies = [ 1200 | "memchr", 1201 | "minimal-lexical", 1202 | "version_check", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "ntapi" 1207 | version = "0.3.7" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" 1210 | dependencies = [ 1211 | "winapi", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "num-traits" 1216 | version = "0.1.43" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" 1219 | dependencies = [ 1220 | "num-traits 0.2.14", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "num-traits" 1225 | version = "0.2.14" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 1228 | dependencies = [ 1229 | "autocfg", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "num_cpus" 1234 | version = "1.13.1" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 1237 | dependencies = [ 1238 | "hermit-abi", 1239 | "libc", 1240 | ] 1241 | 1242 | [[package]] 1243 | name = "num_threads" 1244 | version = "0.1.3" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" 1247 | dependencies = [ 1248 | "libc", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "once_cell" 1253 | version = "1.9.0" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 1256 | 1257 | [[package]] 1258 | name = "opaque-debug" 1259 | version = "0.3.0" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 1262 | 1263 | [[package]] 1264 | name = "openssl" 1265 | version = "0.10.38" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" 1268 | dependencies = [ 1269 | "bitflags", 1270 | "cfg-if", 1271 | "foreign-types", 1272 | "libc", 1273 | "once_cell", 1274 | "openssl-sys", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "openssl-probe" 1279 | version = "0.1.5" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1282 | 1283 | [[package]] 1284 | name = "openssl-sys" 1285 | version = "0.9.72" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" 1288 | dependencies = [ 1289 | "autocfg", 1290 | "cc", 1291 | "libc", 1292 | "pkg-config", 1293 | "vcpkg", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "parking_lot" 1298 | version = "0.11.2" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 1301 | dependencies = [ 1302 | "instant", 1303 | "lock_api", 1304 | "parking_lot_core 0.8.5", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "parking_lot" 1309 | version = "0.12.0" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" 1312 | dependencies = [ 1313 | "lock_api", 1314 | "parking_lot_core 0.9.1", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "parking_lot_core" 1319 | version = "0.8.5" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 1322 | dependencies = [ 1323 | "cfg-if", 1324 | "instant", 1325 | "libc", 1326 | "redox_syscall", 1327 | "smallvec", 1328 | "winapi", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "parking_lot_core" 1333 | version = "0.9.1" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" 1336 | dependencies = [ 1337 | "cfg-if", 1338 | "libc", 1339 | "redox_syscall", 1340 | "smallvec", 1341 | "windows-sys", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "paste" 1346 | version = "1.0.6" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" 1349 | 1350 | [[package]] 1351 | name = "percent-encoding" 1352 | version = "2.1.0" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1355 | 1356 | [[package]] 1357 | name = "pin-project-lite" 1358 | version = "0.2.8" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" 1361 | 1362 | [[package]] 1363 | name = "pin-utils" 1364 | version = "0.1.0" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1367 | 1368 | [[package]] 1369 | name = "pkg-config" 1370 | version = "0.3.24" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" 1373 | 1374 | [[package]] 1375 | name = "polyval" 1376 | version = "0.5.3" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" 1379 | dependencies = [ 1380 | "cfg-if", 1381 | "cpufeatures", 1382 | "opaque-debug", 1383 | "universal-hash", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "ppv-lite86" 1388 | version = "0.2.16" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 1391 | 1392 | [[package]] 1393 | name = "proc-macro2" 1394 | version = "1.0.36" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 1397 | dependencies = [ 1398 | "unicode-xid", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "quote" 1403 | version = "1.0.15" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 1406 | dependencies = [ 1407 | "proc-macro2", 1408 | ] 1409 | 1410 | [[package]] 1411 | name = "rand" 1412 | version = "0.8.4" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 1415 | dependencies = [ 1416 | "libc", 1417 | "rand_chacha", 1418 | "rand_core", 1419 | "rand_hc", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "rand_chacha" 1424 | version = "0.3.1" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1427 | dependencies = [ 1428 | "ppv-lite86", 1429 | "rand_core", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "rand_core" 1434 | version = "0.6.3" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 1437 | dependencies = [ 1438 | "getrandom", 1439 | ] 1440 | 1441 | [[package]] 1442 | name = "rand_hc" 1443 | version = "0.3.1" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 1446 | dependencies = [ 1447 | "rand_core", 1448 | ] 1449 | 1450 | [[package]] 1451 | name = "redox_syscall" 1452 | version = "0.2.10" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 1455 | dependencies = [ 1456 | "bitflags", 1457 | ] 1458 | 1459 | [[package]] 1460 | name = "redox_users" 1461 | version = "0.4.0" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" 1464 | dependencies = [ 1465 | "getrandom", 1466 | "redox_syscall", 1467 | ] 1468 | 1469 | [[package]] 1470 | name = "regex" 1471 | version = "1.5.4" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 1474 | dependencies = [ 1475 | "aho-corasick", 1476 | "memchr", 1477 | "regex-syntax", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "regex-syntax" 1482 | version = "0.6.25" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 1485 | 1486 | [[package]] 1487 | name = "remove_dir_all" 1488 | version = "0.5.3" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1491 | dependencies = [ 1492 | "winapi", 1493 | ] 1494 | 1495 | [[package]] 1496 | name = "reqwest" 1497 | version = "0.11.9" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" 1500 | dependencies = [ 1501 | "base64", 1502 | "bytes", 1503 | "encoding_rs", 1504 | "futures-core", 1505 | "futures-util", 1506 | "h2", 1507 | "http", 1508 | "http-body", 1509 | "hyper", 1510 | "hyper-tls", 1511 | "ipnet", 1512 | "js-sys", 1513 | "lazy_static", 1514 | "log", 1515 | "mime", 1516 | "native-tls", 1517 | "percent-encoding", 1518 | "pin-project-lite", 1519 | "serde 1.0.136", 1520 | "serde_json", 1521 | "serde_urlencoded", 1522 | "tokio", 1523 | "tokio-native-tls", 1524 | "url", 1525 | "wasm-bindgen", 1526 | "wasm-bindgen-futures", 1527 | "web-sys", 1528 | "winreg", 1529 | ] 1530 | 1531 | [[package]] 1532 | name = "rust-ini" 1533 | version = "0.13.0" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" 1536 | 1537 | [[package]] 1538 | name = "rustc_version" 1539 | version = "0.4.0" 1540 | source = "registry+https://github.com/rust-lang/crates.io-index" 1541 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1542 | dependencies = [ 1543 | "semver", 1544 | ] 1545 | 1546 | [[package]] 1547 | name = "ryu" 1548 | version = "1.0.9" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 1551 | 1552 | [[package]] 1553 | name = "schannel" 1554 | version = "0.1.19" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 1557 | dependencies = [ 1558 | "lazy_static", 1559 | "winapi", 1560 | ] 1561 | 1562 | [[package]] 1563 | name = "scopeguard" 1564 | version = "1.1.0" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1567 | 1568 | [[package]] 1569 | name = "security-framework" 1570 | version = "2.6.1" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" 1573 | dependencies = [ 1574 | "bitflags", 1575 | "core-foundation", 1576 | "core-foundation-sys", 1577 | "libc", 1578 | "security-framework-sys", 1579 | ] 1580 | 1581 | [[package]] 1582 | name = "security-framework-sys" 1583 | version = "2.6.1" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 1586 | dependencies = [ 1587 | "core-foundation-sys", 1588 | "libc", 1589 | ] 1590 | 1591 | [[package]] 1592 | name = "semver" 1593 | version = "1.0.5" 1594 | source = "registry+https://github.com/rust-lang/crates.io-index" 1595 | checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" 1596 | 1597 | [[package]] 1598 | name = "serde" 1599 | version = "0.8.23" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" 1602 | 1603 | [[package]] 1604 | name = "serde" 1605 | version = "1.0.136" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 1608 | dependencies = [ 1609 | "serde_derive", 1610 | ] 1611 | 1612 | [[package]] 1613 | name = "serde-hjson" 1614 | version = "0.9.1" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" 1617 | dependencies = [ 1618 | "lazy_static", 1619 | "linked-hash-map 0.3.0", 1620 | "num-traits 0.1.43", 1621 | "regex", 1622 | "serde 0.8.23", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "serde_derive" 1627 | version = "1.0.136" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 1630 | dependencies = [ 1631 | "proc-macro2", 1632 | "quote", 1633 | "syn", 1634 | ] 1635 | 1636 | [[package]] 1637 | name = "serde_json" 1638 | version = "1.0.78" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" 1641 | dependencies = [ 1642 | "itoa", 1643 | "ryu", 1644 | "serde 1.0.136", 1645 | ] 1646 | 1647 | [[package]] 1648 | name = "serde_test" 1649 | version = "0.8.23" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" 1652 | dependencies = [ 1653 | "serde 0.8.23", 1654 | ] 1655 | 1656 | [[package]] 1657 | name = "serde_urlencoded" 1658 | version = "0.7.1" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1661 | dependencies = [ 1662 | "form_urlencoded", 1663 | "itoa", 1664 | "ryu", 1665 | "serde 1.0.136", 1666 | ] 1667 | 1668 | [[package]] 1669 | name = "server" 1670 | version = "0.3.0" 1671 | dependencies = [ 1672 | "actix-cors", 1673 | "actix-files", 1674 | "actix-http", 1675 | "actix-session", 1676 | "actix-web", 1677 | "base58", 1678 | "config", 1679 | "dashmap", 1680 | "dotenv", 1681 | "env_logger", 1682 | "futures-util", 1683 | "lazy_static", 1684 | "log", 1685 | "num_cpus", 1686 | "rand", 1687 | "regex", 1688 | "reqwest", 1689 | "serde 1.0.136", 1690 | "serde_json", 1691 | "sqlx", 1692 | "url", 1693 | "uuid", 1694 | ] 1695 | 1696 | [[package]] 1697 | name = "sha-1" 1698 | version = "0.9.8" 1699 | source = "registry+https://github.com/rust-lang/crates.io-index" 1700 | checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" 1701 | dependencies = [ 1702 | "block-buffer 0.9.0", 1703 | "cfg-if", 1704 | "cpufeatures", 1705 | "digest 0.9.0", 1706 | "opaque-debug", 1707 | ] 1708 | 1709 | [[package]] 1710 | name = "sha-1" 1711 | version = "0.10.0" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" 1714 | dependencies = [ 1715 | "cfg-if", 1716 | "cpufeatures", 1717 | "digest 0.10.3", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "sha2" 1722 | version = "0.9.9" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" 1725 | dependencies = [ 1726 | "block-buffer 0.9.0", 1727 | "cfg-if", 1728 | "cpufeatures", 1729 | "digest 0.9.0", 1730 | "opaque-debug", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "sha2" 1735 | version = "0.10.2" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" 1738 | dependencies = [ 1739 | "cfg-if", 1740 | "cpufeatures", 1741 | "digest 0.10.3", 1742 | ] 1743 | 1744 | [[package]] 1745 | name = "signal-hook-registry" 1746 | version = "1.4.0" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1749 | dependencies = [ 1750 | "libc", 1751 | ] 1752 | 1753 | [[package]] 1754 | name = "slab" 1755 | version = "0.4.5" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 1758 | 1759 | [[package]] 1760 | name = "smallvec" 1761 | version = "1.8.0" 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" 1763 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 1764 | 1765 | [[package]] 1766 | name = "socket2" 1767 | version = "0.4.4" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 1770 | dependencies = [ 1771 | "libc", 1772 | "winapi", 1773 | ] 1774 | 1775 | [[package]] 1776 | name = "sqlformat" 1777 | version = "0.1.8" 1778 | source = "registry+https://github.com/rust-lang/crates.io-index" 1779 | checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" 1780 | dependencies = [ 1781 | "itertools", 1782 | "nom 7.1.0", 1783 | "unicode_categories", 1784 | ] 1785 | 1786 | [[package]] 1787 | name = "sqlx" 1788 | version = "0.5.11" 1789 | source = "registry+https://github.com/rust-lang/crates.io-index" 1790 | checksum = "fc15591eb44ffb5816a4a70a7efd5dd87bfd3aa84c4c200401c4396140525826" 1791 | dependencies = [ 1792 | "sqlx-core", 1793 | "sqlx-macros", 1794 | ] 1795 | 1796 | [[package]] 1797 | name = "sqlx-core" 1798 | version = "0.5.11" 1799 | source = "registry+https://github.com/rust-lang/crates.io-index" 1800 | checksum = "195183bf6ff8328bb82c0511a83faf60aacf75840103388851db61d7a9854ae3" 1801 | dependencies = [ 1802 | "ahash", 1803 | "atoi", 1804 | "base64", 1805 | "bitflags", 1806 | "byteorder", 1807 | "bytes", 1808 | "crc", 1809 | "crossbeam-queue", 1810 | "dirs", 1811 | "either", 1812 | "futures-channel", 1813 | "futures-core", 1814 | "futures-intrusive", 1815 | "futures-util", 1816 | "hashlink", 1817 | "hex", 1818 | "hmac 0.11.0", 1819 | "indexmap", 1820 | "itoa", 1821 | "libc", 1822 | "log", 1823 | "md-5", 1824 | "memchr", 1825 | "once_cell", 1826 | "paste", 1827 | "percent-encoding", 1828 | "rand", 1829 | "serde 1.0.136", 1830 | "serde_json", 1831 | "sha-1 0.9.8", 1832 | "sha2 0.9.9", 1833 | "smallvec", 1834 | "sqlformat", 1835 | "sqlx-rt", 1836 | "stringprep", 1837 | "thiserror", 1838 | "tokio-stream", 1839 | "url", 1840 | "whoami", 1841 | ] 1842 | 1843 | [[package]] 1844 | name = "sqlx-macros" 1845 | version = "0.5.11" 1846 | source = "registry+https://github.com/rust-lang/crates.io-index" 1847 | checksum = "eee35713129561f5e55c554bba1c378e2a7e67f81257b7311183de98c50e6f94" 1848 | dependencies = [ 1849 | "dotenv", 1850 | "either", 1851 | "heck", 1852 | "once_cell", 1853 | "proc-macro2", 1854 | "quote", 1855 | "sha2 0.9.9", 1856 | "sqlx-core", 1857 | "sqlx-rt", 1858 | "syn", 1859 | "url", 1860 | ] 1861 | 1862 | [[package]] 1863 | name = "sqlx-rt" 1864 | version = "0.5.11" 1865 | source = "registry+https://github.com/rust-lang/crates.io-index" 1866 | checksum = "b555e70fbbf84e269ec3858b7a6515bcfe7a166a7cc9c636dd6efd20431678b6" 1867 | dependencies = [ 1868 | "native-tls", 1869 | "once_cell", 1870 | "tokio", 1871 | "tokio-native-tls", 1872 | ] 1873 | 1874 | [[package]] 1875 | name = "static_assertions" 1876 | version = "1.1.0" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1879 | 1880 | [[package]] 1881 | name = "stringprep" 1882 | version = "0.1.2" 1883 | source = "registry+https://github.com/rust-lang/crates.io-index" 1884 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 1885 | dependencies = [ 1886 | "unicode-bidi", 1887 | "unicode-normalization", 1888 | ] 1889 | 1890 | [[package]] 1891 | name = "subtle" 1892 | version = "2.4.1" 1893 | source = "registry+https://github.com/rust-lang/crates.io-index" 1894 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1895 | 1896 | [[package]] 1897 | name = "syn" 1898 | version = "1.0.86" 1899 | source = "registry+https://github.com/rust-lang/crates.io-index" 1900 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 1901 | dependencies = [ 1902 | "proc-macro2", 1903 | "quote", 1904 | "unicode-xid", 1905 | ] 1906 | 1907 | [[package]] 1908 | name = "tempfile" 1909 | version = "3.3.0" 1910 | source = "registry+https://github.com/rust-lang/crates.io-index" 1911 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1912 | dependencies = [ 1913 | "cfg-if", 1914 | "fastrand", 1915 | "libc", 1916 | "redox_syscall", 1917 | "remove_dir_all", 1918 | "winapi", 1919 | ] 1920 | 1921 | [[package]] 1922 | name = "termcolor" 1923 | version = "1.1.2" 1924 | source = "registry+https://github.com/rust-lang/crates.io-index" 1925 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1926 | dependencies = [ 1927 | "winapi-util", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "thiserror" 1932 | version = "1.0.30" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 1935 | dependencies = [ 1936 | "thiserror-impl", 1937 | ] 1938 | 1939 | [[package]] 1940 | name = "thiserror-impl" 1941 | version = "1.0.30" 1942 | source = "registry+https://github.com/rust-lang/crates.io-index" 1943 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 1944 | dependencies = [ 1945 | "proc-macro2", 1946 | "quote", 1947 | "syn", 1948 | ] 1949 | 1950 | [[package]] 1951 | name = "time" 1952 | version = "0.3.7" 1953 | source = "registry+https://github.com/rust-lang/crates.io-index" 1954 | checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" 1955 | dependencies = [ 1956 | "itoa", 1957 | "libc", 1958 | "num_threads", 1959 | "time-macros", 1960 | ] 1961 | 1962 | [[package]] 1963 | name = "time-macros" 1964 | version = "0.2.3" 1965 | source = "registry+https://github.com/rust-lang/crates.io-index" 1966 | checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" 1967 | 1968 | [[package]] 1969 | name = "tinyvec" 1970 | version = "1.5.1" 1971 | source = "registry+https://github.com/rust-lang/crates.io-index" 1972 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 1973 | dependencies = [ 1974 | "tinyvec_macros", 1975 | ] 1976 | 1977 | [[package]] 1978 | name = "tinyvec_macros" 1979 | version = "0.1.0" 1980 | source = "registry+https://github.com/rust-lang/crates.io-index" 1981 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1982 | 1983 | [[package]] 1984 | name = "tokio" 1985 | version = "1.17.0" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" 1988 | dependencies = [ 1989 | "bytes", 1990 | "libc", 1991 | "memchr", 1992 | "mio", 1993 | "num_cpus", 1994 | "once_cell", 1995 | "parking_lot 0.12.0", 1996 | "pin-project-lite", 1997 | "signal-hook-registry", 1998 | "socket2", 1999 | "winapi", 2000 | ] 2001 | 2002 | [[package]] 2003 | name = "tokio-native-tls" 2004 | version = "0.3.0" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 2007 | dependencies = [ 2008 | "native-tls", 2009 | "tokio", 2010 | ] 2011 | 2012 | [[package]] 2013 | name = "tokio-stream" 2014 | version = "0.1.8" 2015 | source = "registry+https://github.com/rust-lang/crates.io-index" 2016 | checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" 2017 | dependencies = [ 2018 | "futures-core", 2019 | "pin-project-lite", 2020 | "tokio", 2021 | ] 2022 | 2023 | [[package]] 2024 | name = "tokio-util" 2025 | version = "0.6.9" 2026 | source = "registry+https://github.com/rust-lang/crates.io-index" 2027 | checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" 2028 | dependencies = [ 2029 | "bytes", 2030 | "futures-core", 2031 | "futures-sink", 2032 | "log", 2033 | "pin-project-lite", 2034 | "tokio", 2035 | ] 2036 | 2037 | [[package]] 2038 | name = "tokio-util" 2039 | version = "0.7.0" 2040 | source = "registry+https://github.com/rust-lang/crates.io-index" 2041 | checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" 2042 | dependencies = [ 2043 | "bytes", 2044 | "futures-core", 2045 | "futures-sink", 2046 | "log", 2047 | "pin-project-lite", 2048 | "tokio", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "toml" 2053 | version = "0.5.8" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 2056 | dependencies = [ 2057 | "serde 1.0.136", 2058 | ] 2059 | 2060 | [[package]] 2061 | name = "tower-service" 2062 | version = "0.3.1" 2063 | source = "registry+https://github.com/rust-lang/crates.io-index" 2064 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 2065 | 2066 | [[package]] 2067 | name = "tracing" 2068 | version = "0.1.30" 2069 | source = "registry+https://github.com/rust-lang/crates.io-index" 2070 | checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9" 2071 | dependencies = [ 2072 | "cfg-if", 2073 | "pin-project-lite", 2074 | "tracing-core", 2075 | ] 2076 | 2077 | [[package]] 2078 | name = "tracing-core" 2079 | version = "0.1.22" 2080 | source = "registry+https://github.com/rust-lang/crates.io-index" 2081 | checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" 2082 | dependencies = [ 2083 | "lazy_static", 2084 | ] 2085 | 2086 | [[package]] 2087 | name = "try-lock" 2088 | version = "0.2.3" 2089 | source = "registry+https://github.com/rust-lang/crates.io-index" 2090 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 2091 | 2092 | [[package]] 2093 | name = "typenum" 2094 | version = "1.15.0" 2095 | source = "registry+https://github.com/rust-lang/crates.io-index" 2096 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 2097 | 2098 | [[package]] 2099 | name = "unicase" 2100 | version = "2.6.0" 2101 | source = "registry+https://github.com/rust-lang/crates.io-index" 2102 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 2103 | dependencies = [ 2104 | "version_check", 2105 | ] 2106 | 2107 | [[package]] 2108 | name = "unicode-bidi" 2109 | version = "0.3.7" 2110 | source = "registry+https://github.com/rust-lang/crates.io-index" 2111 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 2112 | 2113 | [[package]] 2114 | name = "unicode-normalization" 2115 | version = "0.1.19" 2116 | source = "registry+https://github.com/rust-lang/crates.io-index" 2117 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 2118 | dependencies = [ 2119 | "tinyvec", 2120 | ] 2121 | 2122 | [[package]] 2123 | name = "unicode-segmentation" 2124 | version = "1.9.0" 2125 | source = "registry+https://github.com/rust-lang/crates.io-index" 2126 | checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 2127 | 2128 | [[package]] 2129 | name = "unicode-xid" 2130 | version = "0.2.2" 2131 | source = "registry+https://github.com/rust-lang/crates.io-index" 2132 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 2133 | 2134 | [[package]] 2135 | name = "unicode_categories" 2136 | version = "0.1.1" 2137 | source = "registry+https://github.com/rust-lang/crates.io-index" 2138 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 2139 | 2140 | [[package]] 2141 | name = "universal-hash" 2142 | version = "0.4.1" 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" 2144 | checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" 2145 | dependencies = [ 2146 | "generic-array", 2147 | "subtle", 2148 | ] 2149 | 2150 | [[package]] 2151 | name = "url" 2152 | version = "2.2.2" 2153 | source = "registry+https://github.com/rust-lang/crates.io-index" 2154 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 2155 | dependencies = [ 2156 | "form_urlencoded", 2157 | "idna", 2158 | "matches", 2159 | "percent-encoding", 2160 | ] 2161 | 2162 | [[package]] 2163 | name = "uuid" 2164 | version = "0.8.2" 2165 | source = "registry+https://github.com/rust-lang/crates.io-index" 2166 | checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" 2167 | dependencies = [ 2168 | "getrandom", 2169 | ] 2170 | 2171 | [[package]] 2172 | name = "vcpkg" 2173 | version = "0.2.15" 2174 | source = "registry+https://github.com/rust-lang/crates.io-index" 2175 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2176 | 2177 | [[package]] 2178 | name = "version_check" 2179 | version = "0.9.4" 2180 | source = "registry+https://github.com/rust-lang/crates.io-index" 2181 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2182 | 2183 | [[package]] 2184 | name = "want" 2185 | version = "0.3.0" 2186 | source = "registry+https://github.com/rust-lang/crates.io-index" 2187 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 2188 | dependencies = [ 2189 | "log", 2190 | "try-lock", 2191 | ] 2192 | 2193 | [[package]] 2194 | name = "wasi" 2195 | version = "0.10.2+wasi-snapshot-preview1" 2196 | source = "registry+https://github.com/rust-lang/crates.io-index" 2197 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 2198 | 2199 | [[package]] 2200 | name = "wasm-bindgen" 2201 | version = "0.2.79" 2202 | source = "registry+https://github.com/rust-lang/crates.io-index" 2203 | checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" 2204 | dependencies = [ 2205 | "cfg-if", 2206 | "wasm-bindgen-macro", 2207 | ] 2208 | 2209 | [[package]] 2210 | name = "wasm-bindgen-backend" 2211 | version = "0.2.79" 2212 | source = "registry+https://github.com/rust-lang/crates.io-index" 2213 | checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" 2214 | dependencies = [ 2215 | "bumpalo", 2216 | "lazy_static", 2217 | "log", 2218 | "proc-macro2", 2219 | "quote", 2220 | "syn", 2221 | "wasm-bindgen-shared", 2222 | ] 2223 | 2224 | [[package]] 2225 | name = "wasm-bindgen-futures" 2226 | version = "0.4.29" 2227 | source = "registry+https://github.com/rust-lang/crates.io-index" 2228 | checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" 2229 | dependencies = [ 2230 | "cfg-if", 2231 | "js-sys", 2232 | "wasm-bindgen", 2233 | "web-sys", 2234 | ] 2235 | 2236 | [[package]] 2237 | name = "wasm-bindgen-macro" 2238 | version = "0.2.79" 2239 | source = "registry+https://github.com/rust-lang/crates.io-index" 2240 | checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" 2241 | dependencies = [ 2242 | "quote", 2243 | "wasm-bindgen-macro-support", 2244 | ] 2245 | 2246 | [[package]] 2247 | name = "wasm-bindgen-macro-support" 2248 | version = "0.2.79" 2249 | source = "registry+https://github.com/rust-lang/crates.io-index" 2250 | checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" 2251 | dependencies = [ 2252 | "proc-macro2", 2253 | "quote", 2254 | "syn", 2255 | "wasm-bindgen-backend", 2256 | "wasm-bindgen-shared", 2257 | ] 2258 | 2259 | [[package]] 2260 | name = "wasm-bindgen-shared" 2261 | version = "0.2.79" 2262 | source = "registry+https://github.com/rust-lang/crates.io-index" 2263 | checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" 2264 | 2265 | [[package]] 2266 | name = "web-sys" 2267 | version = "0.3.56" 2268 | source = "registry+https://github.com/rust-lang/crates.io-index" 2269 | checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" 2270 | dependencies = [ 2271 | "js-sys", 2272 | "wasm-bindgen", 2273 | ] 2274 | 2275 | [[package]] 2276 | name = "whoami" 2277 | version = "1.2.1" 2278 | source = "registry+https://github.com/rust-lang/crates.io-index" 2279 | checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" 2280 | dependencies = [ 2281 | "wasm-bindgen", 2282 | "web-sys", 2283 | ] 2284 | 2285 | [[package]] 2286 | name = "winapi" 2287 | version = "0.3.9" 2288 | source = "registry+https://github.com/rust-lang/crates.io-index" 2289 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2290 | dependencies = [ 2291 | "winapi-i686-pc-windows-gnu", 2292 | "winapi-x86_64-pc-windows-gnu", 2293 | ] 2294 | 2295 | [[package]] 2296 | name = "winapi-i686-pc-windows-gnu" 2297 | version = "0.4.0" 2298 | source = "registry+https://github.com/rust-lang/crates.io-index" 2299 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2300 | 2301 | [[package]] 2302 | name = "winapi-util" 2303 | version = "0.1.5" 2304 | source = "registry+https://github.com/rust-lang/crates.io-index" 2305 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 2306 | dependencies = [ 2307 | "winapi", 2308 | ] 2309 | 2310 | [[package]] 2311 | name = "winapi-x86_64-pc-windows-gnu" 2312 | version = "0.4.0" 2313 | source = "registry+https://github.com/rust-lang/crates.io-index" 2314 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2315 | 2316 | [[package]] 2317 | name = "windows-sys" 2318 | version = "0.32.0" 2319 | source = "registry+https://github.com/rust-lang/crates.io-index" 2320 | checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" 2321 | dependencies = [ 2322 | "windows_aarch64_msvc", 2323 | "windows_i686_gnu", 2324 | "windows_i686_msvc", 2325 | "windows_x86_64_gnu", 2326 | "windows_x86_64_msvc", 2327 | ] 2328 | 2329 | [[package]] 2330 | name = "windows_aarch64_msvc" 2331 | version = "0.32.0" 2332 | source = "registry+https://github.com/rust-lang/crates.io-index" 2333 | checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" 2334 | 2335 | [[package]] 2336 | name = "windows_i686_gnu" 2337 | version = "0.32.0" 2338 | source = "registry+https://github.com/rust-lang/crates.io-index" 2339 | checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" 2340 | 2341 | [[package]] 2342 | name = "windows_i686_msvc" 2343 | version = "0.32.0" 2344 | source = "registry+https://github.com/rust-lang/crates.io-index" 2345 | checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" 2346 | 2347 | [[package]] 2348 | name = "windows_x86_64_gnu" 2349 | version = "0.32.0" 2350 | source = "registry+https://github.com/rust-lang/crates.io-index" 2351 | checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" 2352 | 2353 | [[package]] 2354 | name = "windows_x86_64_msvc" 2355 | version = "0.32.0" 2356 | source = "registry+https://github.com/rust-lang/crates.io-index" 2357 | checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" 2358 | 2359 | [[package]] 2360 | name = "winreg" 2361 | version = "0.7.0" 2362 | source = "registry+https://github.com/rust-lang/crates.io-index" 2363 | checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 2364 | dependencies = [ 2365 | "winapi", 2366 | ] 2367 | 2368 | [[package]] 2369 | name = "yaml-rust" 2370 | version = "0.4.5" 2371 | source = "registry+https://github.com/rust-lang/crates.io-index" 2372 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 2373 | dependencies = [ 2374 | "linked-hash-map 0.5.4", 2375 | ] 2376 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "server" 3 | version = "0.3.0" 4 | authors = ["Zhiyuan Zheng "] 5 | edition = "2021" 6 | repository = "https://github.com/zhzy0077/PipeHub" 7 | 8 | [dependencies] 9 | actix-cors = "0.6" 10 | actix-files = "0.6" 11 | actix-http = { version = "3.0", default-features = false } 12 | actix-session = "0.5" 13 | actix-web = { version = "^4.0", features = [ "macros" ], default-features = false } 14 | base58 = "0.2" 15 | config = "0.10" 16 | dashmap = "5.1" 17 | dotenv = "0.15" 18 | futures-util = "0.3" 19 | lazy_static = "1.4" 20 | log = "0.4" 21 | env_logger = "0.9" 22 | num_cpus = "1.13" 23 | rand = "0.8" 24 | reqwest = { version = "0.11", features = ["json"] } 25 | regex = { version = "1.5", default-features = false } 26 | serde = { version = "1.0", features = ["derive"] } 27 | serde_json = "1.0" 28 | sqlx = { version = "0.5", features = ["runtime-tokio-native-tls", "macros", "migrate", "postgres"], default-features = false } 29 | url = "2.1" 30 | uuid = { version = "0.8", features = ["v4"] } 31 | -------------------------------------------------------------------------------- /server/Procfile: -------------------------------------------------------------------------------- 1 | web: ./target/release/server -------------------------------------------------------------------------------- /server/build.rs: -------------------------------------------------------------------------------- 1 | // generated by `sqlx migrate build-script` 2 | fn main() { 3 | // trigger recompilation when a new migration is added 4 | println!("cargo:rerun-if-changed=migrations"); 5 | } -------------------------------------------------------------------------------- /server/migrations/20220215080801_init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS TENANTS 2 | ( 3 | ID BIGSERIAL PRIMARY KEY, 4 | APP_ID BIGINT NOT NULL, 5 | GITHUB_LOGIN VARCHAR NOT NULL, 6 | GITHUB_ID BIGINT NOT NULL, 7 | BLOCK_LIST TEXT DEFAULT '' NOT NULL, 8 | CAPTCHA BOOLEAN DEFAULT FALSE NOT NULL 9 | ); 10 | 11 | CREATE TABLE IF NOT EXISTS WECHAT_WORKS 12 | ( 13 | ID BIGSERIAL PRIMARY KEY, 14 | TENANT_ID BIGINT NOT NULL, 15 | CORP_ID VARCHAR NOT NULL, 16 | AGENT_ID BIGINT NOT NULL, 17 | SECRET VARCHAR NOT NULL, 18 | BOT_TOKEN TEXT DEFAULT '' NOT NULL, 19 | CHAT_ID TEXT DEFAULT '' NOT NULL 20 | ); 21 | 22 | CREATE UNIQUE INDEX IF NOT EXISTS WECHAT_WORKS_TENANT_ID_UINDEX 23 | ON WECHAT_WORKS (TENANT_ID); 24 | 25 | -------------------------------------------------------------------------------- /server/migrations/20220303081032_microsoft.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE TENANTS 2 | ADD msft_refresh_token VARCHAR DEFAULT ''; 3 | 4 | ALTER TABLE TENANTS 5 | ADD msft_task_list_id VARCHAR DEFAULT ''; 6 | -------------------------------------------------------------------------------- /server/rust-toolchain: -------------------------------------------------------------------------------- 1 | stable -------------------------------------------------------------------------------- /server/src/captcha.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use regex::Regex; 3 | 4 | lazy_static! { 5 | static ref RE: Regex = 6 | Regex::new(r"^【(?P.+)】.*?(?P[a-zA-Z\-0-9]{4,8})").unwrap(); 7 | } 8 | 9 | pub fn captcha(content: &str) -> String { 10 | if let Some(captures) = RE.captures(content) { 11 | return format!("{} - {}", &captures["Number"], &captures["Sender"]); 12 | } 13 | String::new() 14 | } 15 | -------------------------------------------------------------------------------- /server/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use config::{Config, Environment}; 3 | use serde::Deserialize; 4 | use std::env; 5 | 6 | #[derive(Debug, Clone, Deserialize)] 7 | pub struct PipeHubConfig { 8 | pub host: String, 9 | pub port: u16, 10 | pub domain: String, 11 | pub domain_web: String, 12 | // If we need to make cookie secure. 13 | pub https: bool, 14 | pub database_url: String, 15 | pub github: GitHubConfig, 16 | pub microsoft: MicrosoftConfig, 17 | } 18 | 19 | #[derive(Debug, Clone, Deserialize)] 20 | pub struct GitHubConfig { 21 | pub client_id: String, 22 | pub client_secret: String, 23 | pub callback_url: String, 24 | } 25 | 26 | #[derive(Debug, Clone, Deserialize)] 27 | pub struct MicrosoftConfig { 28 | pub client_id: String, 29 | pub client_secret: String, 30 | pub callback_url: String, 31 | } 32 | 33 | impl PipeHubConfig { 34 | pub fn new() -> Result { 35 | let environment = Environment::new().prefix("pipehub").separator("__"); 36 | let mut config = Config::new(); 37 | 38 | config.merge(environment)?; 39 | let config = config.try_into()?; 40 | 41 | Ok(config) 42 | } 43 | 44 | pub fn bind_addr(&self) -> String { 45 | let port = env::var("PORT").unwrap_or_else(|_| self.port.to_string()); 46 | format!("{}:{}", self.host, port) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/src/data.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::models::{Tenant, WechatWork}; 3 | use log::LevelFilter; 4 | use std::time::Duration; 5 | 6 | use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; 7 | use sqlx::{ConnectOptions, PgPool}; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct Pool { 11 | inner: PgPool, 12 | } 13 | 14 | impl Pool { 15 | pub async fn new(conn_str: &str) -> Result { 16 | let num_cpus = num_cpus::get() as u32; 17 | 18 | let mut connect_options = conn_str.parse::()?; 19 | 20 | connect_options.log_statements(LevelFilter::Debug); 21 | connect_options.log_slow_statements(LevelFilter::Info, Duration::from_secs(1)); 22 | 23 | let inner = PgPoolOptions::new() 24 | .min_connections(num_cpus) 25 | .max_connections(num_cpus * 4) 26 | .connect_with(connect_options) 27 | .await?; 28 | 29 | Ok(Pool { inner }) 30 | } 31 | 32 | pub async fn migrate(&self) -> Result<()> { 33 | return Ok(sqlx::migrate!("./migrations").run(&self.inner).await?); 34 | } 35 | 36 | pub async fn find_tenant_by_id(&self, tenant_id: i64) -> Result> { 37 | let tenant = sqlx::query_as::<_, Tenant>("SELECT * FROM tenants WHERE id = $1") 38 | .bind(tenant_id) 39 | .fetch_optional(&self.inner) 40 | .await?; 41 | 42 | Ok(tenant) 43 | } 44 | 45 | pub async fn find_tenant_by_github_id(&self, github_id: i64) -> Result> { 46 | let tenant = sqlx::query_as::<_, Tenant>("SELECT * FROM tenants WHERE github_id = $1") 47 | .bind(github_id) 48 | .fetch_optional(&self.inner) 49 | .await?; 50 | 51 | Ok(tenant) 52 | } 53 | 54 | pub async fn find_tenant_by_app_id(&self, app_id: i64) -> Result> { 55 | let tenant = sqlx::query_as::<_, Tenant>("SELECT * FROM tenants WHERE app_id = $1") 56 | .bind(app_id) 57 | .fetch_optional(&self.inner) 58 | .await?; 59 | 60 | Ok(tenant) 61 | } 62 | 63 | pub async fn insert_tenant(&self, tenant: Tenant) -> Result { 64 | let tenant = sqlx::query_as::<_, Tenant>( 65 | "INSERT INTO tenants (app_id, github_login, github_id) VALUES ($1, $2, $3) RETURNING *", 66 | ) 67 | .bind(tenant.app_id) 68 | .bind(tenant.github_login) 69 | .bind(tenant.github_id) 70 | .fetch_one(&self.inner) 71 | .await?; 72 | 73 | Ok(tenant) 74 | } 75 | 76 | pub async fn update_tenant(&self, tenant: Tenant) -> Result<()> { 77 | sqlx::query( 78 | "UPDATE tenants SET app_id = $1, block_list = $2, captcha = $3, msft_refresh_token = $4, msft_task_list_id = $5 WHERE id = $6", 79 | ) 80 | .bind(tenant.app_id) 81 | .bind(tenant.block_list) 82 | .bind(tenant.captcha) 83 | .bind(tenant.msft_refresh_token) 84 | .bind(tenant.msft_task_list_id) 85 | .bind(tenant.id) 86 | .execute(&self.inner) 87 | .await?; 88 | 89 | Ok(()) 90 | } 91 | 92 | pub async fn find_wechat_by_id(&self, tenant_id: i64) -> Result> { 93 | let wechat_work = 94 | sqlx::query_as::<_, WechatWork>("SELECT * FROM wechat_works WHERE tenant_id = $1") 95 | .bind(tenant_id) 96 | .fetch_optional(&self.inner) 97 | .await?; 98 | 99 | Ok(wechat_work) 100 | } 101 | 102 | pub async fn upsert_wechat(&self, new_wechat: WechatWork) -> Result<()> { 103 | sqlx::query( 104 | "INSERT INTO wechat_works (tenant_id, corp_id, agent_id, secret, bot_token, chat_id) 105 | VALUES ($1, $2, $3, $4, $5, $6) 106 | ON CONFLICT (tenant_id) 107 | DO UPDATE SET corp_id = $2, 108 | agent_id = $3, 109 | secret = $4, 110 | bot_token = $5, 111 | chat_id = $6 112 | ", 113 | ) 114 | .bind(new_wechat.tenant_id) 115 | .bind(new_wechat.corp_id) 116 | .bind(new_wechat.agent_id) 117 | .bind(new_wechat.secret) 118 | .bind(new_wechat.bot_token) 119 | .bind(new_wechat.chat_id) 120 | .execute(&self.inner) 121 | .await?; 122 | 123 | Ok(()) 124 | } 125 | 126 | pub async fn find_wechat_by_app_id(&self, app_id: i64) -> Result> { 127 | let wechat_work = sqlx::query_as::<_, WechatWork>( 128 | "SELECT wechat_works.* 129 | FROM wechat_works 130 | LEFT JOIN tenants ON wechat_works.tenant_id = tenants.id 131 | WHERE app_id = $1", 132 | ) 133 | .bind(app_id) 134 | .fetch_optional(&self.inner) 135 | .await?; 136 | 137 | Ok(wechat_work) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /server/src/error.rs: -------------------------------------------------------------------------------- 1 | use actix_http::body::BoxBody; 2 | use actix_web::http::StatusCode; 3 | use actix_web::{HttpResponse, ResponseError}; 4 | use sqlx::migrate::MigrateError; 5 | use std::fmt; 6 | use std::fmt::Display; 7 | 8 | #[derive(Debug)] 9 | pub enum Error { 10 | Initialization(config::ConfigError), 11 | Migrate(MigrateError), 12 | DataAccess(String), 13 | Execution(String), 14 | Io(std::io::Error), 15 | Dependency(String), 16 | Unexpected(String), 17 | User(&'static str), 18 | } 19 | 20 | pub type Result = std::result::Result; 21 | 22 | impl Display for Error { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | write!(f, "{:?}", self) 25 | } 26 | } 27 | 28 | impl ResponseError for Error { 29 | fn status_code(&self) -> StatusCode { 30 | match self { 31 | Error::User(_) => StatusCode::BAD_REQUEST, 32 | _ => StatusCode::INTERNAL_SERVER_ERROR, 33 | } 34 | } 35 | 36 | fn error_response(&self) -> HttpResponse { 37 | HttpResponse::build(self.status_code()).json(crate::Response { 38 | success: self.status_code().is_success(), 39 | error_message: self.to_string(), 40 | }) 41 | } 42 | } 43 | 44 | impl From for Error { 45 | fn from(e: config::ConfigError) -> Self { 46 | Error::Initialization(e) 47 | } 48 | } 49 | 50 | impl From for Error { 51 | fn from(e: std::io::Error) -> Self { 52 | Error::Io(e) 53 | } 54 | } 55 | 56 | impl From for Error { 57 | fn from(e: actix_http::Error) -> Self { 58 | Error::Execution(format!("{:?}", e)) 59 | } 60 | } 61 | impl From for Error { 62 | fn from(e: serde_json::Error) -> Self { 63 | Error::Unexpected(format!("{:?}", e)) 64 | } 65 | } 66 | 67 | impl From for Error { 68 | fn from(e: base58::FromBase58Error) -> Self { 69 | Error::Unexpected(format!("{:?}", e)) 70 | } 71 | } 72 | 73 | impl std::convert::From for Error { 74 | fn from(e: reqwest::Error) -> Self { 75 | Error::Dependency(format!("{:?}", e)) 76 | } 77 | } 78 | 79 | impl From for Error { 80 | fn from(e: sqlx::Error) -> Self { 81 | Error::DataAccess(e.to_string()) 82 | } 83 | } 84 | 85 | impl From for Error { 86 | fn from(e: MigrateError) -> Self { 87 | Error::Migrate(e) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /server/src/github.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::error::Result; 3 | use actix_web::http::header; 4 | use reqwest::Client; 5 | use serde::Deserialize; 6 | use std::str::FromStr; 7 | use url::Url; 8 | 9 | lazy_static! { 10 | static ref AUTH_URL: Url = Url::parse("https://github.com/login/oauth/authorize").unwrap(); 11 | } 12 | 13 | pub struct GitHubClient { 14 | client_id: String, 15 | client_secret: String, 16 | callback_url: Url, 17 | } 18 | 19 | impl GitHubClient { 20 | pub fn new(client_id: String, client_secret: String, callback_url: impl Into) -> Self { 21 | GitHubClient { 22 | client_id, 23 | client_secret, 24 | callback_url: Url::from_str(&callback_url.into()).expect("Malformed callback url."), 25 | } 26 | } 27 | 28 | pub fn authorize_url(&self, state: &str) -> Url { 29 | let mut url = AUTH_URL.clone(); 30 | url.query_pairs_mut() 31 | .append_pair("client_id", &self.client_id) 32 | .append_pair("state", state) 33 | .append_pair("redirect_uri", self.callback_url.as_str()); 34 | 35 | url 36 | } 37 | 38 | pub async fn exchange_code(&self, http_client: &Client, code: &str) -> Result { 39 | let response = http_client 40 | .post("https://github.com/login/oauth/access_token") 41 | .query(&[ 42 | ("client_id", &self.client_id[..]), 43 | ("client_secret", &self.client_secret[..]), 44 | ("code", code), 45 | ]) 46 | .header(header::ACCEPT, "application/json") 47 | .send() 48 | .await?; 49 | let access_token: GitHubAccessToken = response.json().await?; 50 | Ok(access_token.access_token) 51 | } 52 | 53 | pub async fn get_user(&self, http_client: &Client, token: &str) -> Result { 54 | let response = http_client 55 | .get("https://api.github.com/user") 56 | .header(header::USER_AGENT, "PipeHub") 57 | .header(header::AUTHORIZATION, format!("token {}", token)) 58 | .send() 59 | .await 60 | .map_err(Error::from)?; 61 | let github_user = response.json::().await?; 62 | Ok(github_user) 63 | } 64 | } 65 | 66 | #[derive(Debug, Deserialize)] 67 | pub struct GithubUser { 68 | pub login: String, 69 | pub id: i64, 70 | } 71 | 72 | #[derive(Debug, Deserialize)] 73 | struct GitHubAccessToken { 74 | access_token: String, 75 | } 76 | 77 | // {"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"} 78 | -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | use crate::config::PipeHubConfig; 4 | use crate::data::Pool; 5 | use crate::error::Result; 6 | use crate::github::GitHubClient; 7 | use crate::request_id::{RequestId, RequestIdAware}; 8 | use crate::send::WeChatAccessToken; 9 | 10 | use crate::microsoft::MicrosoftClient; 11 | use actix_cors::Cors; 12 | use actix_files::Files; 13 | use actix_http::HttpMessage; 14 | use actix_session::CookieSession; 15 | use actix_web::middleware::Logger; 16 | use actix_web::{web, App, HttpServer}; 17 | use dashmap::DashMap; 18 | use dotenv::dotenv; 19 | use reqwest::{Client, ClientBuilder}; 20 | use serde::Serialize; 21 | use std::time::Duration; 22 | 23 | mod captcha; 24 | mod config; 25 | mod data; 26 | mod error; 27 | mod github; 28 | mod microsoft; 29 | mod models; 30 | mod request_id; 31 | mod send; 32 | mod user; 33 | mod util; 34 | mod wechat; 35 | 36 | pub type AccessTokenCache = DashMap; 37 | 38 | #[actix_web::main] 39 | async fn main() -> Result<()> { 40 | dotenv().ok(); 41 | env_logger::init(); 42 | 43 | let config = PipeHubConfig::new()?; 44 | 45 | let https = config.https; 46 | let session_key: [u8; 32] = rand::random(); 47 | 48 | let pool = Pool::new(&config.database_url).await?; 49 | pool.migrate().await?; 50 | 51 | let pool = web::Data::new(pool); 52 | let github_client = web::Data::new(github_client(&config)); 53 | let microsoft_client = web::Data::new(microsoft_client(&config)); 54 | let access_token_cache: web::Data = web::Data::new(DashMap::new()); 55 | let http_client = web::Data::new(http_client()); 56 | 57 | HttpServer::new(move || { 58 | let cors = Cors::default() 59 | .allow_any_origin() 60 | .allowed_methods(vec!["GET", "POST", "PUT"]) 61 | .allow_any_header() 62 | .supports_credentials() 63 | .expose_headers(vec!["Location"]); 64 | 65 | let logger = Logger::new(r#"%t %{request_id}xi "%r" %s %b %T"#) 66 | .custom_request_replace("request_id", |req| { 67 | req.extensions().get::().unwrap().to_string() 68 | }); 69 | 70 | App::new() 71 | .app_data(pool.clone()) 72 | .app_data(github_client.clone()) 73 | .app_data(microsoft_client.clone()) 74 | .app_data(access_token_cache.clone()) 75 | .app_data(http_client.clone()) 76 | .wrap(cors) 77 | .wrap(session(&session_key[..], https)) 78 | .wrap(logger) 79 | .wrap(RequestIdAware) 80 | .service(user::reset_key) 81 | .service(user::user) 82 | .service(user::update) 83 | .service(user::callback) 84 | .service(user::login) 85 | .service(user::msft_auth_url) 86 | .service(user::msft_callback) 87 | .service(wechat::wechat) 88 | .service(wechat::update) 89 | .service( 90 | web::resource("/send/{key}") 91 | .route(web::get().to(send::send)) 92 | .route(web::post().to(send::send)), 93 | ) 94 | .service(Files::new("/", "./static/").use_hidden_files()) 95 | }) 96 | .workers(num_cpus::get()) 97 | .bind(config.bind_addr())? 98 | .run() 99 | .await?; 100 | 101 | Ok(()) 102 | } 103 | 104 | #[derive(Debug, Serialize)] 105 | pub struct Response { 106 | success: bool, 107 | error_message: String, 108 | } 109 | 110 | fn session(key: &[u8], https: bool) -> CookieSession { 111 | CookieSession::private(key) 112 | .name("session") 113 | .secure(https) 114 | .http_only(true) 115 | } 116 | 117 | fn github_client(config: &PipeHubConfig) -> GitHubClient { 118 | GitHubClient::new( 119 | config.github.client_id.clone(), 120 | config.github.client_secret.clone(), 121 | &config.github.callback_url, 122 | ) 123 | } 124 | 125 | fn microsoft_client(config: &PipeHubConfig) -> MicrosoftClient { 126 | MicrosoftClient::new( 127 | config.microsoft.client_id.clone(), 128 | config.microsoft.client_secret.clone(), 129 | &config.microsoft.callback_url, 130 | ) 131 | } 132 | 133 | fn http_client() -> Client { 134 | ClientBuilder::new() 135 | .connect_timeout(Duration::from_secs(5)) 136 | .timeout(Duration::from_secs(10)) 137 | .pool_idle_timeout(Duration::from_secs(60)) 138 | .pool_max_idle_per_host(8) 139 | .build() 140 | .expect("Failed to create reqwest client.") 141 | } 142 | -------------------------------------------------------------------------------- /server/src/microsoft.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use actix_web::http::header; 3 | use reqwest::Client; 4 | use serde::Deserialize; 5 | use serde::Serialize; 6 | use std::str::FromStr; 7 | use url::Url; 8 | 9 | lazy_static! { 10 | static ref AUTH_URL: Url = 11 | Url::parse("https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize").unwrap(); 12 | } 13 | 14 | pub struct MicrosoftClient { 15 | client_id: String, 16 | client_secret: String, 17 | callback_url: Url, 18 | } 19 | 20 | impl MicrosoftClient { 21 | pub fn new(client_id: String, client_secret: String, callback_url: impl Into) -> Self { 22 | MicrosoftClient { 23 | client_id, 24 | client_secret, 25 | callback_url: Url::from_str(&callback_url.into()).expect("Malformed callback url."), 26 | } 27 | } 28 | 29 | pub fn authorize_url(&self, state: &str) -> Url { 30 | let mut url = AUTH_URL.clone(); 31 | url.query_pairs_mut() 32 | .append_pair("client_id", &self.client_id) 33 | .append_pair("state", state) 34 | .append_pair("redirect_uri", self.callback_url.as_str()) 35 | .append_pair("response_type", "code") 36 | .append_pair("response_mode", "query") 37 | .append_pair("scope", "Tasks.ReadWrite,offline_access"); 38 | 39 | url 40 | } 41 | 42 | pub async fn exchange_refresh_code(&self, http_client: &Client, code: &str) -> Result { 43 | let response = http_client 44 | .post("https://login.microsoftonline.com/consumers/oauth2/v2.0/token") 45 | .form(&[ 46 | ("client_id", &self.client_id[..]), 47 | ("client_secret", &self.client_secret[..]), 48 | ("code", code), 49 | ("scope", "Tasks.ReadWrite,offline_access"), 50 | ("grant_type", "authorization_code"), 51 | ("redirect_uri", self.callback_url.as_str()), 52 | ]) 53 | .header(header::ACCEPT, "application/json") 54 | .send() 55 | .await?; 56 | let text = response.text().await?; 57 | println!("{:?}", text); 58 | let refresh_token: MicrosoftToken = serde_json::from_str(&text)?; 59 | Ok(refresh_token.refresh_token) 60 | } 61 | 62 | pub async fn post_task( 63 | &self, 64 | http_client: &Client, 65 | refresh_token: &str, 66 | list_id: &str, 67 | title: &str, 68 | content: &str, 69 | ) -> Result<()> { 70 | let response = http_client 71 | .post("https://login.microsoftonline.com/consumers/oauth2/v2.0/token") 72 | .form(&[ 73 | ("client_id", &self.client_id[..]), 74 | ("client_secret", &self.client_secret[..]), 75 | ("refresh_token", refresh_token), 76 | ("scope", "Tasks.ReadWrite"), 77 | ("grant_type", "refresh_token"), 78 | ]) 79 | .header(header::ACCEPT, "application/json") 80 | .send() 81 | .await?; 82 | let access_token: MicrosoftToken = response.json().await?; 83 | 84 | let response = http_client 85 | .post(&format!( 86 | "https://graph.microsoft.com/v1.0/me/todo/lists/{}/tasks", 87 | list_id 88 | )) 89 | .header( 90 | "Authorization", 91 | format!("Bearer {}", access_token.access_token), 92 | ) 93 | .json(&MicrosoftTodoTask { 94 | title: title.to_string(), 95 | body: MicrosoftTodoTaskBody { 96 | content: content.to_string(), 97 | }, 98 | }) 99 | .send() 100 | .await?; 101 | 102 | let _: MicrosoftTodoTask = response.json().await?; 103 | Ok(()) 104 | } 105 | } 106 | 107 | #[derive(Debug, Deserialize, Serialize)] 108 | pub struct MicrosoftTodoTask { 109 | pub title: String, 110 | pub body: MicrosoftTodoTaskBody, 111 | } 112 | 113 | #[derive(Debug, Deserialize, Serialize)] 114 | pub struct MicrosoftTodoTaskBody { 115 | pub content: String, 116 | } 117 | 118 | #[derive(Debug, Deserialize)] 119 | struct MicrosoftToken { 120 | access_token: String, 121 | #[serde(default)] 122 | refresh_token: String, 123 | } 124 | -------------------------------------------------------------------------------- /server/src/models.rs: -------------------------------------------------------------------------------- 1 | use base58::ToBase58; 2 | use serde::{Deserialize, Serialize}; 3 | use sqlx::FromRow; 4 | use std::env; 5 | 6 | #[derive(Serialize, Deserialize, Clone, FromRow)] 7 | pub struct Tenant { 8 | #[serde(skip)] 9 | pub id: i64, 10 | #[serde(skip)] 11 | pub app_id: i64, 12 | pub github_login: String, 13 | pub github_id: i64, 14 | pub block_list: String, 15 | pub captcha: bool, 16 | pub msft_refresh_token: String, 17 | pub msft_task_list_id: String, 18 | } 19 | 20 | #[derive(Serialize)] 21 | pub struct UserTenant { 22 | #[serde(flatten)] 23 | tenant: Tenant, 24 | app_key: String, 25 | callback_url: String, 26 | } 27 | 28 | impl From for UserTenant { 29 | fn from(t: Tenant) -> Self { 30 | let app_key = t.app_id.to_le_bytes().to_base58(); 31 | let domain = env::var("pipehub_domain").unwrap(); 32 | let callback_url = format!("{}/send/{}", domain, app_key); 33 | UserTenant { 34 | tenant: t, 35 | app_key, 36 | callback_url, 37 | } 38 | } 39 | } 40 | 41 | impl Tenant { 42 | pub fn new(app_id: i64, github_login: String, github_id: i64) -> Self { 43 | Tenant { 44 | id: i64::default(), 45 | app_id, 46 | github_login, 47 | github_id, 48 | block_list: "".to_string(), 49 | captcha: false, 50 | msft_refresh_token: "".to_string(), 51 | msft_task_list_id: "".to_string(), 52 | } 53 | } 54 | } 55 | 56 | #[derive(Serialize, Deserialize, Default, FromRow, Debug)] 57 | pub struct WechatWork { 58 | #[serde(skip)] 59 | pub id: i64, 60 | #[serde(skip)] 61 | pub tenant_id: i64, 62 | pub corp_id: String, 63 | pub agent_id: i64, 64 | pub secret: String, 65 | #[serde(rename = "telegram_bot_token")] 66 | pub bot_token: String, 67 | #[serde(rename = "telegram_chat_id")] 68 | pub chat_id: String, 69 | } 70 | -------------------------------------------------------------------------------- /server/src/request_id.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use actix_http::{HttpMessage, Payload}; 3 | use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; 4 | use actix_web::http::header::{HeaderName, HeaderValue}; 5 | use actix_web::{Error as AWError, FromRequest, HttpRequest}; 6 | use futures_util::future::{ok, ready, Ready}; 7 | use std::fmt::{Display, Formatter}; 8 | use std::future::Future; 9 | use std::pin::Pin; 10 | use std::task::{Context, Poll}; 11 | use uuid::Uuid; 12 | 13 | #[derive(Clone, Copy, Debug)] 14 | pub struct RequestId(Uuid); 15 | 16 | impl Display for RequestId { 17 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 18 | write!(f, "{}", self.0.to_hyphenated()) 19 | } 20 | } 21 | 22 | impl RequestId { 23 | pub fn new() -> Self { 24 | RequestId(Uuid::new_v4()) 25 | } 26 | } 27 | 28 | impl FromRequest for RequestId { 29 | type Error = Error; 30 | type Future = Ready>; 31 | 32 | fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { 33 | ready( 34 | req.extensions() 35 | .get::() 36 | .map(RequestId::clone) 37 | .ok_or_else(|| Error::Unexpected("Missing request id.".to_string())), 38 | ) 39 | } 40 | } 41 | 42 | pub struct RequestIdAware; 43 | 44 | impl Transform for RequestIdAware 45 | where 46 | S: Service, Error = AWError>, 47 | S::Future: 'static, 48 | B: 'static, 49 | { 50 | type Response = S::Response; 51 | type Error = S::Error; 52 | type Transform = RequestIdMiddleware; 53 | type InitError = (); 54 | type Future = Ready>; 55 | 56 | fn new_transform(&self, service: S) -> Self::Future { 57 | // associate with the request 58 | ok(RequestIdMiddleware { 59 | service, 60 | id: RequestId::new(), 61 | }) 62 | } 63 | } 64 | 65 | pub struct RequestIdMiddleware { 66 | service: S, 67 | id: RequestId, 68 | } 69 | 70 | impl Service for RequestIdMiddleware 71 | where 72 | S: Service, Error = AWError>, 73 | S::Future: 'static, 74 | B: 'static, 75 | { 76 | type Response = S::Response; 77 | type Error = S::Error; 78 | type Future = Pin>>>; 79 | 80 | fn poll_ready(&self, ctx: &mut Context<'_>) -> Poll> { 81 | self.service.poll_ready(ctx) 82 | } 83 | 84 | fn call(&self, req: ServiceRequest) -> Self::Future { 85 | req.extensions_mut().insert(self.id); 86 | let fut = self.service.call(req); 87 | 88 | let request_id = self.id.to_string(); 89 | 90 | Box::pin(async move { 91 | let mut res = fut.await?; 92 | 93 | res.headers_mut().insert( 94 | HeaderName::from_static("x-request-id"), 95 | HeaderValue::from_str(&request_id).unwrap(), 96 | ); 97 | 98 | Ok(res) 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /server/src/send.rs: -------------------------------------------------------------------------------- 1 | use crate::data::Pool; 2 | use crate::error::{Error, Result}; 3 | use crate::models::WechatWork; 4 | use crate::{AccessTokenCache, MicrosoftClient, RequestId, Response}; 5 | 6 | use crate::captcha; 7 | use actix_web::{web, Error as AWError, HttpResponse}; 8 | use base58::FromBase58; 9 | use futures_util::future::{ok, BoxFuture}; 10 | use log::info; 11 | use reqwest::Client; 12 | use serde::{Deserialize, Serialize}; 13 | use std::convert::TryInto; 14 | use std::time::Instant; 15 | 16 | #[derive(Debug, Deserialize)] 17 | pub struct WeChatAccessToken { 18 | #[serde(rename = "errcode")] 19 | error_code: u64, 20 | #[serde(rename = "errmsg")] 21 | error_message: String, 22 | access_token: String, 23 | #[serde(rename = "expires_in", deserialize_with = "crate::util::expires_at")] 24 | expires_at: Instant, 25 | } 26 | 27 | #[derive(Debug, Serialize)] 28 | struct WeChatMessage { 29 | #[serde(rename = "touser")] 30 | to_user: Option, 31 | #[serde(rename = "toparty")] 32 | to_party: Option, 33 | #[serde(rename = "agentid")] 34 | agent_id: i64, 35 | #[serde(rename = "msgtype")] 36 | message_type: String, 37 | text: WeChatMessageText, 38 | #[serde(serialize_with = "crate::util::bool_to_int")] 39 | enable_duplicate_check: bool, 40 | duplicate_check_interval: u64, 41 | } 42 | 43 | #[derive(Debug, Serialize)] 44 | struct TelegramMessage { 45 | chat_id: String, 46 | text: String, 47 | } 48 | 49 | #[derive(Debug, Serialize)] 50 | struct WeChatMessageText { 51 | content: String, 52 | } 53 | 54 | #[derive(Debug, Deserialize)] 55 | struct WeChatSendResponse { 56 | #[serde(rename = "errcode")] 57 | error_code: u64, 58 | #[serde(rename = "errmsg")] 59 | error_message: String, 60 | } 61 | 62 | #[derive(Debug, Deserialize)] 63 | struct TelegramSendResponse { 64 | ok: bool, 65 | description: Option, 66 | } 67 | 68 | #[derive(Debug, Deserialize)] 69 | pub struct Message { 70 | text: Option, 71 | to_party: Option, 72 | } 73 | 74 | pub async fn send( 75 | pool: web::Data, 76 | key: web::Path, 77 | payload: web::Bytes, 78 | web::Query(message): web::Query, 79 | access_token_cache: web::Data, 80 | http_client: web::Data, 81 | request_id: RequestId, 82 | microsoft_client: web::Data, 83 | ) -> std::result::Result { 84 | let key = key.into_inner(); 85 | let app_key = key.clone().from_base58().map_err(Error::from)?; 86 | let app_id = i64::from_le_bytes((&app_key[0..8]).try_into().expect("Unexpected")); 87 | 88 | let tenant = pool 89 | .find_tenant_by_app_id(app_id) 90 | .await? 91 | .ok_or(Error::User("Unknown APP ID."))?; 92 | let wechat = pool 93 | .find_wechat_by_app_id(app_id) 94 | .await? 95 | .ok_or(Error::User( 96 | "No WeChat/Telegram credentials are configured.", 97 | ))?; 98 | 99 | let mut text = if let Message { 100 | text: Some(ref text), 101 | to_party: _, 102 | } = message 103 | { 104 | text.clone() 105 | } else if let Ok(text) = String::from_utf8(payload.to_vec()) { 106 | text 107 | } else { 108 | return Err(Error::User("No message is provided.").into()); 109 | }; 110 | 111 | if tenant 112 | .block_list 113 | .split(',') 114 | .map(|word| word.trim()) 115 | .filter(|word| !word.is_empty()) 116 | .any(|block_word| text.contains(block_word)) 117 | { 118 | return Err(Error::User("Message blocked.").into()); 119 | } 120 | 121 | let header = captcha::captcha(&text); 122 | let mut msft_future: BoxFuture> = Box::pin(ok(())); 123 | if tenant.captcha { 124 | text = format!("{}\n{}", header, text); 125 | if !tenant.msft_task_list_id.is_empty() && (text.contains("菜鸟") || text.contains("快递")) 126 | { 127 | info!("{} [Add] [Todo]", request_id); 128 | msft_future = Box::pin(microsoft_client.post_task( 129 | &http_client, 130 | &tenant.msft_refresh_token, 131 | &tenant.msft_task_list_id, 132 | &header, 133 | &text, 134 | )) 135 | } 136 | } 137 | 138 | let wechat_future: BoxFuture> = if !wechat.corp_id.is_empty() { 139 | info!("{} [Send] [WeChat] {}", request_id, key); 140 | Box::pin(send_wechat( 141 | app_id, 142 | access_token_cache, 143 | &http_client, 144 | &wechat, 145 | &text, 146 | &message, 147 | )) 148 | } else { 149 | Box::pin(ok(())) 150 | }; 151 | 152 | let telegram_future: BoxFuture> = if !wechat.bot_token.is_empty() { 153 | info!("{} [Send] [Telegram] {}", request_id, key); 154 | Box::pin(send_telegram(&http_client, &wechat, &text)) 155 | } else { 156 | Box::pin(ok(())) 157 | }; 158 | 159 | let wechat_result: Result<()> = wechat_future.await; 160 | let telegram_result: Result<()> = telegram_future.await; 161 | let msft_result: Result<()> = msft_future.await; 162 | 163 | Ok(HttpResponse::Ok().json(Response { 164 | success: (wechat_result.is_ok() || telegram_result.is_ok()) && msft_result.is_ok(), 165 | error_message: "".to_owned(), 166 | })) 167 | } 168 | 169 | async fn send_telegram( 170 | http_client: &web::Data, 171 | wechat: &WechatWork, 172 | text: &str, 173 | ) -> Result<()> { 174 | let url = format!( 175 | "https://api.telegram.org/bot{}/sendMessage", 176 | wechat.bot_token 177 | ); 178 | let response = http_client 179 | .post(&url) 180 | .json(&TelegramMessage { 181 | chat_id: wechat.chat_id.clone(), 182 | text: text.to_string(), 183 | }) 184 | .send() 185 | .await?; 186 | 187 | let response: TelegramSendResponse = response.json().await?; 188 | 189 | if !response.ok { 190 | return Err(Error::Dependency( 191 | response 192 | .description 193 | .unwrap_or_else(|| "Unknown Error".to_string()), 194 | )); 195 | } 196 | 197 | Ok(()) 198 | } 199 | 200 | async fn send_wechat( 201 | app_id: i64, 202 | access_token_cache: web::Data, 203 | http_client: &web::Data, 204 | wechat: &WechatWork, 205 | text: &str, 206 | message: &Message, 207 | ) -> Result<()> { 208 | let mut token = access_token_cache.get(&app_id); 209 | if token.is_none() || token.as_ref().unwrap().expires_at.le(&Instant::now()) { 210 | let new_token = get_token(http_client, wechat).await?; 211 | access_token_cache.insert(app_id, new_token); 212 | token = access_token_cache.get(&app_id); 213 | } 214 | 215 | let mut token = token.unwrap(); 216 | let mut retry_count = 0; 217 | while let Err(e) = do_send( 218 | http_client, 219 | wechat, 220 | token.value(), 221 | text.to_string(), 222 | message.to_party.clone(), 223 | ) 224 | .await 225 | { 226 | if retry_count > 3 { 227 | return Err(e); 228 | } else { 229 | retry_count += 1; 230 | } 231 | let new_token = get_token(http_client, wechat).await?; 232 | access_token_cache.insert(app_id, new_token); 233 | token = access_token_cache.get(&app_id).unwrap(); 234 | } 235 | Ok(()) 236 | } 237 | 238 | async fn get_token(client: &Client, wechat: &WechatWork) -> Result { 239 | let corpid = &wechat.corp_id; 240 | let secret = &wechat.secret; 241 | 242 | let _start = Instant::now(); 243 | let url = format!( 244 | "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={}&corpsecret={}", 245 | corpid, secret 246 | ); 247 | 248 | let response = client.get(&url).send().await?; 249 | let token: WeChatAccessToken = response.json().await?; 250 | 251 | if token.error_code != 0 { 252 | return Err(Error::Dependency(token.error_message)); 253 | } 254 | 255 | Ok(token) 256 | } 257 | 258 | async fn do_send( 259 | client: &Client, 260 | wechat: &WechatWork, 261 | token: &WeChatAccessToken, 262 | msg: String, 263 | to_party: Option, 264 | ) -> Result { 265 | let url = format!( 266 | "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={}", 267 | token.access_token 268 | ); 269 | let response = client 270 | .post(&url) 271 | .json(&WeChatMessage { 272 | to_user: match to_party { 273 | Some(_) => None, 274 | None => Some("@all".to_owned()), 275 | }, 276 | to_party, 277 | agent_id: wechat.agent_id, 278 | message_type: "text".to_string(), 279 | text: WeChatMessageText { content: msg }, 280 | enable_duplicate_check: false, 281 | duplicate_check_interval: 0, 282 | }) 283 | .send() 284 | .await?; 285 | 286 | let response: WeChatSendResponse = response.json().await?; 287 | 288 | if response.error_code != 0 { 289 | return Err(Error::Dependency(response.error_message)); 290 | } 291 | 292 | Ok(response) 293 | } 294 | -------------------------------------------------------------------------------- /server/src/user.rs: -------------------------------------------------------------------------------- 1 | use crate::data::Pool; 2 | use crate::github::GitHubClient; 3 | use crate::models::{Tenant, UserTenant}; 4 | 5 | use crate::{MicrosoftClient, RequestId}; 6 | use actix_session::Session; 7 | use actix_web::error::Error as AWError; 8 | use actix_web::{get, post, put, web, HttpResponse}; 9 | use base58::ToBase58; 10 | use log::info; 11 | use rand::{thread_rng, Rng}; 12 | use reqwest::Client; 13 | use serde::Deserialize; 14 | use std::env; 15 | 16 | pub const TENANT_ID_KEY: &str = "tenant_id"; 17 | pub const STATE_KEY: &str = "state"; 18 | 19 | #[get("/user")] 20 | pub async fn user( 21 | session: Session, 22 | client: web::Data, 23 | pool: web::Data, 24 | request_id: RequestId, 25 | ) -> std::result::Result { 26 | info!("{} [Get User]", request_id); 27 | if let Some(tenant_id) = session.get::(TENANT_ID_KEY)? { 28 | if let Some(tenant) = pool.find_tenant_by_id(tenant_id).await? { 29 | info!("{} [Get User] {}", request_id, tenant.id); 30 | return Ok(HttpResponse::Ok().json(UserTenant::from(tenant))); 31 | }; 32 | } 33 | 34 | let state = new_csrf_token(); 35 | let url = client.authorize_url(&state); 36 | session.insert(STATE_KEY, state)?; 37 | Ok(HttpResponse::Unauthorized() 38 | .append_header(("Location", url.to_string())) 39 | .body(())) 40 | } 41 | 42 | fn new_csrf_token() -> String { 43 | let random_bytes: [u8; 16] = thread_rng().gen::<[u8; 16]>(); 44 | random_bytes.to_base58() 45 | } 46 | 47 | #[derive(Deserialize)] 48 | pub struct Callback { 49 | code: String, 50 | state: String, 51 | } 52 | 53 | #[derive(Deserialize)] 54 | pub struct LoginCallback { 55 | access_token: String, 56 | } 57 | 58 | // It's for testing purpose. 59 | #[post("/login")] 60 | pub async fn login( 61 | session: Session, 62 | http_client: web::Data, 63 | github_client: web::Data, 64 | pool: web::Data, 65 | web::Query(login): web::Query, 66 | ) -> std::result::Result { 67 | let access_token = login.access_token; 68 | let github_user = github_client.get_user(&http_client, &access_token).await?; 69 | match pool.find_tenant_by_github_id(github_user.id).await? { 70 | Some(tenant) => session.insert(TENANT_ID_KEY, tenant.id)?, 71 | None => { 72 | let app_id: i64 = thread_rng().gen(); 73 | let tenant = Tenant::new(app_id, github_user.login, github_user.id); 74 | let tenant = pool.insert_tenant(tenant).await?; 75 | session.insert(TENANT_ID_KEY, tenant.id)? 76 | } 77 | } 78 | Ok(HttpResponse::Found() 79 | .append_header(("Location", "/#/user")) 80 | .body(())) 81 | } 82 | 83 | #[get("/msft_auth_url")] 84 | pub async fn msft_auth_url( 85 | session: Session, 86 | client: web::Data, 87 | ) -> std::result::Result { 88 | let state = new_csrf_token(); 89 | let url = client.authorize_url(&state); 90 | session.insert(STATE_KEY, state)?; 91 | Ok(HttpResponse::Ok() 92 | .append_header(("Location", url.to_string())) 93 | .body(())) 94 | } 95 | 96 | #[get("/msft_callback")] 97 | pub async fn msft_callback( 98 | session: Session, 99 | microsoft_client: web::Data, 100 | http_client: web::Data, 101 | web::Query(msft_callback): web::Query, 102 | pool: web::Data, 103 | ) -> std::result::Result { 104 | if let Some(state) = session.get::(STATE_KEY)? { 105 | if state == msft_callback.state { 106 | if let Some(tenant_id) = session.get::(TENANT_ID_KEY)? { 107 | if let Some(mut tenant) = pool.find_tenant_by_id(tenant_id).await? { 108 | let refresh_token = microsoft_client 109 | .exchange_refresh_code(&http_client, &msft_callback.code) 110 | .await?; 111 | 112 | tenant.msft_refresh_token = refresh_token; 113 | pool.update_tenant(tenant).await?; 114 | } 115 | } 116 | } 117 | } 118 | Ok(HttpResponse::Found() 119 | .append_header(( 120 | "Location", 121 | format!("{}/#/user", env::var("pipehub_domain_web").unwrap()), 122 | )) 123 | .body(())) 124 | } 125 | 126 | #[get("/callback")] 127 | pub async fn callback( 128 | session: Session, 129 | github_client: web::Data, 130 | http_client: web::Data, 131 | pool: web::Data, 132 | web::Query(callback): web::Query, 133 | request_id: RequestId, 134 | ) -> std::result::Result { 135 | match session.get::(STATE_KEY)? { 136 | Some(state) if state == callback.state => { 137 | let access_token = github_client 138 | .exchange_code(&http_client, &callback.code) 139 | .await?; 140 | let github_user = github_client.get_user(&http_client, &access_token).await?; 141 | 142 | match pool.find_tenant_by_github_id(github_user.id).await? { 143 | Some(tenant) => { 144 | info!("{} [Login] {}", request_id, github_user.login); 145 | session.insert(TENANT_ID_KEY, tenant.id)? 146 | } 147 | None => { 148 | let app_id: i64 = thread_rng().gen(); 149 | info!("{} [Register] {}", request_id, github_user.login); 150 | let tenant = Tenant::new(app_id, github_user.login, github_user.id); 151 | let tenant = pool.insert_tenant(tenant).await?; 152 | session.insert(TENANT_ID_KEY, tenant.id)? 153 | } 154 | } 155 | Ok(HttpResponse::Found() 156 | .append_header(( 157 | "Location", 158 | format!("{}/#/user", env::var("pipehub_domain_web").unwrap()), 159 | )) 160 | .body(())) 161 | } 162 | _ => Ok(HttpResponse::Found() 163 | .append_header(( 164 | "Location", 165 | format!("{}/", env::var("pipehub_domain_web").unwrap()), 166 | )) 167 | .body(())), 168 | } 169 | } 170 | 171 | #[post("/user/reset_key")] 172 | pub async fn reset_key( 173 | session: Session, 174 | pool: web::Data, 175 | ) -> std::result::Result { 176 | if let Some(tenant_id) = session.get::(TENANT_ID_KEY)? { 177 | if let Some(tenant) = pool.find_tenant_by_id(tenant_id).await? { 178 | let new_tenant = Tenant { 179 | id: tenant.id, 180 | app_id: thread_rng().gen(), 181 | github_login: tenant.github_login, 182 | github_id: tenant.github_id, 183 | block_list: tenant.block_list, 184 | captcha: tenant.captcha, 185 | msft_refresh_token: tenant.msft_refresh_token, 186 | msft_task_list_id: tenant.msft_task_list_id, 187 | }; 188 | pool.update_tenant(new_tenant.clone()).await?; 189 | 190 | return Ok(HttpResponse::Ok().json(UserTenant::from(new_tenant))); 191 | }; 192 | } 193 | 194 | Ok(HttpResponse::Unauthorized().body(())) 195 | } 196 | 197 | #[put("/user")] 198 | pub async fn update( 199 | session: Session, 200 | pool: web::Data, 201 | web::Json(new_tenant): web::Json, 202 | request_id: RequestId, 203 | ) -> std::result::Result { 204 | info!("{} [Update User]", request_id); 205 | if let Some(tenant_id) = session.get::(TENANT_ID_KEY)? { 206 | if let Some(tenant) = pool.find_tenant_by_id(tenant_id).await? { 207 | info!("{} [Update User] {}", request_id, tenant.id); 208 | let new_tenant = Tenant { 209 | id: tenant.id, 210 | app_id: tenant.app_id, 211 | github_login: tenant.github_login, 212 | github_id: tenant.github_id, 213 | block_list: new_tenant.block_list, 214 | captcha: new_tenant.captcha, 215 | msft_refresh_token: tenant.msft_refresh_token, 216 | msft_task_list_id: new_tenant.msft_task_list_id, 217 | }; 218 | pool.update_tenant(new_tenant.clone()).await?; 219 | 220 | return Ok(HttpResponse::Ok().json(UserTenant::from(new_tenant))); 221 | }; 222 | } 223 | 224 | Ok(HttpResponse::Unauthorized().body(())) 225 | } 226 | -------------------------------------------------------------------------------- /server/src/util.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Deserializer, Serializer}; 2 | use std::ops::Add; 3 | use std::time::{Duration, Instant}; 4 | 5 | // Serde crate enforces following signature. 6 | #[allow(clippy::trivially_copy_pass_by_ref)] 7 | pub fn bool_to_int(input: &bool, serializer: S) -> Result 8 | where 9 | S: Serializer, 10 | { 11 | serializer.serialize_u64(if *input { 1 } else { 0 }) 12 | } 13 | 14 | pub fn expires_at<'de, D>(deserializer: D) -> Result 15 | where 16 | D: Deserializer<'de>, 17 | { 18 | Deserialize::deserialize(deserializer) 19 | .map(|expires_in| Instant::now().add(Duration::from_secs(expires_in))) 20 | } 21 | -------------------------------------------------------------------------------- /server/src/wechat.rs: -------------------------------------------------------------------------------- 1 | use crate::data::Pool; 2 | use crate::models::WechatWork; 3 | use crate::user::TENANT_ID_KEY; 4 | use crate::RequestId; 5 | use actix_session::Session; 6 | use actix_web::{get, put, web, Error as AWError, HttpResponse}; 7 | use log::info; 8 | 9 | #[get("/wechat")] 10 | pub async fn wechat( 11 | session: Session, 12 | pool: web::Data, 13 | request_id: RequestId, 14 | ) -> std::result::Result { 15 | info!("{} [Get WeChat]", request_id); 16 | if let Some(tenant_id) = session.get::(TENANT_ID_KEY)? { 17 | if let Some(wechat) = pool.find_wechat_by_id(tenant_id).await? { 18 | info!("{} [Get WeChat] {}", request_id, wechat.tenant_id); 19 | Ok(HttpResponse::Ok().json(wechat)) 20 | } else { 21 | Ok(HttpResponse::Ok().json(WechatWork::default())) 22 | } 23 | } else { 24 | Ok(HttpResponse::Unauthorized().body(())) 25 | } 26 | } 27 | 28 | #[put("/wechat")] 29 | pub async fn update( 30 | session: Session, 31 | pool: web::Data, 32 | web::Json(mut entity): web::Json, 33 | request_id: RequestId, 34 | ) -> std::result::Result { 35 | info!("{} [Update WeChat]", request_id); 36 | if let Some(tenant_id) = session.get::(TENANT_ID_KEY)? { 37 | info!("{} [Update WeChat] {}", request_id, tenant_id); 38 | entity.tenant_id = tenant_id; 39 | entity.corp_id = entity.corp_id.trim().to_string(); 40 | entity.secret = entity.secret.trim().to_string(); 41 | pool.upsert_wechat(entity).await?; 42 | Ok(HttpResponse::NoContent().body(())) 43 | } else { 44 | Ok(HttpResponse::Unauthorized().body(())) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/static/.well-known/microsoft-identity-association.json: -------------------------------------------------------------------------------- 1 | { 2 | "associatedApplications": [ 3 | { 4 | "applicationId": "37f133d3-dd30-41f7-9420-d656823a38a3" 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea 3 | *.iml 4 | .env -------------------------------------------------------------------------------- /tests/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "arc-swap" 5 | version = "0.4.7" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" 8 | 9 | [[package]] 10 | name = "autocfg" 11 | version = "1.0.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 14 | 15 | [[package]] 16 | name = "base64" 17 | version = "0.12.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" 20 | 21 | [[package]] 22 | name = "bitflags" 23 | version = "1.2.1" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 26 | 27 | [[package]] 28 | name = "bumpalo" 29 | version = "3.4.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 32 | 33 | [[package]] 34 | name = "bytes" 35 | version = "0.5.4" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" 38 | 39 | [[package]] 40 | name = "cc" 41 | version = "1.0.54" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" 44 | 45 | [[package]] 46 | name = "cfg-if" 47 | version = "0.1.10" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 50 | 51 | [[package]] 52 | name = "chrono" 53 | version = "0.4.11" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" 56 | dependencies = [ 57 | "num-integer", 58 | "num-traits", 59 | "time", 60 | ] 61 | 62 | [[package]] 63 | name = "cookie" 64 | version = "0.12.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" 67 | dependencies = [ 68 | "time", 69 | "url 1.7.2", 70 | ] 71 | 72 | [[package]] 73 | name = "cookie_store" 74 | version = "0.11.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "2b2a7e5bf5517bf3c3f4358f13d3ec2092419ac2332aa8593605c957da73d491" 77 | dependencies = [ 78 | "cookie", 79 | "idna 0.2.0", 80 | "log", 81 | "publicsuffix", 82 | "serde", 83 | "serde_json", 84 | "time", 85 | "url 2.1.1", 86 | ] 87 | 88 | [[package]] 89 | name = "core-foundation" 90 | version = "0.7.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 93 | dependencies = [ 94 | "core-foundation-sys", 95 | "libc", 96 | ] 97 | 98 | [[package]] 99 | name = "core-foundation-sys" 100 | version = "0.7.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 103 | 104 | [[package]] 105 | name = "dotenv" 106 | version = "0.15.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 109 | 110 | [[package]] 111 | name = "dtoa" 112 | version = "0.4.5" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" 115 | 116 | [[package]] 117 | name = "encoding_rs" 118 | version = "0.8.23" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" 121 | dependencies = [ 122 | "cfg-if", 123 | ] 124 | 125 | [[package]] 126 | name = "error-chain" 127 | version = "0.12.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" 130 | dependencies = [ 131 | "version_check", 132 | ] 133 | 134 | [[package]] 135 | name = "fnv" 136 | version = "1.0.7" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 139 | 140 | [[package]] 141 | name = "foreign-types" 142 | version = "0.3.2" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 145 | dependencies = [ 146 | "foreign-types-shared", 147 | ] 148 | 149 | [[package]] 150 | name = "foreign-types-shared" 151 | version = "0.1.1" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 154 | 155 | [[package]] 156 | name = "fuchsia-zircon" 157 | version = "0.3.3" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 160 | dependencies = [ 161 | "bitflags", 162 | "fuchsia-zircon-sys", 163 | ] 164 | 165 | [[package]] 166 | name = "fuchsia-zircon-sys" 167 | version = "0.3.3" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 170 | 171 | [[package]] 172 | name = "futures-channel" 173 | version = "0.3.5" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" 176 | dependencies = [ 177 | "futures-core", 178 | ] 179 | 180 | [[package]] 181 | name = "futures-core" 182 | version = "0.3.5" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" 185 | 186 | [[package]] 187 | name = "futures-sink" 188 | version = "0.3.5" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" 191 | 192 | [[package]] 193 | name = "futures-task" 194 | version = "0.3.5" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" 197 | 198 | [[package]] 199 | name = "futures-util" 200 | version = "0.3.5" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" 203 | dependencies = [ 204 | "futures-core", 205 | "futures-task", 206 | "pin-project", 207 | "pin-utils", 208 | ] 209 | 210 | [[package]] 211 | name = "getrandom" 212 | version = "0.1.14" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 215 | dependencies = [ 216 | "cfg-if", 217 | "libc", 218 | "wasi", 219 | ] 220 | 221 | [[package]] 222 | name = "h2" 223 | version = "0.2.5" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" 226 | dependencies = [ 227 | "bytes", 228 | "fnv", 229 | "futures-core", 230 | "futures-sink", 231 | "futures-util", 232 | "http", 233 | "indexmap", 234 | "log", 235 | "slab", 236 | "tokio", 237 | "tokio-util", 238 | ] 239 | 240 | [[package]] 241 | name = "hermit-abi" 242 | version = "0.1.13" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" 245 | dependencies = [ 246 | "libc", 247 | ] 248 | 249 | [[package]] 250 | name = "http" 251 | version = "0.2.1" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" 254 | dependencies = [ 255 | "bytes", 256 | "fnv", 257 | "itoa", 258 | ] 259 | 260 | [[package]] 261 | name = "http-body" 262 | version = "0.3.1" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" 265 | dependencies = [ 266 | "bytes", 267 | "http", 268 | ] 269 | 270 | [[package]] 271 | name = "httparse" 272 | version = "1.3.4" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 275 | 276 | [[package]] 277 | name = "hyper" 278 | version = "0.13.6" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" 281 | dependencies = [ 282 | "bytes", 283 | "futures-channel", 284 | "futures-core", 285 | "futures-util", 286 | "h2", 287 | "http", 288 | "http-body", 289 | "httparse", 290 | "itoa", 291 | "log", 292 | "pin-project", 293 | "socket2", 294 | "time", 295 | "tokio", 296 | "tower-service", 297 | "want", 298 | ] 299 | 300 | [[package]] 301 | name = "hyper-tls" 302 | version = "0.4.1" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" 305 | dependencies = [ 306 | "bytes", 307 | "hyper", 308 | "native-tls", 309 | "tokio", 310 | "tokio-tls", 311 | ] 312 | 313 | [[package]] 314 | name = "idna" 315 | version = "0.1.5" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" 318 | dependencies = [ 319 | "matches", 320 | "unicode-bidi", 321 | "unicode-normalization", 322 | ] 323 | 324 | [[package]] 325 | name = "idna" 326 | version = "0.2.0" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 329 | dependencies = [ 330 | "matches", 331 | "unicode-bidi", 332 | "unicode-normalization", 333 | ] 334 | 335 | [[package]] 336 | name = "indexmap" 337 | version = "1.4.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" 340 | dependencies = [ 341 | "autocfg", 342 | ] 343 | 344 | [[package]] 345 | name = "iovec" 346 | version = "0.1.4" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 349 | dependencies = [ 350 | "libc", 351 | ] 352 | 353 | [[package]] 354 | name = "itoa" 355 | version = "0.4.5" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 358 | 359 | [[package]] 360 | name = "js-sys" 361 | version = "0.3.40" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" 364 | dependencies = [ 365 | "wasm-bindgen", 366 | ] 367 | 368 | [[package]] 369 | name = "kernel32-sys" 370 | version = "0.2.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 373 | dependencies = [ 374 | "winapi 0.2.8", 375 | "winapi-build", 376 | ] 377 | 378 | [[package]] 379 | name = "lazy_static" 380 | version = "1.4.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 383 | 384 | [[package]] 385 | name = "libc" 386 | version = "0.2.71" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 389 | 390 | [[package]] 391 | name = "log" 392 | version = "0.4.8" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 395 | dependencies = [ 396 | "cfg-if", 397 | ] 398 | 399 | [[package]] 400 | name = "matches" 401 | version = "0.1.8" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 404 | 405 | [[package]] 406 | name = "memchr" 407 | version = "2.3.3" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 410 | 411 | [[package]] 412 | name = "mime" 413 | version = "0.3.16" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 416 | 417 | [[package]] 418 | name = "mime_guess" 419 | version = "2.0.3" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" 422 | dependencies = [ 423 | "mime", 424 | "unicase", 425 | ] 426 | 427 | [[package]] 428 | name = "mio" 429 | version = "0.6.22" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 432 | dependencies = [ 433 | "cfg-if", 434 | "fuchsia-zircon", 435 | "fuchsia-zircon-sys", 436 | "iovec", 437 | "kernel32-sys", 438 | "libc", 439 | "log", 440 | "miow 0.2.1", 441 | "net2", 442 | "slab", 443 | "winapi 0.2.8", 444 | ] 445 | 446 | [[package]] 447 | name = "mio-named-pipes" 448 | version = "0.1.6" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" 451 | dependencies = [ 452 | "log", 453 | "mio", 454 | "miow 0.3.5", 455 | "winapi 0.3.8", 456 | ] 457 | 458 | [[package]] 459 | name = "mio-uds" 460 | version = "0.6.8" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 463 | dependencies = [ 464 | "iovec", 465 | "libc", 466 | "mio", 467 | ] 468 | 469 | [[package]] 470 | name = "miow" 471 | version = "0.2.1" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 474 | dependencies = [ 475 | "kernel32-sys", 476 | "net2", 477 | "winapi 0.2.8", 478 | "ws2_32-sys", 479 | ] 480 | 481 | [[package]] 482 | name = "miow" 483 | version = "0.3.5" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" 486 | dependencies = [ 487 | "socket2", 488 | "winapi 0.3.8", 489 | ] 490 | 491 | [[package]] 492 | name = "native-tls" 493 | version = "0.2.4" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d" 496 | dependencies = [ 497 | "lazy_static", 498 | "libc", 499 | "log", 500 | "openssl", 501 | "openssl-probe", 502 | "openssl-sys", 503 | "schannel", 504 | "security-framework", 505 | "security-framework-sys", 506 | "tempfile", 507 | ] 508 | 509 | [[package]] 510 | name = "net2" 511 | version = "0.2.34" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 514 | dependencies = [ 515 | "cfg-if", 516 | "libc", 517 | "winapi 0.3.8", 518 | ] 519 | 520 | [[package]] 521 | name = "num-integer" 522 | version = "0.1.42" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 525 | dependencies = [ 526 | "autocfg", 527 | "num-traits", 528 | ] 529 | 530 | [[package]] 531 | name = "num-traits" 532 | version = "0.2.11" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 535 | dependencies = [ 536 | "autocfg", 537 | ] 538 | 539 | [[package]] 540 | name = "num_cpus" 541 | version = "1.13.0" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 544 | dependencies = [ 545 | "hermit-abi", 546 | "libc", 547 | ] 548 | 549 | [[package]] 550 | name = "openssl" 551 | version = "0.10.29" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd" 554 | dependencies = [ 555 | "bitflags", 556 | "cfg-if", 557 | "foreign-types", 558 | "lazy_static", 559 | "libc", 560 | "openssl-sys", 561 | ] 562 | 563 | [[package]] 564 | name = "openssl-probe" 565 | version = "0.1.2" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 568 | 569 | [[package]] 570 | name = "openssl-sys" 571 | version = "0.9.58" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" 574 | dependencies = [ 575 | "autocfg", 576 | "cc", 577 | "libc", 578 | "pkg-config", 579 | "vcpkg", 580 | ] 581 | 582 | [[package]] 583 | name = "percent-encoding" 584 | version = "1.0.1" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" 587 | 588 | [[package]] 589 | name = "percent-encoding" 590 | version = "2.1.0" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 593 | 594 | [[package]] 595 | name = "pin-project" 596 | version = "0.4.20" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "e75373ff9037d112bb19bc61333a06a159eaeb217660dcfbea7d88e1db823919" 599 | dependencies = [ 600 | "pin-project-internal", 601 | ] 602 | 603 | [[package]] 604 | name = "pin-project-internal" 605 | version = "0.4.20" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "10b4b44893d3c370407a1d6a5cfde7c41ae0478e31c516c85f67eb3adc51be6d" 608 | dependencies = [ 609 | "proc-macro2", 610 | "quote", 611 | "syn", 612 | ] 613 | 614 | [[package]] 615 | name = "pin-project-lite" 616 | version = "0.1.7" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" 619 | 620 | [[package]] 621 | name = "pin-utils" 622 | version = "0.1.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 625 | 626 | [[package]] 627 | name = "pkg-config" 628 | version = "0.3.17" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 631 | 632 | [[package]] 633 | name = "ppv-lite86" 634 | version = "0.2.8" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 637 | 638 | [[package]] 639 | name = "proc-macro2" 640 | version = "1.0.18" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 643 | dependencies = [ 644 | "unicode-xid", 645 | ] 646 | 647 | [[package]] 648 | name = "publicsuffix" 649 | version = "1.5.4" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b" 652 | dependencies = [ 653 | "error-chain", 654 | "idna 0.2.0", 655 | "lazy_static", 656 | "regex", 657 | "url 2.1.1", 658 | ] 659 | 660 | [[package]] 661 | name = "quote" 662 | version = "1.0.7" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 665 | dependencies = [ 666 | "proc-macro2", 667 | ] 668 | 669 | [[package]] 670 | name = "rand" 671 | version = "0.7.3" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 674 | dependencies = [ 675 | "getrandom", 676 | "libc", 677 | "rand_chacha", 678 | "rand_core", 679 | "rand_hc", 680 | ] 681 | 682 | [[package]] 683 | name = "rand_chacha" 684 | version = "0.2.2" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 687 | dependencies = [ 688 | "ppv-lite86", 689 | "rand_core", 690 | ] 691 | 692 | [[package]] 693 | name = "rand_core" 694 | version = "0.5.1" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 697 | dependencies = [ 698 | "getrandom", 699 | ] 700 | 701 | [[package]] 702 | name = "rand_hc" 703 | version = "0.2.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 706 | dependencies = [ 707 | "rand_core", 708 | ] 709 | 710 | [[package]] 711 | name = "redox_syscall" 712 | version = "0.1.56" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 715 | 716 | [[package]] 717 | name = "regex" 718 | version = "1.3.9" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 721 | dependencies = [ 722 | "regex-syntax", 723 | ] 724 | 725 | [[package]] 726 | name = "regex-syntax" 727 | version = "0.6.18" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 730 | 731 | [[package]] 732 | name = "remove_dir_all" 733 | version = "0.5.2" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 736 | dependencies = [ 737 | "winapi 0.3.8", 738 | ] 739 | 740 | [[package]] 741 | name = "reqwest" 742 | version = "0.10.6" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" 745 | dependencies = [ 746 | "base64", 747 | "bytes", 748 | "cookie", 749 | "cookie_store", 750 | "encoding_rs", 751 | "futures-core", 752 | "futures-util", 753 | "http", 754 | "http-body", 755 | "hyper", 756 | "hyper-tls", 757 | "js-sys", 758 | "lazy_static", 759 | "log", 760 | "mime", 761 | "mime_guess", 762 | "native-tls", 763 | "percent-encoding 2.1.0", 764 | "pin-project-lite", 765 | "serde", 766 | "serde_json", 767 | "serde_urlencoded", 768 | "time", 769 | "tokio", 770 | "tokio-tls", 771 | "url 2.1.1", 772 | "wasm-bindgen", 773 | "wasm-bindgen-futures", 774 | "web-sys", 775 | "winreg", 776 | ] 777 | 778 | [[package]] 779 | name = "ryu" 780 | version = "1.0.5" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 783 | 784 | [[package]] 785 | name = "schannel" 786 | version = "0.1.19" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 789 | dependencies = [ 790 | "lazy_static", 791 | "winapi 0.3.8", 792 | ] 793 | 794 | [[package]] 795 | name = "security-framework" 796 | version = "0.4.4" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535" 799 | dependencies = [ 800 | "bitflags", 801 | "core-foundation", 802 | "core-foundation-sys", 803 | "libc", 804 | "security-framework-sys", 805 | ] 806 | 807 | [[package]] 808 | name = "security-framework-sys" 809 | version = "0.4.3" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405" 812 | dependencies = [ 813 | "core-foundation-sys", 814 | "libc", 815 | ] 816 | 817 | [[package]] 818 | name = "serde" 819 | version = "1.0.111" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" 822 | dependencies = [ 823 | "serde_derive", 824 | ] 825 | 826 | [[package]] 827 | name = "serde_derive" 828 | version = "1.0.111" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" 831 | dependencies = [ 832 | "proc-macro2", 833 | "quote", 834 | "syn", 835 | ] 836 | 837 | [[package]] 838 | name = "serde_json" 839 | version = "1.0.55" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" 842 | dependencies = [ 843 | "itoa", 844 | "ryu", 845 | "serde", 846 | ] 847 | 848 | [[package]] 849 | name = "serde_urlencoded" 850 | version = "0.6.1" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" 853 | dependencies = [ 854 | "dtoa", 855 | "itoa", 856 | "serde", 857 | "url 2.1.1", 858 | ] 859 | 860 | [[package]] 861 | name = "signal-hook-registry" 862 | version = "1.2.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" 865 | dependencies = [ 866 | "arc-swap", 867 | "libc", 868 | ] 869 | 870 | [[package]] 871 | name = "simplelog" 872 | version = "0.8.0" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "2b2736f58087298a448859961d3f4a0850b832e72619d75adc69da7993c2cd3c" 875 | dependencies = [ 876 | "chrono", 877 | "log", 878 | "termcolor", 879 | ] 880 | 881 | [[package]] 882 | name = "slab" 883 | version = "0.4.2" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 886 | 887 | [[package]] 888 | name = "smallvec" 889 | version = "1.4.0" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" 892 | 893 | [[package]] 894 | name = "socket2" 895 | version = "0.3.12" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" 898 | dependencies = [ 899 | "cfg-if", 900 | "libc", 901 | "redox_syscall", 902 | "winapi 0.3.8", 903 | ] 904 | 905 | [[package]] 906 | name = "syn" 907 | version = "1.0.31" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" 910 | dependencies = [ 911 | "proc-macro2", 912 | "quote", 913 | "unicode-xid", 914 | ] 915 | 916 | [[package]] 917 | name = "tempfile" 918 | version = "3.1.0" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 921 | dependencies = [ 922 | "cfg-if", 923 | "libc", 924 | "rand", 925 | "redox_syscall", 926 | "remove_dir_all", 927 | "winapi 0.3.8", 928 | ] 929 | 930 | [[package]] 931 | name = "termcolor" 932 | version = "1.1.0" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 935 | dependencies = [ 936 | "winapi-util", 937 | ] 938 | 939 | [[package]] 940 | name = "tests" 941 | version = "0.2.8" 942 | dependencies = [ 943 | "dotenv", 944 | "reqwest", 945 | "serde", 946 | "simplelog", 947 | "tokio", 948 | ] 949 | 950 | [[package]] 951 | name = "time" 952 | version = "0.1.43" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 955 | dependencies = [ 956 | "libc", 957 | "winapi 0.3.8", 958 | ] 959 | 960 | [[package]] 961 | name = "tokio" 962 | version = "0.2.21" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" 965 | dependencies = [ 966 | "bytes", 967 | "fnv", 968 | "futures-core", 969 | "iovec", 970 | "lazy_static", 971 | "libc", 972 | "memchr", 973 | "mio", 974 | "mio-named-pipes", 975 | "mio-uds", 976 | "num_cpus", 977 | "pin-project-lite", 978 | "signal-hook-registry", 979 | "slab", 980 | "tokio-macros", 981 | "winapi 0.3.8", 982 | ] 983 | 984 | [[package]] 985 | name = "tokio-macros" 986 | version = "0.2.5" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" 989 | dependencies = [ 990 | "proc-macro2", 991 | "quote", 992 | "syn", 993 | ] 994 | 995 | [[package]] 996 | name = "tokio-tls" 997 | version = "0.3.1" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" 1000 | dependencies = [ 1001 | "native-tls", 1002 | "tokio", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "tokio-util" 1007 | version = "0.3.1" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" 1010 | dependencies = [ 1011 | "bytes", 1012 | "futures-core", 1013 | "futures-sink", 1014 | "log", 1015 | "pin-project-lite", 1016 | "tokio", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "tower-service" 1021 | version = "0.3.0" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" 1024 | 1025 | [[package]] 1026 | name = "try-lock" 1027 | version = "0.2.2" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 1030 | 1031 | [[package]] 1032 | name = "unicase" 1033 | version = "2.6.0" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1036 | dependencies = [ 1037 | "version_check", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "unicode-bidi" 1042 | version = "0.3.4" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1045 | dependencies = [ 1046 | "matches", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "unicode-normalization" 1051 | version = "0.1.12" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" 1054 | dependencies = [ 1055 | "smallvec", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "unicode-xid" 1060 | version = "0.2.0" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 1063 | 1064 | [[package]] 1065 | name = "url" 1066 | version = "1.7.2" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" 1069 | dependencies = [ 1070 | "idna 0.1.5", 1071 | "matches", 1072 | "percent-encoding 1.0.1", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "url" 1077 | version = "2.1.1" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 1080 | dependencies = [ 1081 | "idna 0.2.0", 1082 | "matches", 1083 | "percent-encoding 2.1.0", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "vcpkg" 1088 | version = "0.2.10" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" 1091 | 1092 | [[package]] 1093 | name = "version_check" 1094 | version = "0.9.2" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1097 | 1098 | [[package]] 1099 | name = "want" 1100 | version = "0.3.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1103 | dependencies = [ 1104 | "log", 1105 | "try-lock", 1106 | ] 1107 | 1108 | [[package]] 1109 | name = "wasi" 1110 | version = "0.9.0+wasi-snapshot-preview1" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1113 | 1114 | [[package]] 1115 | name = "wasm-bindgen" 1116 | version = "0.2.63" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" 1119 | dependencies = [ 1120 | "cfg-if", 1121 | "serde", 1122 | "serde_json", 1123 | "wasm-bindgen-macro", 1124 | ] 1125 | 1126 | [[package]] 1127 | name = "wasm-bindgen-backend" 1128 | version = "0.2.63" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" 1131 | dependencies = [ 1132 | "bumpalo", 1133 | "lazy_static", 1134 | "log", 1135 | "proc-macro2", 1136 | "quote", 1137 | "syn", 1138 | "wasm-bindgen-shared", 1139 | ] 1140 | 1141 | [[package]] 1142 | name = "wasm-bindgen-futures" 1143 | version = "0.4.13" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6" 1146 | dependencies = [ 1147 | "cfg-if", 1148 | "js-sys", 1149 | "wasm-bindgen", 1150 | "web-sys", 1151 | ] 1152 | 1153 | [[package]] 1154 | name = "wasm-bindgen-macro" 1155 | version = "0.2.63" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" 1158 | dependencies = [ 1159 | "quote", 1160 | "wasm-bindgen-macro-support", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "wasm-bindgen-macro-support" 1165 | version = "0.2.63" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" 1168 | dependencies = [ 1169 | "proc-macro2", 1170 | "quote", 1171 | "syn", 1172 | "wasm-bindgen-backend", 1173 | "wasm-bindgen-shared", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "wasm-bindgen-shared" 1178 | version = "0.2.63" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" 1181 | 1182 | [[package]] 1183 | name = "web-sys" 1184 | version = "0.3.40" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" 1187 | dependencies = [ 1188 | "js-sys", 1189 | "wasm-bindgen", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "winapi" 1194 | version = "0.2.8" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1197 | 1198 | [[package]] 1199 | name = "winapi" 1200 | version = "0.3.8" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 1203 | dependencies = [ 1204 | "winapi-i686-pc-windows-gnu", 1205 | "winapi-x86_64-pc-windows-gnu", 1206 | ] 1207 | 1208 | [[package]] 1209 | name = "winapi-build" 1210 | version = "0.1.1" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1213 | 1214 | [[package]] 1215 | name = "winapi-i686-pc-windows-gnu" 1216 | version = "0.4.0" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1219 | 1220 | [[package]] 1221 | name = "winapi-util" 1222 | version = "0.1.5" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1225 | dependencies = [ 1226 | "winapi 0.3.8", 1227 | ] 1228 | 1229 | [[package]] 1230 | name = "winapi-x86_64-pc-windows-gnu" 1231 | version = "0.4.0" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1234 | 1235 | [[package]] 1236 | name = "winreg" 1237 | version = "0.7.0" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 1240 | dependencies = [ 1241 | "winapi 0.3.8", 1242 | ] 1243 | 1244 | [[package]] 1245 | name = "ws2_32-sys" 1246 | version = "0.2.1" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1249 | dependencies = [ 1250 | "winapi 0.2.8", 1251 | "winapi-build", 1252 | ] 1253 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | version = "0.2.8" 4 | authors = ["Zhiyuan Zheng "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | reqwest = { version = "0.10", features = ["json", "cookies"] } 9 | tokio = { version = "0.2", features = ["full"] } 10 | serde = "1.0" 11 | dotenv = "0.15" 12 | simplelog = "0.8" 13 | -------------------------------------------------------------------------------- /tests/src/main.rs: -------------------------------------------------------------------------------- 1 | use reqwest::redirect::Policy; 2 | use serde::Deserialize; 3 | use serde::Serialize; 4 | use simplelog::{ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; 5 | use std::process::Command; 6 | use std::str::FromStr; 7 | use std::{env, process, thread}; 8 | use tokio::time::Duration; 9 | 10 | // Arguments: 11 | // - Executable 12 | // - Workdir 13 | // - Endpoint 14 | 15 | #[tokio::main] 16 | async fn main() -> Result<(), Box> { 17 | dotenv::dotenv().ok(); 18 | TermLogger::init( 19 | LevelFilter::Debug, 20 | ConfigBuilder::new() 21 | .set_time_format_str("%Y-%m-%d %H:%M:%S%.3f") 22 | .build(), 23 | TerminalMode::Mixed, 24 | ) 25 | .expect("Unable to bind terminal logger."); 26 | let args: Vec<_> = env::args().collect(); 27 | if args.len() != 4 { 28 | eprintln!("Wrong number of arguments."); 29 | process::exit(1); 30 | } 31 | 32 | let mut args = args.into_iter(); 33 | args.next(); 34 | let executable = args.next().unwrap(); 35 | let work_dir = args.next().unwrap(); 36 | let endpoint = args.next().unwrap(); 37 | let access_token = env::var("ACCESS_TOKEN").expect("No access_token found."); 38 | let corp_id = env::var("CORP_ID").unwrap(); 39 | let secret = env::var("SECRET").unwrap(); 40 | let agent_id = i32::from_str(&env::var("AGENT_ID").unwrap()).unwrap(); 41 | 42 | // 0. Start server. 43 | thread::spawn(move || { 44 | println!("Starting the server."); 45 | Command::new(executable) 46 | .current_dir(work_dir) 47 | .spawn() 48 | .expect("Failed to start"); 49 | }); 50 | 51 | tokio::time::delay_for(Duration::from_secs(10)).await; 52 | let client = reqwest::Client::builder() 53 | .cookie_store(true) 54 | .redirect(Policy::none()) 55 | .build() 56 | .unwrap(); 57 | // 1. I can access index. 58 | let resp = client.get(&format!("{}/", endpoint)).send().await?; 59 | assert!(resp.status().is_success()); 60 | 61 | // 2. I have to login. 62 | let resp = client 63 | .post(&format!("{}/login", endpoint)) 64 | .query(&[("access_token", &access_token)]) 65 | .send() 66 | .await?; 67 | assert!(resp.status().is_redirection()); 68 | let location = resp.headers().get("Location").expect("No Location found."); 69 | assert_eq!("/#/user", location.to_str().unwrap()); 70 | 71 | #[derive(Serialize)] 72 | struct Wechat { 73 | corp_id: String, 74 | agent_id: i32, 75 | secret: String, 76 | } 77 | // 3. I will fill my information. 78 | let resp = client 79 | .put(&format!("{}/wechat", endpoint)) 80 | .json(&Wechat { 81 | corp_id, 82 | agent_id, 83 | secret, 84 | }) 85 | .send() 86 | .await?; 87 | assert!(resp.status().is_success()); 88 | 89 | #[derive(Deserialize)] 90 | pub struct User { 91 | callback_url: String, 92 | } 93 | 94 | #[derive(Debug, Deserialize)] 95 | pub struct Response { 96 | success: bool, 97 | } 98 | 99 | // 4. Get my callback url. 100 | let resp = client.get(&format!("{}/user", endpoint)).send().await?; 101 | assert!(resp.status().is_success()); 102 | let callback = resp.json::().await?; 103 | assert!(!callback.callback_url.is_empty()); 104 | 105 | // 5. Send message. 106 | let resp = client 107 | .get(&callback.callback_url) 108 | .query(&[("text", "Message challenge sent from PipeHub test.")]) 109 | .send() 110 | .await?; 111 | assert!(resp.status().is_success()); 112 | let resp = resp.json::().await?; 113 | assert!(resp.success); 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /usecases/Notify Me.flo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzy0077/pipehub/05cfd59847f786907ba42f212c72f1fc0599d3a4/usecases/Notify Me.flo -------------------------------------------------------------------------------- /usecases/Notify Me.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhzy0077/pipehub/05cfd59847f786907ba42f212c72f1fc0599d3a4/usecases/Notify Me.pdf -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pipehub-web", 3 | "version": "0.3.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fluentui/react": "^8.55.2", 7 | "@types/jest": "^26.0.5", 8 | "@types/node": "^17.0.17", 9 | "@types/react": "^16.14.23", 10 | "@types/react-dom": "^16.9.0", 11 | "@types/react-router-dom": "^5.1.5", 12 | "@fluentui/theme": "^2.4.9", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-router-dom": "^6.2.1", 16 | "react-scripts": "^5.0.0", 17 | "typescript": "~4.5.5" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": "react-app" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | PipeHub 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /web/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppRoute from './Routes'; 3 | import Navs from './Navs'; 4 | import { Stack, Separator } from '@fluentui/react'; 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /web/src/Constants.ts: -------------------------------------------------------------------------------- 1 | 2 | const backend = "https://api.pipehub.net"; 3 | 4 | export { 5 | backend 6 | }; -------------------------------------------------------------------------------- /web/src/Faq.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Stack, Label, FontSizes, Text, Link, Depths } from '@fluentui/react'; 3 | 4 | function Faq() { 5 | return ( 6 | 7 | 8 |
9 | 10 | 11 | 12 | 简单来说就是一个方便你实现推送通知的玩意, 你可以通过简单的 HTTP 调用来将各种消息发送到手机上. 目前我用来做: 13 | 14 | 15 | 1. 对于 Android 用户来说, 原生, 国产 Rom 和不折腾几乎是个不可能三角, 我通过 PipeHub 来推送 Google FCM 消息, 以避免在国产 Rom 上折腾FCM. 16 | 17 | 18 | 2. 将一张专门用来收验证码的手机号放在旧手机中, 通过 PipeHub 推送收到的短信, 避免占用一个卡槽并且把我的短信箱搞的一团糟. 19 | 20 | 21 | 3. 通过一些 IFTTT 的规则, 推送一些消息, 如 Breaking News, 天气预报等. 22 | 23 | 24 | 25 | 我个人强依赖上述的几个用法, 就做了 PipeHub, 想到可能也有人有和我类似的需求, 就买了个域名接入了 GitHub, 希望能帮更多的朋友. 26 | 27 | 28 | 29 | 我以前也是 Server 酱的用户, 后来 Server 酱改成模板消息后就收不到推送很苦恼, 就做了PipeHub. PipeHub 使企业微信进行推送, 可靠性更好的同时也更安全. 30 | 31 | 32 | 33 | 免费, 不出意外的话 PipeHub 应该会一直免费下去. 34 | 35 | 36 | 37 | 目前开源在: https://github.com/zhzy0077/PipeHub, 欢迎 Star. 38 | 39 | 40 | 41 | 隐私是每个人最重要的权利之一, PipeHub 通过以下方式保障您的隐私: 42 | 43 | 44 | 1. PipeHub 仅依赖企业微信, 无需关注第三方公众号, 您可以(这也是推荐的方式)注册一个单独的企业用于推送, PipeHub 除了可以向您推送消息以外对您一无所知. 45 | 46 | 47 | 2. PipeHub 在 GitHub 登录过程中不要求任何权限, 也就是说 PipeHub 并不比一个不小心点开您 GitHub 主页的人知道更多. 48 | 49 | 50 | 3. PipeHub 不会存储 / 记录您发送的任何一条消息, 仅会对您是否发送成功做日志性的记录. 51 | 52 |
53 |
54 |
55 | ); 56 | } 57 | 58 | export default Faq; 59 | -------------------------------------------------------------------------------- /web/src/GetStarted.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Stack, Label, FontSizes, Text, Link, Depths } from '@fluentui/react'; 3 | 4 | function GetStarted() { 5 | return ( 6 | 7 | 8 |
9 | 10 | 11 | 12 | 在这里注册一个企业微信. 可以随意填写, 不需要验证账号. 13 | 14 | 15 | 记录下在'我的企业' Tab 里看到的企业 ID. 16 | 17 | 18 | 19 | 用你的个人微信账号, 在微信插件中关注你的企业微信服务号. 20 | 21 | 22 | 23 | 在应用管理中创建一个应用, 并记录下 Agent ID 和 Secret. 24 | 25 | 26 | 27 | 在右上角通过 GitHub 登录后, 填上你之前得到的企业 ID, Agent ID 和 Secret 并更新后, 就可以通过 User 页面中的 Callback URL 发送消息了. 请求示例: 28 | 29 | 30 | 1. GET https://www.pipehub.net/send/abcde?text=helloworld. 31 | 32 | 33 | 2. POST https://www.pipehub.net/send/abcde. 在 Payload 中的所有内容都会被推送. 34 | 35 | 36 | 3. 也可以在 GET 或者 POST 的 URL 中添加参数 to_party=$(对应的部门 ID, 可以用|(0x7C)连接), 会推送到对应的部门中所有人. 37 | 38 |
39 |
40 |
41 | ); 42 | } 43 | 44 | export default GetStarted; 45 | -------------------------------------------------------------------------------- /web/src/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Stack } from '@fluentui/react'; 3 | import GetStarted from './GetStarted'; 4 | import Faq from './Faq'; 5 | 6 | function Home() { 7 | return ( 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | export default Home; 16 | -------------------------------------------------------------------------------- /web/src/Navs.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties } from 'react'; 2 | import { Stack, FontSizes, Icon, Text } from '@fluentui/react'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const noLinkStyles: CSSProperties = { textDecoration: 'none', color: 'inherit' }; 6 | 7 | function Navs() { 8 | return ( 9 | 10 | 11 | 12 | 13 | PipeHub 14 | 15 | 16 | 17 | 18 | User 19 | 20 | 21 | 22 | ); 23 | } 24 | 25 | export default Navs; 26 | -------------------------------------------------------------------------------- /web/src/Routes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Route, 4 | Routes 5 | } from "react-router-dom"; 6 | import Home from './Home'; 7 | import User from './User'; 8 | 9 | function AppRoute() { 10 | return ( 11 | 12 | } /> 13 | } /> 14 | 15 | ); 16 | } 17 | 18 | export default AppRoute; 19 | -------------------------------------------------------------------------------- /web/src/Send.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Stack, Label, TextField, PrimaryButton } from '@fluentui/react'; 3 | import { UserEntity } from './User'; 4 | 5 | interface SendProps { 6 | user: UserEntity, 7 | } 8 | 9 | function Send(props: SendProps) { 10 | const [message, setMessage] = useState(""); 11 | const send = () => { 12 | fetch(`${props.user.callback_url}`, { 13 | method: "POST", 14 | body: message, 15 | }) 16 | .then(res => { 17 | return res.json(); 18 | }).then(entity => { 19 | const str = JSON.stringify(entity, null, 2); 20 | alert(str); 21 | }); 22 | }; 23 | 24 | const onMessageUpdate = (event: React.FormEvent, newVal?: string) => { 25 | setMessage(newVal || ""); 26 | } 27 | 28 | return ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 测试发送 38 | 39 | 40 | ); 41 | } 42 | 43 | export default Send; 44 | -------------------------------------------------------------------------------- /web/src/User.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Label, TextField, PrimaryButton, Separator, Text, DefaultButton, Callout, Stack, Checkbox, Icon, FontSizes } from '@fluentui/react'; 3 | import { useBoolean } from '@fluentui/react-hooks'; 4 | import { backend } from './Constants'; 5 | import Send from './Send'; 6 | 7 | function User() { 8 | const [user, setUser] = useState({} as UserEntity); 9 | const [wechat, setWechat] = useState({} as Wechat); 10 | const [isCalloutVisible, { toggle: toggleIsCalloutVisible }] = useBoolean(false); 11 | 12 | useEffect(() => { 13 | fetch(`${backend}/wechat`, { 14 | credentials: 'include' 15 | }) 16 | .then(res => { 17 | return res.json(); 18 | }).then((wechat: Wechat) => { 19 | setWechat(wechat); 20 | }) 21 | }, []); 22 | 23 | useEffect(() => { 24 | fetch(`${backend}/user`, { 25 | credentials: 'include' 26 | }) 27 | .then(res => { 28 | if (res.status === 401) { 29 | window.location.href = res.headers.get("Location") ?? "/"; 30 | } else { 31 | return res.json(); 32 | } 33 | }).then((entity: UserEntity) => { 34 | setUser(entity); 35 | }) 36 | }, []); 37 | 38 | const update = () => { 39 | fetch(`${backend}/wechat`, { 40 | method: "PUT", 41 | credentials: 'include', 42 | headers: { 43 | 'Content-Type': 'application/json' 44 | }, 45 | body: JSON.stringify(wechat) 46 | }) 47 | .then(res => { 48 | if (res.status < 400) { 49 | alert("Success"); 50 | } 51 | return res.text(); 52 | }).then(res => { 53 | console.log(res); 54 | }); 55 | 56 | fetch(`${backend}/user`, { 57 | method: "PUT", 58 | credentials: 'include', 59 | headers: { 60 | 'Content-Type': 'application/json' 61 | }, 62 | body: JSON.stringify(user) 63 | }) 64 | .then(res => { 65 | return res.text(); 66 | }).then(res => { 67 | console.log(res); 68 | }); 69 | }; 70 | const resetKey = () => { 71 | toggleIsCalloutVisible(); 72 | fetch(`${backend}/user/reset_key`, { 73 | method: "POST", 74 | credentials: 'include', 75 | }) 76 | .then(res => { 77 | if (res.status < 400) { 78 | alert("Success"); 79 | } 80 | return res.json(); 81 | }).then((entity: UserEntity) => { 82 | setUser(entity); 83 | }); 84 | } 85 | const onCorpIdChange = (event: React.FormEvent, newVal?: string) => { 86 | setWechat({ 87 | ...wechat, 88 | corp_id: newVal || '', 89 | }); 90 | } 91 | const onAgentIdChange = (event: React.FormEvent, newVal?: string) => { 92 | setWechat({ 93 | ...wechat, 94 | agent_id: parseInt(newVal || '0'), 95 | }); 96 | } 97 | const onSecretChange = (event: React.FormEvent, newVal?: string) => { 98 | setWechat({ 99 | ...wechat, 100 | secret: newVal || '', 101 | }); 102 | } 103 | const onBlockListChange = (event: React.FormEvent, newVal?: string) => { 104 | setUser({ 105 | ...user, 106 | block_list: newVal || '', 107 | }); 108 | } 109 | 110 | const onMicrosoftListIdChange = (event: React.FormEvent, newVal?: string) => { 111 | setUser({ 112 | ...user, 113 | msft_task_list_id: newVal || '', 114 | }); 115 | } 116 | 117 | const onCaptchaChange = (event: React.FormEvent | undefined, newVal?: boolean) => { 118 | setUser({ 119 | ...user, 120 | captcha: newVal || false, 121 | }); 122 | } 123 | const onBotTokenChange = (event: React.FormEvent, newVal?: string) => { 124 | setWechat({ 125 | ...wechat, 126 | telegram_bot_token: newVal || '', 127 | }); 128 | } 129 | 130 | const onChatIdChange = (event: React.FormEvent, newVal?: string) => { 131 | setWechat({ 132 | ...wechat, 133 | telegram_chat_id: newVal || '', 134 | }); 135 | } 136 | 137 | const msftLogin = () => { 138 | fetch(`${backend}/msft_auth_url`, { 139 | credentials: 'include' 140 | }) 141 | .then(res => { 142 | window.location.href = res.headers.get("Location") ?? "/"; 143 | }) 144 | }; 145 | 146 | return ( 147 |
148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | { user.msft_refresh_token != "" && }
162 | 更新 163 | 164 | 170 | {isCalloutVisible ? ( 171 |
172 | 178 |
179 | 182 | 183 | 184 | 188 | 192 | 193 |
194 |
195 |
196 | ) : null} 197 | 198 | 199 | 您的 Callback URL 是: {user.callback_url} 200 | 201 | 202 |
203 | ); 204 | } 205 | 206 | export interface UserEntity { 207 | github_login: string, 208 | github_id: number, 209 | app_key: string, 210 | callback_url: string, 211 | block_list: string, 212 | captcha: boolean, 213 | msft_refresh_token: string, 214 | msft_task_list_id: string, 215 | } 216 | 217 | export interface Wechat { 218 | corp_id: string, 219 | agent_id: number, 220 | secret: string, 221 | telegram_bot_token: string, 222 | telegram_chat_id: string, 223 | } 224 | 225 | export default User; 226 | -------------------------------------------------------------------------------- /web/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | width: 600px; 7 | margin-left: auto; 8 | margin-right: auto; 9 | margin-top: 20px; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | code { 15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 16 | monospace; 17 | } 18 | -------------------------------------------------------------------------------- /web/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | import { HashRouter } from 'react-router-dom'; 7 | import { initializeIcons } from '@fluentui/react'; 8 | 9 | initializeIcons(); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | , 16 | document.getElementById('root') 17 | ); 18 | 19 | // If you want your app to work offline and load faster, you can change 20 | // unregister() to register() below. Note this comes with some pitfalls. 21 | // Learn more about service workers: https://bit.ly/CRA-PWA 22 | serviceWorker.unregister(); 23 | -------------------------------------------------------------------------------- /web/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | process.env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl, { 112 | headers: { 'Service-Worker': 'script' } 113 | }) 114 | .then(response => { 115 | // Ensure service worker exists, and that we really are getting a JS file. 116 | const contentType = response.headers.get('content-type'); 117 | if ( 118 | response.status === 404 || 119 | (contentType != null && contentType.indexOf('javascript') === -1) 120 | ) { 121 | // No service worker found. Probably a different app. Reload the page. 122 | navigator.serviceWorker.ready.then(registration => { 123 | registration.unregister().then(() => { 124 | window.location.reload(); 125 | }); 126 | }); 127 | } else { 128 | // Service worker found. Proceed as normal. 129 | registerValidSW(swUrl, config); 130 | } 131 | }) 132 | .catch(() => { 133 | console.log( 134 | 'No internet connection found. App is running in offline mode.' 135 | ); 136 | }); 137 | } 138 | 139 | export function unregister() { 140 | if ('serviceWorker' in navigator) { 141 | navigator.serviceWorker.ready 142 | .then(registration => { 143 | registration.unregister(); 144 | }) 145 | .catch(error => { 146 | console.error(error.message); 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------