├── .dockerignore ├── .env.dist ├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── release.yml │ └── temp-release.yml ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── data └── .gitignore ├── docker-compose.gluetun.yml ├── docker-compose.simple.yml ├── package-lock.json ├── package.json ├── src ├── index.js ├── middlewares │ └── auth.js ├── routers │ ├── config │ │ ├── fs.js │ │ └── index.js │ └── qbit │ │ └── index.js ├── routines │ └── update.js ├── ssl.js └── utils.js └── tests ├── config.http ├── ping.http └── qbit.http /.dockerignore: -------------------------------------------------------------------------------- 1 | data 2 | node_modules 3 | .env* 4 | -------------------------------------------------------------------------------- /.env.dist: -------------------------------------------------------------------------------- 1 | CONFIG_PATH=/config 2 | PORT=3000 3 | QBIT_BASE=http://host.docker.internal:8080 4 | RELEASE_TYPE=stable 5 | UPDATE_VT_CRON=0 * * * * 6 | USE_INSECURE_SSL=false 7 | SKIP_X_FORWARD_HEADERS=false 8 | VUETORRENT_PATH=/vuetorrent 9 | GITHUB_AUTH=***** 10 | 11 | # SSL_CERT_PATH=/config/ssl/cert.pem 12 | # SSL_KEY_PATH=/config/ssl/key.pem 13 | # SSL_CERT=***** 14 | # SSL_KEY=***** 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: 'github-actions' 5 | directory: '/' 6 | schedule: 7 | interval: 'weekly' 8 | 9 | # Maintain dependencies for npm 10 | - package-ecosystem: 'npm' 11 | directory: '/' 12 | schedule: 13 | interval: 'weekly' 14 | groups: 15 | all: 16 | patterns: 17 | - '*' 18 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: 'CodeQL' 2 | 3 | on: 4 | schedule: 5 | # At 00:00 on Sunday 6 | - cron: '0 0 * * 0' 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | branches: 12 | - main 13 | paths: 14 | - src 15 | 16 | jobs: 17 | analyze: 18 | name: Analyze 19 | runs-on: ubuntu-latest 20 | timeout-minutes: 360 21 | 22 | permissions: 23 | actions: read 24 | contents: read 25 | security-events: write 26 | 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | language: ['javascript-typescript'] 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v3 37 | with: 38 | languages: ${{ matrix.language }} 39 | 40 | - name: Autobuild 41 | uses: github/codeql-action/autobuild@v3 42 | 43 | - name: Perform CodeQL Analysis 44 | uses: github/codeql-action/analyze@v3 45 | with: 46 | category: '/language:${{matrix.language}}' -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | IMAGE_NAME: ${{ github.repository }} 11 | 12 | jobs: 13 | setup-release: 14 | name: Run Release Please action 15 | outputs: 16 | release_created: ${{ steps.release.outputs.release_created }} 17 | tag_name: ${{ steps.release.outputs.tag_name }} 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write 21 | pull-requests: write 22 | 23 | steps: 24 | - name: Run release please 25 | uses: googleapis/release-please-action@v4 26 | id: release 27 | with: 28 | release-type: node 29 | 30 | build: 31 | name: Build & push docker image 32 | runs-on: ubuntu-latest 33 | needs: setup-release 34 | if: needs.setup-release.outputs.release_created 35 | permissions: 36 | contents: write 37 | packages: write 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v4 41 | 42 | - name: Update latest tag 43 | uses: EndBug/latest-tag@v1 44 | with: 45 | ref: latest-release 46 | 47 | - name: Set up QEMU 48 | uses: docker/setup-qemu-action@v3 49 | 50 | - name: Set up Docker Buildx 51 | uses: docker/setup-buildx-action@v3 52 | 53 | - name: Log in to the Container registry 54 | uses: docker/login-action@v3 55 | with: 56 | registry: ${{ env.REGISTRY }} 57 | username: ${{ github.actor }} 58 | password: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | - name: Extract metadata (tags, labels) for Docker 61 | id: meta 62 | uses: docker/metadata-action@v5 63 | with: 64 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 65 | tags: type=match,pattern=v(.*),group=1,value=${{ needs.setup-release.outputs.tag_name }} 66 | 67 | - name: Build and push Docker image 68 | uses: docker/build-push-action@v6 69 | env: 70 | DOCKER_BUILD_SUMMARY: false 71 | with: 72 | context: . 73 | push: true 74 | platforms: linux/amd64,linux/arm64 75 | tags: ${{ steps.meta.outputs.tags }} 76 | labels: ${{ steps.meta.outputs.labels }} 77 | -------------------------------------------------------------------------------- /.github/workflows/temp-release.yml: -------------------------------------------------------------------------------- 1 | name: Temporary Release CI 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | name: 7 | description: Docker image tag to apply to the image 8 | required: true 9 | 10 | env: 11 | REGISTRY: ghcr.io 12 | IMAGE_NAME: ${{ github.repository }} 13 | 14 | jobs: 15 | build: 16 | name: Build & push temporary docker image 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: read 20 | packages: write 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up QEMU 26 | uses: docker/setup-qemu-action@v3 27 | 28 | - name: Set up Docker Buildx 29 | uses: docker/setup-buildx-action@v3 30 | 31 | - name: Log in to the Container registry 32 | uses: docker/login-action@v3 33 | with: 34 | registry: ${{ env.REGISTRY }} 35 | username: ${{ github.actor }} 36 | password: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | - name: Extract metadata (tags, labels) for Docker 39 | id: meta 40 | uses: docker/metadata-action@v5 41 | with: 42 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 43 | tags: ${{ github.event.inputs.name }} 44 | 45 | - name: Build and push Docker image 46 | uses: docker/build-push-action@v6 47 | env: 48 | DOCKER_BUILD_SUMMARY: false 49 | with: 50 | context: . 51 | push: true 52 | platforms: linux/amd64,linux/arm64 53 | tags: ${{ steps.meta.outputs.tags }} 54 | labels: ${{ steps.meta.outputs.labels }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | vuetorrent/ 4 | .env 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.5.1](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.5.0...v2.5.1) (2025-05-30) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * **ci:** Update token permissions ([c595def](https://github.com/VueTorrent/vuetorrent-backend/commit/c595deff6d9740f9ff98cdc0f0380300149342f1)) 9 | 10 | ## [2.5.0](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.4.0...v2.5.0) (2025-05-30) 11 | 12 | 13 | ### Features 14 | 15 | * **ci:** Add "latest-release" tag on source release ([#95](https://github.com/VueTorrent/vuetorrent-backend/issues/95)) ([f658dbc](https://github.com/VueTorrent/vuetorrent-backend/commit/f658dbc1e4ee954efd3bb1c2cbae2922a7017c80)) 16 | 17 | ## [2.4.0](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.3.0...v2.4.0) (2025-05-25) 18 | 19 | 20 | ### Features 21 | 22 | * Support running server in HTTPS ([#92](https://github.com/VueTorrent/vuetorrent-backend/issues/92)) ([790bbdb](https://github.com/VueTorrent/vuetorrent-backend/commit/790bbdb70b4c2a5b98e654d720901b50d35bc0fd)) 23 | 24 | ## [2.3.0](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.2.0...v2.3.0) (2025-04-26) 25 | 26 | 27 | ### Features 28 | 29 | * Allow version pinning ([#89](https://github.com/VueTorrent/vuetorrent-backend/issues/89)) ([1a56892](https://github.com/VueTorrent/vuetorrent-backend/commit/1a56892c32159633cbca7f2a16551c0fcf93d5f6)) 30 | * **GitHub:** Add PAT support for increased rate limit ([#87](https://github.com/VueTorrent/vuetorrent-backend/issues/87)) ([2318ca4](https://github.com/VueTorrent/vuetorrent-backend/commit/2318ca434adb680bd170c2ebd890bdbb42c1e161)) 31 | 32 | ## [2.2.0](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.1.3...v2.2.0) (2024-12-15) 33 | 34 | 35 | ### Features 36 | 37 | * **Docker:** expose a SKIP_X_FORWARD_HEADERS option ([#75](https://github.com/VueTorrent/vuetorrent-backend/issues/75)) ([b3677ab](https://github.com/VueTorrent/vuetorrent-backend/commit/b3677ab3db379aae3bfe55352e270f81e24843f2)) 38 | 39 | ## [2.1.3](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.1.2...v2.1.3) (2024-10-06) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * **auth:** Handle 401 being thrown on login ([5c0e24b](https://github.com/VueTorrent/vuetorrent-backend/commit/5c0e24bde11b135a11c7fbd8e5156a19530542cf)) 45 | 46 | ## [2.1.2](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.1.1...v2.1.2) (2024-10-01) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * **Docker:** Allow container with non-root user ([#54](https://github.com/VueTorrent/vuetorrent-backend/issues/54)) ([bc4bf70](https://github.com/VueTorrent/vuetorrent-backend/commit/bc4bf707c86d9effefeb5b6226d2df30ff79c5fb)) 52 | 53 | ## [2.1.1](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.1.0...v2.1.1) (2024-09-26) 54 | 55 | 56 | ### Bug Fixes 57 | 58 | * **update:** Use release branches instead of release asset ([#51](https://github.com/VueTorrent/vuetorrent-backend/issues/51)) ([25b139b](https://github.com/VueTorrent/vuetorrent-backend/commit/25b139b9986a5fc7154b231377d2664e59ff6a9f)) 59 | 60 | ## [2.1.0](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.0.2...v2.1.0) (2024-09-22) 61 | 62 | 63 | ### Features 64 | 65 | * Add auto-update scheduler for WebUI ([16bd320](https://github.com/VueTorrent/vuetorrent-backend/commit/16bd3204615e02bb7b4d089776138345d99c16be)) 66 | 67 | 68 | ### Bug Fixes 69 | 70 | * Make dev-only CONFIG_PATH env optional ([503434b](https://github.com/VueTorrent/vuetorrent-backend/commit/503434b512e73c422d5b884535990ff46c1b1242)) 71 | 72 | ## [2.0.2](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.0.1...v2.0.2) (2024-09-20) 73 | 74 | 75 | ### Bug Fixes 76 | 77 | * **router:** Remove /backend prefix on /api requests ([#45](https://github.com/VueTorrent/vuetorrent-backend/issues/45)) ([512c619](https://github.com/VueTorrent/vuetorrent-backend/commit/512c61959d28093356482754a395f4349e8fd6b2)) 78 | 79 | 80 | ### Performance Improvements 81 | 82 | * Update 404 error message ([#43](https://github.com/VueTorrent/vuetorrent-backend/issues/43)) ([fc5a1dd](https://github.com/VueTorrent/vuetorrent-backend/commit/fc5a1ddecaa13aa42da204a69da51dbd76de68ca)) 83 | 84 | ## [2.0.1](https://github.com/VueTorrent/vuetorrent-backend/compare/v2.0.0...v2.0.1) (2024-09-19) 85 | 86 | 87 | ### Bug Fixes 88 | 89 | * Add /backend prefix to all routes ([#40](https://github.com/VueTorrent/vuetorrent-backend/issues/40)) ([4849614](https://github.com/VueTorrent/vuetorrent-backend/commit/4849614fa8fbbde1059ac24ffa437348d79140c5)) 90 | 91 | ## [2.0.0](https://github.com/VueTorrent/vuetorrent-backend/compare/v1.2.3...v2.0.0) (2024-09-18) 92 | 93 | 94 | ### ⚠ BREAKING CHANGES 95 | 96 | * Forward API requests to qBittorrent ([#35](https://github.com/VueTorrent/vuetorrent-backend/issues/35)) 97 | 98 | ### Features 99 | 100 | * Add update management routes ([#36](https://github.com/VueTorrent/vuetorrent-backend/issues/36)) ([f26dffe](https://github.com/VueTorrent/vuetorrent-backend/commit/f26dffeb7f6854f431a7cc7b70754de1e4be8024)) 101 | * Forward API requests to qBittorrent ([#35](https://github.com/VueTorrent/vuetorrent-backend/issues/35)) ([acd643b](https://github.com/VueTorrent/vuetorrent-backend/commit/acd643b05746dcb65fbadf4509c16cea23d81b03)) 102 | 103 | ## [1.2.3](https://github.com/VueTorrent/vuetorrent-backend/compare/v1.2.2...v1.2.3) (2024-06-07) 104 | 105 | 106 | ### Reverts 107 | 108 | * Add ARM architectures support ([#23](https://github.com/VueTorrent/vuetorrent-backend/issues/23)) ([#25](https://github.com/VueTorrent/vuetorrent-backend/issues/25)) ([ed80b5a](https://github.com/VueTorrent/vuetorrent-backend/commit/ed80b5a55080bd1366fc44fdc3a7f56b0a46ee9d)) 109 | 110 | ## [1.2.2](https://github.com/VueTorrent/vuetorrent-backend/compare/v1.2.1...v1.2.2) (2024-06-07) 111 | 112 | 113 | ### Bug Fixes 114 | 115 | * **docker:** Add ARM architectures support ([#23](https://github.com/VueTorrent/vuetorrent-backend/issues/23)) ([14fdaf9](https://github.com/VueTorrent/vuetorrent-backend/commit/14fdaf9cac4a825a64d01b23cfa6774fa09f3634)) 116 | 117 | ## [1.2.1](https://github.com/VueTorrent/vuetorrent-backend/compare/v1.2.0...v1.2.1) (2024-05-12) 118 | 119 | 120 | ### Bug Fixes 121 | 122 | * **docker:** Enable multi-platform build ([946717b](https://github.com/VueTorrent/vuetorrent-backend/commit/946717b94fb717efc43c4144298b255e8e9069f6)) 123 | 124 | ## [1.2.0](https://github.com/VueTorrent/vuetorrent-backend/compare/v1.1.2...v1.2.0) (2024-05-12) 125 | 126 | 127 | ### Features 128 | 129 | * docker container support arm64 ([#18](https://github.com/VueTorrent/vuetorrent-backend/issues/18)) ([b7666f2](https://github.com/VueTorrent/vuetorrent-backend/commit/b7666f209b7b9e1649d37db46b42c1a1f4061c0d)) 130 | 131 | ## [1.1.2](https://github.com/VueTorrent/vuetorrent-backend/compare/v1.1.1...v1.1.2) (2024-05-08) 132 | 133 | 134 | ### Bug Fixes 135 | 136 | * **docker:** Switch to alpine base to reduce image size ([4214e9b](https://github.com/VueTorrent/vuetorrent-backend/commit/4214e9b90d9df90e96925d5665184c7ef5783725)) 137 | 138 | ## [1.1.1](https://github.com/VueTorrent/vuetorrent-backend/compare/v1.1.0...v1.1.1) (2024-05-08) 139 | 140 | 141 | ### Bug Fixes 142 | 143 | * **auth:** Add DISABLE_AUTH env to disable auth check ([#15](https://github.com/VueTorrent/vuetorrent-backend/issues/15)) ([f09899d](https://github.com/VueTorrent/vuetorrent-backend/commit/f09899d19321f9b25f1b391635be88f2df656254)) 144 | 145 | ## [1.1.0](https://github.com/VueTorrent/vuetorrent-backend/compare/v1.0.0...v1.1.0) (2024-04-01) 146 | 147 | 148 | ### Features 149 | 150 | * Handle authentication through qbit ([#10](https://github.com/VueTorrent/vuetorrent-backend/issues/10)) ([3954e99](https://github.com/VueTorrent/vuetorrent-backend/commit/3954e991f7f79784d854440e1b574ebfce79452e)) 151 | 152 | ## 1.0.0 (2024-03-15) 153 | 154 | 155 | ### Features 156 | 157 | * **config:** Create server-side store ([bdd15e4](https://github.com/VueTorrent/vuetorrent-backend/commit/bdd15e404e9efc839ef3023d74ba7011b31fe46d)) 158 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:21-alpine 2 | RUN mkdir -p -m 0777 /vuetorrent 3 | WORKDIR /app 4 | COPY package*.json ./ 5 | RUN npm ci 6 | COPY src/ src/ 7 | EXPOSE 3000 8 | CMD ["node", "src/index.js"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vuetorrent-backend 2 | 3 | This project is a Node.js web service designed to enhance the capabilities of [VueTorrent](https://github.com/VueTorrent/VueTorrent) users. 4 | 5 | > [!TIP] 6 | > Installation guide can be found in the [wiki](https://github.com/VueTorrent/vuetorrent-backend/wiki/Installation) 7 | 8 | Endpoints are accessible using tools like cURL, Postman, or by making HTTP requests from your browser or any programming language/framework. 9 | 10 | You can find a list of every feature in the [Wiki](https://github.com/VueTorrent/vuetorrent-backend/wiki). 11 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore -------------------------------------------------------------------------------- /docker-compose.gluetun.yml: -------------------------------------------------------------------------------- 1 | services: 2 | gluetun: 3 | image: ghcr.io/qdm12/gluetun:latest 4 | container_name: gluetun 5 | restart: unless-stopped 6 | cap_add: 7 | - NET_ADMIN 8 | environment: 9 | # Include your gluetun config here 10 | devices: 11 | - "/dev/net/tun:/dev/net/tun" 12 | volumes: 13 | - "./data/gluetun:/gluetun" 14 | ports: 15 | - 8123:8123/tcp # Gluetun API 16 | 17 | # qBittorrent ports, vuetorrent-backend will forward requests to it so no need to expose it 18 | # - "8080:8080" 19 | # NAT port forwarding required for them to work 20 | - "6881:6881" 21 | - "6881:6881/udp" 22 | 23 | # vuetorrent-backend 24 | - "8081:8081" 25 | 26 | qbit: 27 | image: lscr.io/linuxserver/qbittorrent:latest 28 | container_name: qbit 29 | restart: unless-stopped 30 | network_mode: service:gluetun 31 | environment: 32 | - WEBUI_PORT=8080 33 | - PUID=1000 34 | - PGID=1000 35 | volumes: 36 | - "./data/qbittorrent:/config" 37 | - "./data/downloads:/downloads" 38 | - "./data/torrents:/torrents" 39 | 40 | vuetorrent-backend: 41 | # build: . 42 | image: ghcr.io/vuetorrent/vuetorrent-backend:latest 43 | container_name: vuetorrent-backend 44 | restart: unless-stopped 45 | network_mode: service:gluetun 46 | environment: 47 | # Backend port can't match qbit one because of the network_mode 48 | # You'll have to remove "Host header validation" in qbit settings > WebUI 49 | - PORT=8081 50 | 51 | # Here we can use localhost because both qbit and backend container are on the same docker host (network_mode) 52 | - QBIT_BASE=http://localhost:8080 53 | # Use "dev" for nightly releases 54 | - RELEASE_TYPE=stable 55 | # Optional, refer to the node-schedule package for compatible syntax 56 | - UPDATE_VT_CRON=0 * * * * 57 | 58 | # Define this if using self-signed certificates on qBittorrent 59 | # - NODE_EXTRA_CA_CERTS=/config/ssl/cert.pem 60 | # You can also disable SSL verification (not recommended) 61 | # - USE_INSECURE_SSL=true 62 | 63 | # Only enable if backend container is behind a proxy server which already add x-forward headers 64 | # - SKIP_X_FORWARD_HEADERS=true 65 | # Create a Github PAT with your account to increase API rate limit, fine-grained with only public repo access is enough 66 | # - GITHUB_AUTH=${GITHUB_AUTH} 67 | 68 | # If you want HTTPS, define the following variables with respective paths 69 | # - SSL_CERT_PATH=/config/ssl/cert.pem 70 | # - SSL_KEY_PATH=/config/ssl/key.pem 71 | # Use these instead for passing the value directly 72 | # - SSL_CERT=***** 73 | # - SSL_KEY=***** 74 | volumes: 75 | - "./data/config:/config" 76 | -------------------------------------------------------------------------------- /docker-compose.simple.yml: -------------------------------------------------------------------------------- 1 | services: 2 | qbit: 3 | image: lscr.io/linuxserver/qbittorrent:latest 4 | container_name: qbit 5 | restart: unless-stopped 6 | networks: 7 | - qbit-net 8 | environment: 9 | - WEBUI_PORT=8080 10 | - PUID=1000 11 | - PGID=1000 12 | ports: 13 | # qBittorrent WebUI is exposed by vuetorrent-backend service 14 | # - "8080:8080" 15 | 16 | # qBittorrent ports, NAT port forwarding required for them to work 17 | - "6881:6881" 18 | - "6881:6881/udp" 19 | volumes: 20 | - "./data/qbittorrent:/config" 21 | - "./data/downloads:/downloads" 22 | - "./data/torrents:/torrents" 23 | 24 | vuetorrent-backend: 25 | # build: . 26 | image: ghcr.io/vuetorrent/vuetorrent-backend:latest 27 | container_name: vuetorrent-backend 28 | restart: unless-stopped 29 | networks: 30 | - qbit-net 31 | environment: 32 | # Backend port needs to match qbit one to avoid "Host header validation" issues 33 | - PORT=8080 34 | - QBIT_BASE=http://qbit:8080 35 | # Use "dev" for nightly releases 36 | - RELEASE_TYPE=stable 37 | # Optional, refer to the node-schedule package for compatible syntax 38 | - UPDATE_VT_CRON=0 * * * * 39 | 40 | # Define this if using self-signed certificates on qBittorrent 41 | # - NODE_EXTRA_CA_CERTS=/config/ssl/cert.pem 42 | # You can also disable SSL verification (not recommended) 43 | # - USE_INSECURE_SSL=true 44 | 45 | # Only enable if backend container is behind a proxy server which already add x-forward headers 46 | # - SKIP_X_FORWARD_HEADERS=true 47 | # Create a Github PAT with your account to increase API rate limit 48 | # fine-grained token with public repo access is enough 49 | # https://github.com/settings/personal-access-tokens/new 50 | # - GITHUB_AUTH=${GITHUB_AUTH} 51 | 52 | # If you want HTTPS, define the following variables with respective paths 53 | # - SSL_CERT_PATH=/config/ssl/cert.pem 54 | # - SSL_KEY_PATH=/config/ssl/key.pem 55 | # Use these instead for passing the value directly 56 | # - SSL_CERT=***** 57 | # - SSL_KEY=***** 58 | ports: 59 | - "8080:8080" 60 | volumes: 61 | - "./data/config:/config" 62 | 63 | # This network is only there for the example, 64 | # if you already have an external network (e.g. for use with a reverse proxy), 65 | # you can use the existing one instead. 66 | networks: 67 | qbit-net: 68 | driver: bridge 69 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuetorrent-backend", 3 | "version": "2.5.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "vuetorrent-backend", 9 | "version": "2.5.1", 10 | "license": "ISC", 11 | "dependencies": { 12 | "axios": "^1.10.0", 13 | "cookie-parser": "^1.4.7", 14 | "cors": "^2.8.5", 15 | "dotenv": "^17.0.0", 16 | "express": "^5.1.0", 17 | "http-proxy": "^1.18.1", 18 | "jszip": "^3.10.1", 19 | "morgan": "^1.10.0", 20 | "node-schedule": "^2.1.1" 21 | }, 22 | "devDependencies": { 23 | "nodemon": "^3.1.10" 24 | } 25 | }, 26 | "node_modules/abbrev": { 27 | "version": "1.1.1", 28 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 29 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 30 | "dev": true 31 | }, 32 | "node_modules/accepts": { 33 | "version": "2.0.0", 34 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 35 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 36 | "license": "MIT", 37 | "dependencies": { 38 | "mime-types": "^3.0.0", 39 | "negotiator": "^1.0.0" 40 | }, 41 | "engines": { 42 | "node": ">= 0.6" 43 | } 44 | }, 45 | "node_modules/accepts/node_modules/mime-db": { 46 | "version": "1.54.0", 47 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 48 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 49 | "license": "MIT", 50 | "engines": { 51 | "node": ">= 0.6" 52 | } 53 | }, 54 | "node_modules/accepts/node_modules/mime-types": { 55 | "version": "3.0.1", 56 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 57 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 58 | "license": "MIT", 59 | "dependencies": { 60 | "mime-db": "^1.54.0" 61 | }, 62 | "engines": { 63 | "node": ">= 0.6" 64 | } 65 | }, 66 | "node_modules/anymatch": { 67 | "version": "3.1.3", 68 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 69 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 70 | "dev": true, 71 | "dependencies": { 72 | "normalize-path": "^3.0.0", 73 | "picomatch": "^2.0.4" 74 | }, 75 | "engines": { 76 | "node": ">= 8" 77 | } 78 | }, 79 | "node_modules/asynckit": { 80 | "version": "0.4.0", 81 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 82 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 83 | }, 84 | "node_modules/axios": { 85 | "version": "1.10.0", 86 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", 87 | "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", 88 | "license": "MIT", 89 | "dependencies": { 90 | "follow-redirects": "^1.15.6", 91 | "form-data": "^4.0.0", 92 | "proxy-from-env": "^1.1.0" 93 | } 94 | }, 95 | "node_modules/balanced-match": { 96 | "version": "1.0.2", 97 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 98 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 99 | "dev": true 100 | }, 101 | "node_modules/basic-auth": { 102 | "version": "2.0.1", 103 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 104 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 105 | "dependencies": { 106 | "safe-buffer": "5.1.2" 107 | }, 108 | "engines": { 109 | "node": ">= 0.8" 110 | } 111 | }, 112 | "node_modules/basic-auth/node_modules/safe-buffer": { 113 | "version": "5.1.2", 114 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 115 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 116 | }, 117 | "node_modules/binary-extensions": { 118 | "version": "2.3.0", 119 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 120 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 121 | "dev": true, 122 | "engines": { 123 | "node": ">=8" 124 | }, 125 | "funding": { 126 | "url": "https://github.com/sponsors/sindresorhus" 127 | } 128 | }, 129 | "node_modules/body-parser": { 130 | "version": "2.2.0", 131 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", 132 | "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 133 | "license": "MIT", 134 | "dependencies": { 135 | "bytes": "^3.1.2", 136 | "content-type": "^1.0.5", 137 | "debug": "^4.4.0", 138 | "http-errors": "^2.0.0", 139 | "iconv-lite": "^0.6.3", 140 | "on-finished": "^2.4.1", 141 | "qs": "^6.14.0", 142 | "raw-body": "^3.0.0", 143 | "type-is": "^2.0.0" 144 | }, 145 | "engines": { 146 | "node": ">=18" 147 | } 148 | }, 149 | "node_modules/body-parser/node_modules/debug": { 150 | "version": "4.4.0", 151 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 152 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 153 | "license": "MIT", 154 | "dependencies": { 155 | "ms": "^2.1.3" 156 | }, 157 | "engines": { 158 | "node": ">=6.0" 159 | }, 160 | "peerDependenciesMeta": { 161 | "supports-color": { 162 | "optional": true 163 | } 164 | } 165 | }, 166 | "node_modules/body-parser/node_modules/ms": { 167 | "version": "2.1.3", 168 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 169 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 170 | "license": "MIT" 171 | }, 172 | "node_modules/brace-expansion": { 173 | "version": "1.1.11", 174 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 175 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 176 | "dev": true, 177 | "dependencies": { 178 | "balanced-match": "^1.0.0", 179 | "concat-map": "0.0.1" 180 | } 181 | }, 182 | "node_modules/braces": { 183 | "version": "3.0.3", 184 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 185 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 186 | "dev": true, 187 | "dependencies": { 188 | "fill-range": "^7.1.1" 189 | }, 190 | "engines": { 191 | "node": ">=8" 192 | } 193 | }, 194 | "node_modules/bytes": { 195 | "version": "3.1.2", 196 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 197 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 198 | "license": "MIT", 199 | "engines": { 200 | "node": ">= 0.8" 201 | } 202 | }, 203 | "node_modules/call-bind-apply-helpers": { 204 | "version": "1.0.2", 205 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 206 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 207 | "license": "MIT", 208 | "dependencies": { 209 | "es-errors": "^1.3.0", 210 | "function-bind": "^1.1.2" 211 | }, 212 | "engines": { 213 | "node": ">= 0.4" 214 | } 215 | }, 216 | "node_modules/call-bound": { 217 | "version": "1.0.4", 218 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 219 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 220 | "license": "MIT", 221 | "dependencies": { 222 | "call-bind-apply-helpers": "^1.0.2", 223 | "get-intrinsic": "^1.3.0" 224 | }, 225 | "engines": { 226 | "node": ">= 0.4" 227 | }, 228 | "funding": { 229 | "url": "https://github.com/sponsors/ljharb" 230 | } 231 | }, 232 | "node_modules/chokidar": { 233 | "version": "3.6.0", 234 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 235 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 236 | "dev": true, 237 | "dependencies": { 238 | "anymatch": "~3.1.2", 239 | "braces": "~3.0.2", 240 | "glob-parent": "~5.1.2", 241 | "is-binary-path": "~2.1.0", 242 | "is-glob": "~4.0.1", 243 | "normalize-path": "~3.0.0", 244 | "readdirp": "~3.6.0" 245 | }, 246 | "engines": { 247 | "node": ">= 8.10.0" 248 | }, 249 | "funding": { 250 | "url": "https://paulmillr.com/funding/" 251 | }, 252 | "optionalDependencies": { 253 | "fsevents": "~2.3.2" 254 | } 255 | }, 256 | "node_modules/combined-stream": { 257 | "version": "1.0.8", 258 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 259 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 260 | "dependencies": { 261 | "delayed-stream": "~1.0.0" 262 | }, 263 | "engines": { 264 | "node": ">= 0.8" 265 | } 266 | }, 267 | "node_modules/concat-map": { 268 | "version": "0.0.1", 269 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 270 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 271 | "dev": true 272 | }, 273 | "node_modules/content-disposition": { 274 | "version": "1.0.0", 275 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 276 | "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 277 | "license": "MIT", 278 | "dependencies": { 279 | "safe-buffer": "5.2.1" 280 | }, 281 | "engines": { 282 | "node": ">= 0.6" 283 | } 284 | }, 285 | "node_modules/content-type": { 286 | "version": "1.0.5", 287 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 288 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 289 | "license": "MIT", 290 | "engines": { 291 | "node": ">= 0.6" 292 | } 293 | }, 294 | "node_modules/cookie": { 295 | "version": "0.7.1", 296 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 297 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 298 | "engines": { 299 | "node": ">= 0.6" 300 | } 301 | }, 302 | "node_modules/cookie-parser": { 303 | "version": "1.4.7", 304 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", 305 | "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", 306 | "dependencies": { 307 | "cookie": "0.7.2", 308 | "cookie-signature": "1.0.6" 309 | }, 310 | "engines": { 311 | "node": ">= 0.8.0" 312 | } 313 | }, 314 | "node_modules/cookie-parser/node_modules/cookie": { 315 | "version": "0.7.2", 316 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", 317 | "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", 318 | "engines": { 319 | "node": ">= 0.6" 320 | } 321 | }, 322 | "node_modules/cookie-signature": { 323 | "version": "1.0.6", 324 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 325 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 326 | }, 327 | "node_modules/core-util-is": { 328 | "version": "1.0.3", 329 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 330 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 331 | "license": "MIT" 332 | }, 333 | "node_modules/cors": { 334 | "version": "2.8.5", 335 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 336 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 337 | "dependencies": { 338 | "object-assign": "^4", 339 | "vary": "^1" 340 | }, 341 | "engines": { 342 | "node": ">= 0.10" 343 | } 344 | }, 345 | "node_modules/cron-parser": { 346 | "version": "4.9.0", 347 | "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", 348 | "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", 349 | "license": "MIT", 350 | "dependencies": { 351 | "luxon": "^3.2.1" 352 | }, 353 | "engines": { 354 | "node": ">=12.0.0" 355 | } 356 | }, 357 | "node_modules/debug": { 358 | "version": "2.6.9", 359 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 360 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 361 | "dependencies": { 362 | "ms": "2.0.0" 363 | } 364 | }, 365 | "node_modules/delayed-stream": { 366 | "version": "1.0.0", 367 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 368 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 369 | "engines": { 370 | "node": ">=0.4.0" 371 | } 372 | }, 373 | "node_modules/depd": { 374 | "version": "2.0.0", 375 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 376 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 377 | "engines": { 378 | "node": ">= 0.8" 379 | } 380 | }, 381 | "node_modules/dotenv": { 382 | "version": "17.0.0", 383 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.0.0.tgz", 384 | "integrity": "sha512-A0BJ5lrpJVSfnMMXjmeO0xUnoxqsBHWCoqqTnGwGYVdnctqXXUEhJOO7LxmgxJon9tEZFGpe0xPRX0h2v3AANQ==", 385 | "engines": { 386 | "node": ">=12" 387 | }, 388 | "funding": { 389 | "url": "https://dotenvx.com" 390 | } 391 | }, 392 | "node_modules/dunder-proto": { 393 | "version": "1.0.1", 394 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 395 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 396 | "license": "MIT", 397 | "dependencies": { 398 | "call-bind-apply-helpers": "^1.0.1", 399 | "es-errors": "^1.3.0", 400 | "gopd": "^1.2.0" 401 | }, 402 | "engines": { 403 | "node": ">= 0.4" 404 | } 405 | }, 406 | "node_modules/ee-first": { 407 | "version": "1.1.1", 408 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 409 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 410 | }, 411 | "node_modules/encodeurl": { 412 | "version": "2.0.0", 413 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 414 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 415 | "license": "MIT", 416 | "engines": { 417 | "node": ">= 0.8" 418 | } 419 | }, 420 | "node_modules/es-define-property": { 421 | "version": "1.0.1", 422 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 423 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 424 | "license": "MIT", 425 | "engines": { 426 | "node": ">= 0.4" 427 | } 428 | }, 429 | "node_modules/es-errors": { 430 | "version": "1.3.0", 431 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 432 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 433 | "license": "MIT", 434 | "engines": { 435 | "node": ">= 0.4" 436 | } 437 | }, 438 | "node_modules/es-object-atoms": { 439 | "version": "1.1.1", 440 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 441 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 442 | "license": "MIT", 443 | "dependencies": { 444 | "es-errors": "^1.3.0" 445 | }, 446 | "engines": { 447 | "node": ">= 0.4" 448 | } 449 | }, 450 | "node_modules/escape-html": { 451 | "version": "1.0.3", 452 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 453 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 454 | "license": "MIT" 455 | }, 456 | "node_modules/etag": { 457 | "version": "1.8.1", 458 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 459 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 460 | "license": "MIT", 461 | "engines": { 462 | "node": ">= 0.6" 463 | } 464 | }, 465 | "node_modules/eventemitter3": { 466 | "version": "4.0.7", 467 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 468 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", 469 | "license": "MIT" 470 | }, 471 | "node_modules/express": { 472 | "version": "5.1.0", 473 | "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 474 | "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 475 | "license": "MIT", 476 | "dependencies": { 477 | "accepts": "^2.0.0", 478 | "body-parser": "^2.2.0", 479 | "content-disposition": "^1.0.0", 480 | "content-type": "^1.0.5", 481 | "cookie": "^0.7.1", 482 | "cookie-signature": "^1.2.1", 483 | "debug": "^4.4.0", 484 | "encodeurl": "^2.0.0", 485 | "escape-html": "^1.0.3", 486 | "etag": "^1.8.1", 487 | "finalhandler": "^2.1.0", 488 | "fresh": "^2.0.0", 489 | "http-errors": "^2.0.0", 490 | "merge-descriptors": "^2.0.0", 491 | "mime-types": "^3.0.0", 492 | "on-finished": "^2.4.1", 493 | "once": "^1.4.0", 494 | "parseurl": "^1.3.3", 495 | "proxy-addr": "^2.0.7", 496 | "qs": "^6.14.0", 497 | "range-parser": "^1.2.1", 498 | "router": "^2.2.0", 499 | "send": "^1.1.0", 500 | "serve-static": "^2.2.0", 501 | "statuses": "^2.0.1", 502 | "type-is": "^2.0.1", 503 | "vary": "^1.1.2" 504 | }, 505 | "engines": { 506 | "node": ">= 18" 507 | }, 508 | "funding": { 509 | "type": "opencollective", 510 | "url": "https://opencollective.com/express" 511 | } 512 | }, 513 | "node_modules/express/node_modules/cookie-signature": { 514 | "version": "1.2.2", 515 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 516 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 517 | "license": "MIT", 518 | "engines": { 519 | "node": ">=6.6.0" 520 | } 521 | }, 522 | "node_modules/express/node_modules/debug": { 523 | "version": "4.4.0", 524 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 525 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 526 | "license": "MIT", 527 | "dependencies": { 528 | "ms": "^2.1.3" 529 | }, 530 | "engines": { 531 | "node": ">=6.0" 532 | }, 533 | "peerDependenciesMeta": { 534 | "supports-color": { 535 | "optional": true 536 | } 537 | } 538 | }, 539 | "node_modules/express/node_modules/mime-db": { 540 | "version": "1.54.0", 541 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 542 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 543 | "license": "MIT", 544 | "engines": { 545 | "node": ">= 0.6" 546 | } 547 | }, 548 | "node_modules/express/node_modules/mime-types": { 549 | "version": "3.0.1", 550 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 551 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 552 | "license": "MIT", 553 | "dependencies": { 554 | "mime-db": "^1.54.0" 555 | }, 556 | "engines": { 557 | "node": ">= 0.6" 558 | } 559 | }, 560 | "node_modules/express/node_modules/ms": { 561 | "version": "2.1.3", 562 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 563 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 564 | "license": "MIT" 565 | }, 566 | "node_modules/fill-range": { 567 | "version": "7.1.1", 568 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 569 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 570 | "dev": true, 571 | "dependencies": { 572 | "to-regex-range": "^5.0.1" 573 | }, 574 | "engines": { 575 | "node": ">=8" 576 | } 577 | }, 578 | "node_modules/finalhandler": { 579 | "version": "2.1.0", 580 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 581 | "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 582 | "license": "MIT", 583 | "dependencies": { 584 | "debug": "^4.4.0", 585 | "encodeurl": "^2.0.0", 586 | "escape-html": "^1.0.3", 587 | "on-finished": "^2.4.1", 588 | "parseurl": "^1.3.3", 589 | "statuses": "^2.0.1" 590 | }, 591 | "engines": { 592 | "node": ">= 0.8" 593 | } 594 | }, 595 | "node_modules/finalhandler/node_modules/debug": { 596 | "version": "4.4.0", 597 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 598 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 599 | "license": "MIT", 600 | "dependencies": { 601 | "ms": "^2.1.3" 602 | }, 603 | "engines": { 604 | "node": ">=6.0" 605 | }, 606 | "peerDependenciesMeta": { 607 | "supports-color": { 608 | "optional": true 609 | } 610 | } 611 | }, 612 | "node_modules/finalhandler/node_modules/ms": { 613 | "version": "2.1.3", 614 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 615 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 616 | "license": "MIT" 617 | }, 618 | "node_modules/follow-redirects": { 619 | "version": "1.15.6", 620 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 621 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 622 | "funding": [ 623 | { 624 | "type": "individual", 625 | "url": "https://github.com/sponsors/RubenVerborgh" 626 | } 627 | ], 628 | "engines": { 629 | "node": ">=4.0" 630 | }, 631 | "peerDependenciesMeta": { 632 | "debug": { 633 | "optional": true 634 | } 635 | } 636 | }, 637 | "node_modules/form-data": { 638 | "version": "4.0.0", 639 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 640 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 641 | "dependencies": { 642 | "asynckit": "^0.4.0", 643 | "combined-stream": "^1.0.8", 644 | "mime-types": "^2.1.12" 645 | }, 646 | "engines": { 647 | "node": ">= 6" 648 | } 649 | }, 650 | "node_modules/forwarded": { 651 | "version": "0.2.0", 652 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 653 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 654 | "engines": { 655 | "node": ">= 0.6" 656 | } 657 | }, 658 | "node_modules/fresh": { 659 | "version": "2.0.0", 660 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 661 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 662 | "license": "MIT", 663 | "engines": { 664 | "node": ">= 0.8" 665 | } 666 | }, 667 | "node_modules/fsevents": { 668 | "version": "2.3.3", 669 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 670 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 671 | "dev": true, 672 | "hasInstallScript": true, 673 | "optional": true, 674 | "os": [ 675 | "darwin" 676 | ], 677 | "engines": { 678 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 679 | } 680 | }, 681 | "node_modules/function-bind": { 682 | "version": "1.1.2", 683 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 684 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 685 | "license": "MIT", 686 | "funding": { 687 | "url": "https://github.com/sponsors/ljharb" 688 | } 689 | }, 690 | "node_modules/get-intrinsic": { 691 | "version": "1.3.0", 692 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 693 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 694 | "license": "MIT", 695 | "dependencies": { 696 | "call-bind-apply-helpers": "^1.0.2", 697 | "es-define-property": "^1.0.1", 698 | "es-errors": "^1.3.0", 699 | "es-object-atoms": "^1.1.1", 700 | "function-bind": "^1.1.2", 701 | "get-proto": "^1.0.1", 702 | "gopd": "^1.2.0", 703 | "has-symbols": "^1.1.0", 704 | "hasown": "^2.0.2", 705 | "math-intrinsics": "^1.1.0" 706 | }, 707 | "engines": { 708 | "node": ">= 0.4" 709 | }, 710 | "funding": { 711 | "url": "https://github.com/sponsors/ljharb" 712 | } 713 | }, 714 | "node_modules/get-proto": { 715 | "version": "1.0.1", 716 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 717 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 718 | "license": "MIT", 719 | "dependencies": { 720 | "dunder-proto": "^1.0.1", 721 | "es-object-atoms": "^1.0.0" 722 | }, 723 | "engines": { 724 | "node": ">= 0.4" 725 | } 726 | }, 727 | "node_modules/glob-parent": { 728 | "version": "5.1.2", 729 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 730 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 731 | "dev": true, 732 | "dependencies": { 733 | "is-glob": "^4.0.1" 734 | }, 735 | "engines": { 736 | "node": ">= 6" 737 | } 738 | }, 739 | "node_modules/gopd": { 740 | "version": "1.2.0", 741 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 742 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 743 | "license": "MIT", 744 | "engines": { 745 | "node": ">= 0.4" 746 | }, 747 | "funding": { 748 | "url": "https://github.com/sponsors/ljharb" 749 | } 750 | }, 751 | "node_modules/has-flag": { 752 | "version": "3.0.0", 753 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 754 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 755 | "dev": true, 756 | "engines": { 757 | "node": ">=4" 758 | } 759 | }, 760 | "node_modules/has-symbols": { 761 | "version": "1.1.0", 762 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 763 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 764 | "license": "MIT", 765 | "engines": { 766 | "node": ">= 0.4" 767 | }, 768 | "funding": { 769 | "url": "https://github.com/sponsors/ljharb" 770 | } 771 | }, 772 | "node_modules/hasown": { 773 | "version": "2.0.2", 774 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 775 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 776 | "license": "MIT", 777 | "dependencies": { 778 | "function-bind": "^1.1.2" 779 | }, 780 | "engines": { 781 | "node": ">= 0.4" 782 | } 783 | }, 784 | "node_modules/http-errors": { 785 | "version": "2.0.0", 786 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 787 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 788 | "license": "MIT", 789 | "dependencies": { 790 | "depd": "2.0.0", 791 | "inherits": "2.0.4", 792 | "setprototypeof": "1.2.0", 793 | "statuses": "2.0.1", 794 | "toidentifier": "1.0.1" 795 | }, 796 | "engines": { 797 | "node": ">= 0.8" 798 | } 799 | }, 800 | "node_modules/http-proxy": { 801 | "version": "1.18.1", 802 | "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", 803 | "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", 804 | "license": "MIT", 805 | "dependencies": { 806 | "eventemitter3": "^4.0.0", 807 | "follow-redirects": "^1.0.0", 808 | "requires-port": "^1.0.0" 809 | }, 810 | "engines": { 811 | "node": ">=8.0.0" 812 | } 813 | }, 814 | "node_modules/iconv-lite": { 815 | "version": "0.6.3", 816 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 817 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 818 | "license": "MIT", 819 | "dependencies": { 820 | "safer-buffer": ">= 2.1.2 < 3.0.0" 821 | }, 822 | "engines": { 823 | "node": ">=0.10.0" 824 | } 825 | }, 826 | "node_modules/ignore-by-default": { 827 | "version": "1.0.1", 828 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 829 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", 830 | "dev": true 831 | }, 832 | "node_modules/immediate": { 833 | "version": "3.0.6", 834 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", 835 | "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", 836 | "license": "MIT" 837 | }, 838 | "node_modules/inherits": { 839 | "version": "2.0.4", 840 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 841 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 842 | }, 843 | "node_modules/ipaddr.js": { 844 | "version": "1.9.1", 845 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 846 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 847 | "engines": { 848 | "node": ">= 0.10" 849 | } 850 | }, 851 | "node_modules/is-binary-path": { 852 | "version": "2.1.0", 853 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 854 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 855 | "dev": true, 856 | "dependencies": { 857 | "binary-extensions": "^2.0.0" 858 | }, 859 | "engines": { 860 | "node": ">=8" 861 | } 862 | }, 863 | "node_modules/is-extglob": { 864 | "version": "2.1.1", 865 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 866 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 867 | "dev": true, 868 | "engines": { 869 | "node": ">=0.10.0" 870 | } 871 | }, 872 | "node_modules/is-glob": { 873 | "version": "4.0.3", 874 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 875 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 876 | "dev": true, 877 | "dependencies": { 878 | "is-extglob": "^2.1.1" 879 | }, 880 | "engines": { 881 | "node": ">=0.10.0" 882 | } 883 | }, 884 | "node_modules/is-number": { 885 | "version": "7.0.0", 886 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 887 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 888 | "dev": true, 889 | "engines": { 890 | "node": ">=0.12.0" 891 | } 892 | }, 893 | "node_modules/is-promise": { 894 | "version": "4.0.0", 895 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 896 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 897 | "license": "MIT" 898 | }, 899 | "node_modules/isarray": { 900 | "version": "1.0.0", 901 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 902 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", 903 | "license": "MIT" 904 | }, 905 | "node_modules/jszip": { 906 | "version": "3.10.1", 907 | "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", 908 | "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", 909 | "license": "(MIT OR GPL-3.0-or-later)", 910 | "dependencies": { 911 | "lie": "~3.3.0", 912 | "pako": "~1.0.2", 913 | "readable-stream": "~2.3.6", 914 | "setimmediate": "^1.0.5" 915 | } 916 | }, 917 | "node_modules/lie": { 918 | "version": "3.3.0", 919 | "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", 920 | "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", 921 | "license": "MIT", 922 | "dependencies": { 923 | "immediate": "~3.0.5" 924 | } 925 | }, 926 | "node_modules/long-timeout": { 927 | "version": "0.1.1", 928 | "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", 929 | "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==", 930 | "license": "MIT" 931 | }, 932 | "node_modules/lru-cache": { 933 | "version": "6.0.0", 934 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 935 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 936 | "dev": true, 937 | "dependencies": { 938 | "yallist": "^4.0.0" 939 | }, 940 | "engines": { 941 | "node": ">=10" 942 | } 943 | }, 944 | "node_modules/luxon": { 945 | "version": "3.5.0", 946 | "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", 947 | "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", 948 | "license": "MIT", 949 | "engines": { 950 | "node": ">=12" 951 | } 952 | }, 953 | "node_modules/math-intrinsics": { 954 | "version": "1.1.0", 955 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 956 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 957 | "license": "MIT", 958 | "engines": { 959 | "node": ">= 0.4" 960 | } 961 | }, 962 | "node_modules/media-typer": { 963 | "version": "1.1.0", 964 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 965 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 966 | "license": "MIT", 967 | "engines": { 968 | "node": ">= 0.8" 969 | } 970 | }, 971 | "node_modules/merge-descriptors": { 972 | "version": "2.0.0", 973 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 974 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 975 | "license": "MIT", 976 | "engines": { 977 | "node": ">=18" 978 | }, 979 | "funding": { 980 | "url": "https://github.com/sponsors/sindresorhus" 981 | } 982 | }, 983 | "node_modules/mime-db": { 984 | "version": "1.52.0", 985 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 986 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 987 | "engines": { 988 | "node": ">= 0.6" 989 | } 990 | }, 991 | "node_modules/mime-types": { 992 | "version": "2.1.35", 993 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 994 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 995 | "dependencies": { 996 | "mime-db": "1.52.0" 997 | }, 998 | "engines": { 999 | "node": ">= 0.6" 1000 | } 1001 | }, 1002 | "node_modules/minimatch": { 1003 | "version": "3.1.2", 1004 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1005 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1006 | "dev": true, 1007 | "dependencies": { 1008 | "brace-expansion": "^1.1.7" 1009 | }, 1010 | "engines": { 1011 | "node": "*" 1012 | } 1013 | }, 1014 | "node_modules/morgan": { 1015 | "version": "1.10.0", 1016 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", 1017 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", 1018 | "dependencies": { 1019 | "basic-auth": "~2.0.1", 1020 | "debug": "2.6.9", 1021 | "depd": "~2.0.0", 1022 | "on-finished": "~2.3.0", 1023 | "on-headers": "~1.0.2" 1024 | }, 1025 | "engines": { 1026 | "node": ">= 0.8.0" 1027 | } 1028 | }, 1029 | "node_modules/morgan/node_modules/on-finished": { 1030 | "version": "2.3.0", 1031 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1032 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", 1033 | "dependencies": { 1034 | "ee-first": "1.1.1" 1035 | }, 1036 | "engines": { 1037 | "node": ">= 0.8" 1038 | } 1039 | }, 1040 | "node_modules/ms": { 1041 | "version": "2.0.0", 1042 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1043 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1044 | }, 1045 | "node_modules/negotiator": { 1046 | "version": "1.0.0", 1047 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 1048 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 1049 | "license": "MIT", 1050 | "engines": { 1051 | "node": ">= 0.6" 1052 | } 1053 | }, 1054 | "node_modules/node-schedule": { 1055 | "version": "2.1.1", 1056 | "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz", 1057 | "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==", 1058 | "license": "MIT", 1059 | "dependencies": { 1060 | "cron-parser": "^4.2.0", 1061 | "long-timeout": "0.1.1", 1062 | "sorted-array-functions": "^1.3.0" 1063 | }, 1064 | "engines": { 1065 | "node": ">=6" 1066 | } 1067 | }, 1068 | "node_modules/nodemon": { 1069 | "version": "3.1.10", 1070 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", 1071 | "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", 1072 | "dev": true, 1073 | "license": "MIT", 1074 | "dependencies": { 1075 | "chokidar": "^3.5.2", 1076 | "debug": "^4", 1077 | "ignore-by-default": "^1.0.1", 1078 | "minimatch": "^3.1.2", 1079 | "pstree.remy": "^1.1.8", 1080 | "semver": "^7.5.3", 1081 | "simple-update-notifier": "^2.0.0", 1082 | "supports-color": "^5.5.0", 1083 | "touch": "^3.1.0", 1084 | "undefsafe": "^2.0.5" 1085 | }, 1086 | "bin": { 1087 | "nodemon": "bin/nodemon.js" 1088 | }, 1089 | "engines": { 1090 | "node": ">=10" 1091 | }, 1092 | "funding": { 1093 | "type": "opencollective", 1094 | "url": "https://opencollective.com/nodemon" 1095 | } 1096 | }, 1097 | "node_modules/nodemon/node_modules/debug": { 1098 | "version": "4.3.4", 1099 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 1100 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 1101 | "dev": true, 1102 | "dependencies": { 1103 | "ms": "2.1.2" 1104 | }, 1105 | "engines": { 1106 | "node": ">=6.0" 1107 | }, 1108 | "peerDependenciesMeta": { 1109 | "supports-color": { 1110 | "optional": true 1111 | } 1112 | } 1113 | }, 1114 | "node_modules/nodemon/node_modules/ms": { 1115 | "version": "2.1.2", 1116 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1117 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1118 | "dev": true 1119 | }, 1120 | "node_modules/nopt": { 1121 | "version": "1.0.10", 1122 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 1123 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 1124 | "dev": true, 1125 | "dependencies": { 1126 | "abbrev": "1" 1127 | }, 1128 | "bin": { 1129 | "nopt": "bin/nopt.js" 1130 | }, 1131 | "engines": { 1132 | "node": "*" 1133 | } 1134 | }, 1135 | "node_modules/normalize-path": { 1136 | "version": "3.0.0", 1137 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1138 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1139 | "dev": true, 1140 | "engines": { 1141 | "node": ">=0.10.0" 1142 | } 1143 | }, 1144 | "node_modules/object-assign": { 1145 | "version": "4.1.1", 1146 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1147 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 1148 | "engines": { 1149 | "node": ">=0.10.0" 1150 | } 1151 | }, 1152 | "node_modules/object-inspect": { 1153 | "version": "1.13.4", 1154 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 1155 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 1156 | "license": "MIT", 1157 | "engines": { 1158 | "node": ">= 0.4" 1159 | }, 1160 | "funding": { 1161 | "url": "https://github.com/sponsors/ljharb" 1162 | } 1163 | }, 1164 | "node_modules/on-finished": { 1165 | "version": "2.4.1", 1166 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1167 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1168 | "license": "MIT", 1169 | "dependencies": { 1170 | "ee-first": "1.1.1" 1171 | }, 1172 | "engines": { 1173 | "node": ">= 0.8" 1174 | } 1175 | }, 1176 | "node_modules/on-headers": { 1177 | "version": "1.0.2", 1178 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 1179 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", 1180 | "engines": { 1181 | "node": ">= 0.8" 1182 | } 1183 | }, 1184 | "node_modules/once": { 1185 | "version": "1.4.0", 1186 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1187 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1188 | "license": "ISC", 1189 | "dependencies": { 1190 | "wrappy": "1" 1191 | } 1192 | }, 1193 | "node_modules/pako": { 1194 | "version": "1.0.11", 1195 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 1196 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", 1197 | "license": "(MIT AND Zlib)" 1198 | }, 1199 | "node_modules/parseurl": { 1200 | "version": "1.3.3", 1201 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1202 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1203 | "license": "MIT", 1204 | "engines": { 1205 | "node": ">= 0.8" 1206 | } 1207 | }, 1208 | "node_modules/path-to-regexp": { 1209 | "version": "8.2.0", 1210 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 1211 | "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 1212 | "license": "MIT", 1213 | "engines": { 1214 | "node": ">=16" 1215 | } 1216 | }, 1217 | "node_modules/picomatch": { 1218 | "version": "2.3.1", 1219 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1220 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1221 | "dev": true, 1222 | "engines": { 1223 | "node": ">=8.6" 1224 | }, 1225 | "funding": { 1226 | "url": "https://github.com/sponsors/jonschlinkert" 1227 | } 1228 | }, 1229 | "node_modules/process-nextick-args": { 1230 | "version": "2.0.1", 1231 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1232 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 1233 | "license": "MIT" 1234 | }, 1235 | "node_modules/proxy-addr": { 1236 | "version": "2.0.7", 1237 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1238 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1239 | "dependencies": { 1240 | "forwarded": "0.2.0", 1241 | "ipaddr.js": "1.9.1" 1242 | }, 1243 | "engines": { 1244 | "node": ">= 0.10" 1245 | } 1246 | }, 1247 | "node_modules/proxy-from-env": { 1248 | "version": "1.1.0", 1249 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1250 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 1251 | }, 1252 | "node_modules/pstree.remy": { 1253 | "version": "1.1.8", 1254 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 1255 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", 1256 | "dev": true 1257 | }, 1258 | "node_modules/qs": { 1259 | "version": "6.14.0", 1260 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 1261 | "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 1262 | "license": "BSD-3-Clause", 1263 | "dependencies": { 1264 | "side-channel": "^1.1.0" 1265 | }, 1266 | "engines": { 1267 | "node": ">=0.6" 1268 | }, 1269 | "funding": { 1270 | "url": "https://github.com/sponsors/ljharb" 1271 | } 1272 | }, 1273 | "node_modules/range-parser": { 1274 | "version": "1.2.1", 1275 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1276 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1277 | "license": "MIT", 1278 | "engines": { 1279 | "node": ">= 0.6" 1280 | } 1281 | }, 1282 | "node_modules/raw-body": { 1283 | "version": "3.0.0", 1284 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 1285 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 1286 | "license": "MIT", 1287 | "dependencies": { 1288 | "bytes": "3.1.2", 1289 | "http-errors": "2.0.0", 1290 | "iconv-lite": "0.6.3", 1291 | "unpipe": "1.0.0" 1292 | }, 1293 | "engines": { 1294 | "node": ">= 0.8" 1295 | } 1296 | }, 1297 | "node_modules/readable-stream": { 1298 | "version": "2.3.8", 1299 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 1300 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 1301 | "license": "MIT", 1302 | "dependencies": { 1303 | "core-util-is": "~1.0.0", 1304 | "inherits": "~2.0.3", 1305 | "isarray": "~1.0.0", 1306 | "process-nextick-args": "~2.0.0", 1307 | "safe-buffer": "~5.1.1", 1308 | "string_decoder": "~1.1.1", 1309 | "util-deprecate": "~1.0.1" 1310 | } 1311 | }, 1312 | "node_modules/readable-stream/node_modules/safe-buffer": { 1313 | "version": "5.1.2", 1314 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1315 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1316 | "license": "MIT" 1317 | }, 1318 | "node_modules/readdirp": { 1319 | "version": "3.6.0", 1320 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1321 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1322 | "dev": true, 1323 | "dependencies": { 1324 | "picomatch": "^2.2.1" 1325 | }, 1326 | "engines": { 1327 | "node": ">=8.10.0" 1328 | } 1329 | }, 1330 | "node_modules/requires-port": { 1331 | "version": "1.0.0", 1332 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 1333 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", 1334 | "license": "MIT" 1335 | }, 1336 | "node_modules/router": { 1337 | "version": "2.2.0", 1338 | "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 1339 | "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 1340 | "license": "MIT", 1341 | "dependencies": { 1342 | "debug": "^4.4.0", 1343 | "depd": "^2.0.0", 1344 | "is-promise": "^4.0.0", 1345 | "parseurl": "^1.3.3", 1346 | "path-to-regexp": "^8.0.0" 1347 | }, 1348 | "engines": { 1349 | "node": ">= 18" 1350 | } 1351 | }, 1352 | "node_modules/router/node_modules/debug": { 1353 | "version": "4.4.0", 1354 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 1355 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 1356 | "license": "MIT", 1357 | "dependencies": { 1358 | "ms": "^2.1.3" 1359 | }, 1360 | "engines": { 1361 | "node": ">=6.0" 1362 | }, 1363 | "peerDependenciesMeta": { 1364 | "supports-color": { 1365 | "optional": true 1366 | } 1367 | } 1368 | }, 1369 | "node_modules/router/node_modules/ms": { 1370 | "version": "2.1.3", 1371 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1372 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1373 | "license": "MIT" 1374 | }, 1375 | "node_modules/safe-buffer": { 1376 | "version": "5.2.1", 1377 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1378 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1379 | "funding": [ 1380 | { 1381 | "type": "github", 1382 | "url": "https://github.com/sponsors/feross" 1383 | }, 1384 | { 1385 | "type": "patreon", 1386 | "url": "https://www.patreon.com/feross" 1387 | }, 1388 | { 1389 | "type": "consulting", 1390 | "url": "https://feross.org/support" 1391 | } 1392 | ], 1393 | "license": "MIT" 1394 | }, 1395 | "node_modules/safer-buffer": { 1396 | "version": "2.1.2", 1397 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1398 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1399 | "license": "MIT" 1400 | }, 1401 | "node_modules/semver": { 1402 | "version": "7.6.0", 1403 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", 1404 | "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", 1405 | "dev": true, 1406 | "dependencies": { 1407 | "lru-cache": "^6.0.0" 1408 | }, 1409 | "bin": { 1410 | "semver": "bin/semver.js" 1411 | }, 1412 | "engines": { 1413 | "node": ">=10" 1414 | } 1415 | }, 1416 | "node_modules/send": { 1417 | "version": "1.2.0", 1418 | "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 1419 | "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 1420 | "license": "MIT", 1421 | "dependencies": { 1422 | "debug": "^4.3.5", 1423 | "encodeurl": "^2.0.0", 1424 | "escape-html": "^1.0.3", 1425 | "etag": "^1.8.1", 1426 | "fresh": "^2.0.0", 1427 | "http-errors": "^2.0.0", 1428 | "mime-types": "^3.0.1", 1429 | "ms": "^2.1.3", 1430 | "on-finished": "^2.4.1", 1431 | "range-parser": "^1.2.1", 1432 | "statuses": "^2.0.1" 1433 | }, 1434 | "engines": { 1435 | "node": ">= 18" 1436 | } 1437 | }, 1438 | "node_modules/send/node_modules/debug": { 1439 | "version": "4.4.0", 1440 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 1441 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 1442 | "license": "MIT", 1443 | "dependencies": { 1444 | "ms": "^2.1.3" 1445 | }, 1446 | "engines": { 1447 | "node": ">=6.0" 1448 | }, 1449 | "peerDependenciesMeta": { 1450 | "supports-color": { 1451 | "optional": true 1452 | } 1453 | } 1454 | }, 1455 | "node_modules/send/node_modules/mime-db": { 1456 | "version": "1.54.0", 1457 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 1458 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 1459 | "license": "MIT", 1460 | "engines": { 1461 | "node": ">= 0.6" 1462 | } 1463 | }, 1464 | "node_modules/send/node_modules/mime-types": { 1465 | "version": "3.0.1", 1466 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 1467 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 1468 | "license": "MIT", 1469 | "dependencies": { 1470 | "mime-db": "^1.54.0" 1471 | }, 1472 | "engines": { 1473 | "node": ">= 0.6" 1474 | } 1475 | }, 1476 | "node_modules/send/node_modules/ms": { 1477 | "version": "2.1.3", 1478 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1479 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1480 | "license": "MIT" 1481 | }, 1482 | "node_modules/serve-static": { 1483 | "version": "2.2.0", 1484 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 1485 | "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 1486 | "license": "MIT", 1487 | "dependencies": { 1488 | "encodeurl": "^2.0.0", 1489 | "escape-html": "^1.0.3", 1490 | "parseurl": "^1.3.3", 1491 | "send": "^1.2.0" 1492 | }, 1493 | "engines": { 1494 | "node": ">= 18" 1495 | } 1496 | }, 1497 | "node_modules/setimmediate": { 1498 | "version": "1.0.5", 1499 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 1500 | "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", 1501 | "license": "MIT" 1502 | }, 1503 | "node_modules/setprototypeof": { 1504 | "version": "1.2.0", 1505 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1506 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 1507 | "license": "ISC" 1508 | }, 1509 | "node_modules/side-channel": { 1510 | "version": "1.1.0", 1511 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1512 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1513 | "license": "MIT", 1514 | "dependencies": { 1515 | "es-errors": "^1.3.0", 1516 | "object-inspect": "^1.13.3", 1517 | "side-channel-list": "^1.0.0", 1518 | "side-channel-map": "^1.0.1", 1519 | "side-channel-weakmap": "^1.0.2" 1520 | }, 1521 | "engines": { 1522 | "node": ">= 0.4" 1523 | }, 1524 | "funding": { 1525 | "url": "https://github.com/sponsors/ljharb" 1526 | } 1527 | }, 1528 | "node_modules/side-channel-list": { 1529 | "version": "1.0.0", 1530 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1531 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1532 | "license": "MIT", 1533 | "dependencies": { 1534 | "es-errors": "^1.3.0", 1535 | "object-inspect": "^1.13.3" 1536 | }, 1537 | "engines": { 1538 | "node": ">= 0.4" 1539 | }, 1540 | "funding": { 1541 | "url": "https://github.com/sponsors/ljharb" 1542 | } 1543 | }, 1544 | "node_modules/side-channel-map": { 1545 | "version": "1.0.1", 1546 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1547 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1548 | "license": "MIT", 1549 | "dependencies": { 1550 | "call-bound": "^1.0.2", 1551 | "es-errors": "^1.3.0", 1552 | "get-intrinsic": "^1.2.5", 1553 | "object-inspect": "^1.13.3" 1554 | }, 1555 | "engines": { 1556 | "node": ">= 0.4" 1557 | }, 1558 | "funding": { 1559 | "url": "https://github.com/sponsors/ljharb" 1560 | } 1561 | }, 1562 | "node_modules/side-channel-weakmap": { 1563 | "version": "1.0.2", 1564 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1565 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1566 | "license": "MIT", 1567 | "dependencies": { 1568 | "call-bound": "^1.0.2", 1569 | "es-errors": "^1.3.0", 1570 | "get-intrinsic": "^1.2.5", 1571 | "object-inspect": "^1.13.3", 1572 | "side-channel-map": "^1.0.1" 1573 | }, 1574 | "engines": { 1575 | "node": ">= 0.4" 1576 | }, 1577 | "funding": { 1578 | "url": "https://github.com/sponsors/ljharb" 1579 | } 1580 | }, 1581 | "node_modules/simple-update-notifier": { 1582 | "version": "2.0.0", 1583 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 1584 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 1585 | "dev": true, 1586 | "dependencies": { 1587 | "semver": "^7.5.3" 1588 | }, 1589 | "engines": { 1590 | "node": ">=10" 1591 | } 1592 | }, 1593 | "node_modules/sorted-array-functions": { 1594 | "version": "1.3.0", 1595 | "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", 1596 | "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==", 1597 | "license": "MIT" 1598 | }, 1599 | "node_modules/statuses": { 1600 | "version": "2.0.1", 1601 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1602 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1603 | "license": "MIT", 1604 | "engines": { 1605 | "node": ">= 0.8" 1606 | } 1607 | }, 1608 | "node_modules/string_decoder": { 1609 | "version": "1.1.1", 1610 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1611 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1612 | "license": "MIT", 1613 | "dependencies": { 1614 | "safe-buffer": "~5.1.0" 1615 | } 1616 | }, 1617 | "node_modules/string_decoder/node_modules/safe-buffer": { 1618 | "version": "5.1.2", 1619 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1620 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1621 | "license": "MIT" 1622 | }, 1623 | "node_modules/supports-color": { 1624 | "version": "5.5.0", 1625 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1626 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1627 | "dev": true, 1628 | "dependencies": { 1629 | "has-flag": "^3.0.0" 1630 | }, 1631 | "engines": { 1632 | "node": ">=4" 1633 | } 1634 | }, 1635 | "node_modules/to-regex-range": { 1636 | "version": "5.0.1", 1637 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1638 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1639 | "dev": true, 1640 | "dependencies": { 1641 | "is-number": "^7.0.0" 1642 | }, 1643 | "engines": { 1644 | "node": ">=8.0" 1645 | } 1646 | }, 1647 | "node_modules/toidentifier": { 1648 | "version": "1.0.1", 1649 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1650 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1651 | "license": "MIT", 1652 | "engines": { 1653 | "node": ">=0.6" 1654 | } 1655 | }, 1656 | "node_modules/touch": { 1657 | "version": "3.1.0", 1658 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 1659 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 1660 | "dev": true, 1661 | "dependencies": { 1662 | "nopt": "~1.0.10" 1663 | }, 1664 | "bin": { 1665 | "nodetouch": "bin/nodetouch.js" 1666 | } 1667 | }, 1668 | "node_modules/type-is": { 1669 | "version": "2.0.1", 1670 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 1671 | "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 1672 | "license": "MIT", 1673 | "dependencies": { 1674 | "content-type": "^1.0.5", 1675 | "media-typer": "^1.1.0", 1676 | "mime-types": "^3.0.0" 1677 | }, 1678 | "engines": { 1679 | "node": ">= 0.6" 1680 | } 1681 | }, 1682 | "node_modules/type-is/node_modules/mime-db": { 1683 | "version": "1.54.0", 1684 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 1685 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 1686 | "license": "MIT", 1687 | "engines": { 1688 | "node": ">= 0.6" 1689 | } 1690 | }, 1691 | "node_modules/type-is/node_modules/mime-types": { 1692 | "version": "3.0.1", 1693 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 1694 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 1695 | "license": "MIT", 1696 | "dependencies": { 1697 | "mime-db": "^1.54.0" 1698 | }, 1699 | "engines": { 1700 | "node": ">= 0.6" 1701 | } 1702 | }, 1703 | "node_modules/undefsafe": { 1704 | "version": "2.0.5", 1705 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 1706 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", 1707 | "dev": true 1708 | }, 1709 | "node_modules/unpipe": { 1710 | "version": "1.0.0", 1711 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1712 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1713 | "license": "MIT", 1714 | "engines": { 1715 | "node": ">= 0.8" 1716 | } 1717 | }, 1718 | "node_modules/util-deprecate": { 1719 | "version": "1.0.2", 1720 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1721 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 1722 | "license": "MIT" 1723 | }, 1724 | "node_modules/vary": { 1725 | "version": "1.1.2", 1726 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1727 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1728 | "engines": { 1729 | "node": ">= 0.8" 1730 | } 1731 | }, 1732 | "node_modules/wrappy": { 1733 | "version": "1.0.2", 1734 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1735 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1736 | "license": "ISC" 1737 | }, 1738 | "node_modules/yallist": { 1739 | "version": "4.0.0", 1740 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1741 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1742 | "dev": true 1743 | } 1744 | } 1745 | } 1746 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuetorrent-backend", 3 | "version": "2.5.1", 4 | "description": "Node.JS backend for VueTorrent", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "dev": "nodemon -w .env -w src/ src/index.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+ssh://git@github.com/VueTorrent/vuetorrent-backend.git" 14 | }, 15 | "author": "Larsluph", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/VueTorrent/vuetorrent-backend/issues" 19 | }, 20 | "homepage": "https://github.com/VueTorrent/vuetorrent-backend#readme", 21 | "type": "module", 22 | "dependencies": { 23 | "axios": "^1.10.0", 24 | "cookie-parser": "^1.4.7", 25 | "cors": "^2.8.5", 26 | "dotenv": "^17.0.0", 27 | "express": "^5.1.0", 28 | "http-proxy": "^1.18.1", 29 | "jszip": "^3.10.1", 30 | "morgan": "^1.10.0", 31 | "node-schedule": "^2.1.1" 32 | }, 33 | "devDependencies": { 34 | "nodemon": "^3.1.10" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import cookieParser from 'cookie-parser' 2 | import cors from 'cors' 3 | import { config } from 'dotenv' 4 | import express, { Router } from 'express' 5 | import https from 'https' 6 | import morgan from 'morgan' 7 | import scheduler from 'node-schedule' 8 | import authMiddleware from './middlewares/auth.js' 9 | import configRouter from './routers/config/index.js' 10 | import qbitRouter from './routers/qbit/index.js' 11 | import { checkForUpdate } from './routines/update.js' 12 | import { getSSLOptionsFromEnv } from './ssl.js' 13 | import { assert_env } from './utils.js' 14 | 15 | config() 16 | assert_env() 17 | 18 | const app = express() 19 | const router = Router() 20 | 21 | const PORT = process.env.PORT || 3000 22 | 23 | // Middlewares 24 | app.use(morgan('tiny')) 25 | app.use(cors({ origin: true, credentials: true })) 26 | app.use(express.json()) 27 | app.use(cookieParser()) 28 | 29 | // Routers 30 | app.use('/api', qbitRouter) 31 | router.use('/config', authMiddleware, configRouter) 32 | 33 | // Routes 34 | router.get('/ping', async (req, res) => { 35 | res.send('pong') 36 | }) 37 | router.get('/update', authMiddleware, async (req, res) => { 38 | try { 39 | res.send(await checkForUpdate()) 40 | } catch (err) { 41 | console.error(err) 42 | res.status(500).send('Failed to update') 43 | } 44 | }) 45 | 46 | app.use('/backend', router) 47 | 48 | // WebUI 49 | app.use(express.static(`${process.env.VUETORRENT_PATH || '/vuetorrent'}/vuetorrent/public`)) 50 | app.use(async (req, res) => { 51 | res.status(404).send('404 Not Found') 52 | }) 53 | 54 | function launchServer(serverInstance, protocol) { 55 | serverInstance.listen(PORT, async () => { 56 | console.log('Checking for updates...') 57 | console.log(await checkForUpdate()) 58 | console.log(`${protocol} server is running on port ${PORT}`) 59 | const update_cron = process.env.UPDATE_VT_CRON 60 | if (update_cron && update_cron.length) { 61 | scheduler.scheduleJob( 62 | 'Update VueTorrent', 63 | update_cron, 64 | async () => { 65 | return await checkForUpdate() 66 | } 67 | ); 68 | } 69 | }) 70 | } 71 | 72 | const sslOptions = getSSLOptionsFromEnv() 73 | let server 74 | if (sslOptions) { 75 | const httpsServer = https.createServer(sslOptions, app) 76 | server = httpsServer 77 | launchServer(httpsServer, 'HTTPS') 78 | } else { 79 | server = app 80 | launchServer(app, 'HTTP') 81 | } 82 | 83 | async function stopServer(signal) { 84 | console.log(`Received ${ signal } signal. Gracefully shutting down...`) 85 | server.close() 86 | await scheduler.gracefulShutdown() 87 | } 88 | 89 | process.on('SIGTERM', stopServer) 90 | process.on('SIGINT', stopServer) 91 | -------------------------------------------------------------------------------- /src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default async function authMiddleware(req, res, next) { 4 | if (req.path === '/ping') { 5 | return next() 6 | } 7 | const { cookies } = req 8 | const { SID } = cookies 9 | 10 | if (await isValidSID(SID)) { 11 | next() 12 | } else { 13 | res.status(401).json({ error: 'Unauthorized' }) 14 | } 15 | } 16 | 17 | async function isValidSID(SID) { 18 | const headers = {} 19 | if (SID) { 20 | headers['Cookie'] = `SID=${ SID }` 21 | } 22 | 23 | try { 24 | const r = await axios.get(`${ process.env.QBIT_BASE }/api/v2/app/version`, { headers }) 25 | return r.status === 200 26 | } catch (error) { 27 | console.error('Error occurred while fetching version:') 28 | if (error.name === 'AggregateError') { 29 | console.error(error.errors.map(err => err.message).join('\n')) 30 | } else if (error.response) { 31 | console.error(`[${error.response.status}] ${error.response.data}`) 32 | } else { 33 | console.error(error.name, error.message) 34 | } 35 | 36 | return false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/routers/config/fs.js: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import path from 'path' 3 | 4 | const queue = [] 5 | 6 | function getDataFilePath() { 7 | return path.join(process.env.CONFIG_PATH || '/config', 'data.json') 8 | } 9 | 10 | async function processQueue() { 11 | if (queue.length === 0) return 12 | 13 | const { data, resolve, reject } = queue[0] 14 | 15 | try { 16 | await fs.writeFile(getDataFilePath(), JSON.stringify(data, null, 2)) 17 | resolve() 18 | } catch (err) { 19 | reject(err) 20 | } finally { 21 | queue.shift() 22 | await processQueue() 23 | } 24 | } 25 | 26 | export async function getData() { 27 | const canAccess = await fs.access(getDataFilePath(), fs.constants.F_OK) 28 | .then(() => true, () => false) 29 | 30 | if (!canAccess) { 31 | // File doesn't exists 32 | return {} 33 | } 34 | 35 | const canEdit = await fs.access(getDataFilePath(), fs.constants.R_OK | fs.constants.W_OK) 36 | .then(() => true, () => false) 37 | 38 | if (!canEdit) { 39 | // File not readable / writable 40 | throw new Error('Data file isn\'t readable / writable') 41 | } 42 | 43 | return JSON.parse(await fs.readFile(getDataFilePath(), 'utf8')) 44 | } 45 | 46 | export async function setData(data) { 47 | return new Promise((resolve, reject) => { 48 | queue.push({ data, resolve, reject }) 49 | if (queue.length === 1) { 50 | processQueue() 51 | } 52 | }) 53 | } -------------------------------------------------------------------------------- /src/routers/config/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { getData, setData } from './fs.js' 3 | 4 | const router = Router() 5 | 6 | router.get('/', async (req, res) => { 7 | try { 8 | const config = await getData() 9 | res.json(config) 10 | } catch (err) { 11 | res.status(500).json({ error: err.message }) 12 | } 13 | }) 14 | 15 | router.get('/:key', async (req, res) => { 16 | const { key } = req.params 17 | try { 18 | const config = await getData() 19 | if (Object.hasOwn(config, key)) { 20 | res.json({ [key]: config[key] }) 21 | } else { 22 | res.status(404).json({ error: 'Key not found' }) 23 | } 24 | } catch (err) { 25 | res.status(500).json({ error: err.message }) 26 | } 27 | }) 28 | 29 | router.put('/:key', async (req, res) => { 30 | const { key } = req.params 31 | const { value } = req.body 32 | try { 33 | const config = await getData() 34 | config[key] = value 35 | await setData(config) 36 | res.json({ message: 'Config updated successfully' }) 37 | } catch (err) { 38 | res.status(500).json({ error: err.message }) 39 | } 40 | }) 41 | 42 | router.delete('/:key', async (req, res) => { 43 | const { key } = req.params 44 | try { 45 | const config = await getData() 46 | if (Object.hasOwn(config, key)) { 47 | delete config[key] 48 | await setData(config) 49 | res.json({ message: 'Config deleted successfully' }) 50 | } else { 51 | res.status(404).json({ error: 'Key not found' }) 52 | } 53 | } catch (err) { 54 | res.status(500).json({ error: err.message }) 55 | } 56 | }) 57 | 58 | export default router 59 | -------------------------------------------------------------------------------- /src/routers/qbit/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import httpProxy from 'http-proxy' 3 | 4 | const router = Router() 5 | 6 | router.use((req, res, next) => { 7 | const proxy = httpProxy.createProxyServer({ 8 | host: process.env.QBIT_BASE, 9 | xfwd: process.env.SKIP_X_FORWARD_HEADERS !== 'true', 10 | secure: process.env.USE_INSECURE_SSL !== 'true', 11 | }) 12 | proxy.web(req, res, { target: `${process.env.QBIT_BASE}/api` }, next) 13 | }) 14 | 15 | export default router 16 | -------------------------------------------------------------------------------- /src/routines/update.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { config } from 'dotenv' 3 | import fs from 'fs' 4 | import JSZip from 'jszip' 5 | import path from 'path' 6 | 7 | config() 8 | 9 | 10 | const BASE_URL_VERSION = '/repos/VueTorrent/VueTorrent/contents/version.txt?ref=' 11 | const BASE_URL_ZIPBALL = '/repos/VueTorrent/VueTorrent/zipball/' 12 | const BASE_URL_RELEASE = 'https://github.com/VueTorrent/VueTorrent/releases/download/$TAG/vuetorrent.zip' 13 | const STABLE_BRANCH_NAME = 'latest-release' 14 | const DEV_BRANCH_NAME = 'nightly-release' 15 | const VERSION_PATTERN = /^v?(?[0-9.]+)(-(?\d+)-g(?[0-9a-f]+))?$/ 16 | 17 | const BASE_FS_PATH = process.env.VUETORRENT_PATH || '/vuetorrent' 18 | const TEMP_ZIP_PATH = `${ BASE_FS_PATH }/vuetorrent.zip` 19 | const WEBUI_PATH = `${ BASE_FS_PATH }/vuetorrent` 20 | const VERSION_FILE_PATH = `${ WEBUI_PATH }/version.txt` 21 | const WEBUI_OLD_PATH = `${ BASE_FS_PATH }/vuetorrent-old` 22 | 23 | const githubClient = axios.create({ 24 | baseURL: 'https://api.github.com', 25 | headers: { 26 | Accept: 'application/vnd.github.v3+json', 27 | Authorization: process.env.GITHUB_AUTH ? `Bearer ${process.env.GITHUB_AUTH}` : undefined, 28 | 'User-Agent': `vuetorrent/vuetorrent-backend` 29 | } 30 | }) 31 | 32 | /** @typedef {{ version: string; commits?: string; sha?: string }} VersionType */ 33 | 34 | /** 35 | * @param version {string} 36 | * @returns {VersionType | undefined} 37 | */ 38 | function extractVersion(version) { 39 | const match = version.match(VERSION_PATTERN) 40 | return match?.groups 41 | } 42 | 43 | function formatVersion(version) { 44 | if (!version) return 'unknown' 45 | if (!version.commits || !version.sha) return `v${ version.version }` 46 | return `v${ version.version }-${ version.commits }-g${ version.sha }` 47 | } 48 | 49 | function getInstalledVersion() { 50 | if (!fs.existsSync(VERSION_FILE_PATH)) return null 51 | 52 | try { 53 | return extractVersion(fs.readFileSync(VERSION_FILE_PATH).toString()) 54 | } catch (err) { 55 | console.error(err) 56 | return null 57 | } 58 | } 59 | 60 | async function getLatestVersion(ref) { 61 | /** @type {AxiosResponse>} */ 62 | const response = await githubClient.get(BASE_URL_VERSION + ref) 63 | 64 | // Extract from base64 65 | return extractVersion(atob(response.data.content.trim())) 66 | } 67 | 68 | async function downloadFile(url, outputPath) { 69 | const writer = fs.createWriteStream(outputPath) 70 | const response = await githubClient({ 71 | url, 72 | method: 'GET', 73 | responseType: 'stream' 74 | }) 75 | 76 | response.data.pipe(writer) 77 | 78 | return new Promise((resolve, reject) => { 79 | writer.on('finish', resolve) 80 | writer.on('error', reject) 81 | }) 82 | } 83 | 84 | async function unzipFile(zipPath, extractTo) { 85 | const data = fs.readFileSync(zipPath) 86 | const zip = await JSZip.loadAsync(data) 87 | await Promise.all(Object.keys(zip.files).map(async (filename) => { 88 | const file = zip.files[filename] 89 | const newName = filename.slice(filename.indexOf('/')) 90 | const filePath = path.join(extractTo, newName) 91 | if (file.dir) { 92 | fs.mkdirSync(filePath, { recursive: true }) 93 | } else { 94 | const content = await file.async('nodebuffer') 95 | fs.writeFileSync(filePath, content) 96 | } 97 | })) 98 | } 99 | 100 | async function downloadUpdate(url) { 101 | if (!fs.existsSync(BASE_FS_PATH)) { 102 | fs.mkdirSync(BASE_FS_PATH, { recursive: true }) 103 | } 104 | 105 | // Download zip file 106 | await downloadFile(url, TEMP_ZIP_PATH) 107 | 108 | // Backup current install if it exists 109 | if (fs.existsSync(WEBUI_OLD_PATH)) { 110 | fs.rmSync(WEBUI_OLD_PATH, { recursive: true }) 111 | } 112 | if (fs.existsSync(WEBUI_PATH)) { 113 | fs.renameSync(WEBUI_PATH, WEBUI_OLD_PATH) 114 | } 115 | 116 | // Unzip the downloaded file 117 | try { 118 | await unzipFile(TEMP_ZIP_PATH, WEBUI_PATH) 119 | } catch (err) { 120 | console.error(err) 121 | // Restore backup if unzip fails 122 | if (fs.existsSync(WEBUI_OLD_PATH)) { 123 | fs.renameSync(WEBUI_OLD_PATH, WEBUI_PATH) 124 | } 125 | } 126 | } 127 | 128 | export async function checkForUpdate() { 129 | let branchName 130 | switch (process.env.RELEASE_TYPE) { 131 | case undefined: 132 | case 'stable': 133 | case 'latest': 134 | branchName = STABLE_BRANCH_NAME 135 | break 136 | case 'dev': 137 | case 'nightly': 138 | branchName = DEV_BRANCH_NAME 139 | break 140 | } 141 | 142 | const installedVersion = getInstalledVersion() 143 | 144 | /** @type {VersionType | undefined} */ 145 | let latestVersion 146 | if (!branchName) { 147 | // If unknown release type is specified, we try to match a specific version 148 | latestVersion = extractVersion(process.env.RELEASE_TYPE) 149 | } else { 150 | latestVersion = await getLatestVersion(branchName) 151 | } 152 | 153 | if (!latestVersion) { 154 | return `Unable to find candidate for release type "${ process.env.RELEASE_TYPE }"` 155 | } 156 | 157 | if (installedVersion?.version !== latestVersion?.version 158 | || installedVersion?.commits !== latestVersion?.commits 159 | || installedVersion?.sha !== latestVersion?.sha) { 160 | if (!branchName) { 161 | await downloadUpdate(BASE_URL_RELEASE.replace('$TAG', `v${latestVersion.version}`)) 162 | } else { 163 | await downloadUpdate(BASE_URL_ZIPBALL + branchName) 164 | } 165 | return `Update successful from ${ formatVersion(installedVersion) } to ${ formatVersion(latestVersion) }` 166 | } 167 | return `Instance up-to-date using ref ${process.env.RELEASE_TYPE}` 168 | } 169 | -------------------------------------------------------------------------------- /src/ssl.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | 3 | export function getSSLOptionsFromEnv() { 4 | const SSL_CERT = process.env.SSL_CERT 5 | const SSL_KEY = process.env.SSL_KEY 6 | const SSL_CERT_PATH = process.env.SSL_CERT_PATH 7 | const SSL_KEY_PATH = process.env.SSL_KEY_PATH 8 | 9 | let cert, key 10 | if (SSL_CERT && SSL_KEY) { 11 | cert = SSL_CERT 12 | key = SSL_KEY 13 | } else if ( 14 | SSL_CERT_PATH && SSL_KEY_PATH && 15 | fs.existsSync(SSL_CERT_PATH) && fs.existsSync(SSL_KEY_PATH) 16 | ) { 17 | cert = fs.readFileSync(SSL_CERT_PATH) 18 | key = fs.readFileSync(SSL_KEY_PATH) 19 | } 20 | 21 | if (cert && key) { 22 | return { cert, key } 23 | } 24 | 25 | return null 26 | } 27 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export function assert_env() { 2 | for (const key of ['QBIT_BASE']) { 3 | if (!process.env[key]) { 4 | throw new Error(`${ key } environment variable is missing`) 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/config.http: -------------------------------------------------------------------------------- 1 | # Fetch all config 2 | GET http://localhost:3000/config 3 | ### 4 | # Upsert a key 5 | PUT http://localhost:3000/config/key1 6 | Content-Type: application/json 7 | 8 | { 9 | "value": "value1" 10 | } 11 | ### 12 | # Fetch that key 13 | GET http://localhost:3000/config/key1 14 | ### 15 | # And delete it 16 | DELETE http://localhost:3000/config/key1 17 | ### -------------------------------------------------------------------------------- /tests/ping.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/ping -------------------------------------------------------------------------------- /tests/qbit.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:3000/api/v2/auth/login 2 | 3 | username=XXXXX&password=XXXXX 4 | ### 5 | GET http://localhost:3000/api/v2/app/version 6 | ### 7 | --------------------------------------------------------------------------------