diff --git a/.env.example b/.env.example index 4a0f858..3878661 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,4 @@ Client_ID= "Azure Client ID" -Client_Secret= "Azure Client Secret" \ No newline at end of file +Client_Secret= "Azure Client Secret" +CurseForge_APIKey = "CurseForge API Key" +Github_AccessToken = "Github Access Token" \ No newline at end of file diff --git a/.envrc b/.envrc index ff5954f..3550a30 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -use flake . --impure \ No newline at end of file +use flake diff --git a/.github/doc/tauri.md b/.github/doc/tauri.md new file mode 100644 index 0000000..014d6df --- /dev/null +++ b/.github/doc/tauri.md @@ -0,0 +1,35 @@ +# Tauri Support + +# OAuth + +To use our custom authentication OAuth you have to spawn a new thread so we have provided an example below to look at if your having troubles. + +```rs +#[tauri::command] +use tokio::sync::mpsc; + +async fn auth() -> Result { + let (tx, mut rx) = mpsc::channel(1); + tauri::async_runtime::spawn(async move { + let result = handle_auth().await.map_err(|e| e.to_string()); + let _ = tx.send(result).await; // Send the result back through the channel + }); + + // Receive the result from the channel + match rx.recv().await { + Some(result) => result, // Return the result from handle_auth + None => Err("No result received from handle_auth".to_string()), // Handle the case where no result is received + } +} + +async fn handle_auth() -> Result> { + let auth = Oauth::new("ClientID", None); + let window_url = auth.url(); + + let _ = open::that(window_url); + + let auth_info = auth.launch(false, "ClientSecret").await?; + + Ok(auth_info) +} +``` \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml index f65afc5..ce04521 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,2 +1,3 @@ -Main Library: -- "/src/**/**" \ No newline at end of file +main_library: + - changed-files: + - any-glob-to-any-file: src/**/* \ No newline at end of file diff --git a/.github/workflows/.ci.txt b/.github/workflows/.ci.txt new file mode 100644 index 0000000..dcaa27c --- /dev/null +++ b/.github/workflows/.ci.txt @@ -0,0 +1,500 @@ +name: CI +env: + DEBUG: napi:* + APP_NAME: minecraft-essentials + MACOSX_DEPLOYMENT_TARGET: '10.13' +permissions: + contents: write + id-token: write +'on': + push: + branches: + - main + tags-ignore: + - '**' + paths-ignore: + - '**/*.md' + - LICENSE + - '**/*.gitignore' + - .editorconfig + - docs/** + pull_request: null +jobs: + build: + strategy: + fail-fast: false + matrix: + settings: + - host: macos-latest + target: x86_64-apple-darwin + build: | + yarn build + strip -x *.node + - host: windows-latest + build: yarn build + target: x86_64-pc-windows-msvc + - host: windows-latest + build: | + yarn build --target i686-pc-windows-msvc + yarn test + target: i686-pc-windows-msvc + - host: ubuntu-latest + target: x86_64-unknown-linux-gnu + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian + build: |- + set -e && + yarn build --target x86_64-unknown-linux-gnu && + strip *.node + - host: ubuntu-latest + target: x86_64-unknown-linux-musl + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine + build: set -e && yarn build && strip *.node + - host: macos-latest + target: aarch64-apple-darwin + build: | + yarn build --target aarch64-apple-darwin + strip -x *.node + - host: ubuntu-latest + target: aarch64-unknown-linux-gnu + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 + build: |- + set -e && + yarn build --target aarch64-unknown-linux-gnu && + aarch64-unknown-linux-gnu-strip *.node + - host: ubuntu-latest + target: armv7-unknown-linux-gnueabihf + setup: | + sudo apt-get update + sudo apt-get install gcc-arm-linux-gnueabihf -y + build: | + yarn build --target armv7-unknown-linux-gnueabihf + arm-linux-gnueabihf-strip *.node + - host: ubuntu-latest + target: aarch64-linux-android + build: | + yarn build --target aarch64-linux-android + ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip *.node + - host: ubuntu-latest + target: armv7-linux-androideabi + build: | + yarn build --target armv7-linux-androideabi + ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip *.node + - host: ubuntu-latest + target: aarch64-unknown-linux-musl + docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine + build: |- + set -e && + rustup target add aarch64-unknown-linux-musl && + yarn build --target aarch64-unknown-linux-musl && + /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node + - host: windows-latest + target: aarch64-pc-windows-msvc + build: yarn build --target aarch64-pc-windows-msvc + - host: ubuntu-latest + target: riscv64gc-unknown-linux-gnu + setup: | + sudo apt-get update + sudo apt-get install gcc-riscv64-linux-gnu -y + build: | + yarn build --target riscv64gc-unknown-linux-gnu + riscv64-linux-gnu-strip *.node + name: stable - ${{ matrix.settings.target }} - node@18 + runs-on: ${{ matrix.settings.host }} + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + if: ${{ !matrix.settings.docker }} + with: + node-version: 18 + cache: yarn + - name: Install + uses: dtolnay/rust-toolchain@stable + if: ${{ !matrix.settings.docker }} + with: + toolchain: stable + targets: ${{ matrix.settings.target }} + - name: Cache cargo + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + .cargo-cache + target/ + key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} + - uses: goto-bus-stop/setup-zig@v2 + if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }} + with: + version: 0.11.0 + - name: Setup toolchain + run: ${{ matrix.settings.setup }} + if: ${{ matrix.settings.setup }} + shell: bash + - name: Setup node x86 + if: matrix.settings.target == 'i686-pc-windows-msvc' + run: yarn config set supportedArchitectures.cpu "ia32" + shell: bash + - name: Install dependencies + run: yarn install + - name: Setup node x86 + uses: actions/setup-node@v4 + if: matrix.settings.target == 'i686-pc-windows-msvc' + with: + node-version: 18 + cache: yarn + architecture: x86 + - name: Build in docker + uses: addnab/docker-run-action@v3 + if: ${{ matrix.settings.docker }} + with: + image: ${{ matrix.settings.docker }} + options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build' + run: ${{ matrix.settings.build }} + - name: Build + run: ${{ matrix.settings.build }} + if: ${{ !matrix.settings.docker }} + shell: bash + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: bindings-${{ matrix.settings.target }} + path: ${{ env.APP_NAME }}.*.node + if-no-files-found: error + build-freebsd: + runs-on: macos-12 + name: Build FreeBSD + steps: + - uses: actions/checkout@v4 + - name: Build + id: build + uses: cross-platform-actions/action@v0.21.0 + env: + DEBUG: napi:* + RUSTUP_IO_THREADS: 1 + with: + operating_system: freebsd + version: '13.2' + memory: 13G + cpu_count: 3 + environment_variables: DEBUG RUSTUP_IO_THREADS + shell: bash + run: | + sudo pkg install -y -f curl node libnghttp2 npm + sudo npm install -g yarn --ignore-scripts + curl https://sh.rustup.rs -sSf --output rustup.sh + sh rustup.sh -y --profile minimal --default-toolchain stable + source "$HOME/.cargo/env" + echo "~~~~ rustc --version ~~~~" + rustc --version + echo "~~~~ node -v ~~~~" + node -v + echo "~~~~ yarn --version ~~~~" + yarn --version + pwd + ls -lah + whoami + env + freebsd-version + yarn install + yarn build + strip -x *.node + yarn test + rm -rf node_modules + rm -rf target + rm -rf .yarn/cache + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: bindings-freebsd + path: ${{ env.APP_NAME }}.*.node + if-no-files-found: error + test-macOS-windows-binding: + name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} + needs: + - build + strategy: + fail-fast: false + matrix: + settings: + - host: macos-latest + target: x86_64-apple-darwin + - host: windows-latest + target: x86_64-pc-windows-msvc + node: + - '18' + - '20' + runs-on: ${{ matrix.settings.host }} + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: yarn + - name: Install dependencies + run: yarn install + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-${{ matrix.settings.target }} + path: . + - name: List packages + run: ls -R . + shell: bash + - name: Test bindings + run: yarn test + test-linux-x64-gnu-binding: + name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }} + needs: + - build + strategy: + fail-fast: false + matrix: + node: + - '18' + - '20' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: yarn + - name: Install dependencies + run: yarn install + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-x86_64-unknown-linux-gnu + path: . + - name: List packages + run: ls -R . + shell: bash + - name: Test bindings + run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim yarn test + test-linux-x64-musl-binding: + name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }} + needs: + - build + strategy: + fail-fast: false + matrix: + node: + - '18' + - '20' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: yarn + - name: Install dependencies + run: | + yarn config set supportedArchitectures.libc "musl" + yarn install + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-x86_64-unknown-linux-musl + path: . + - name: List packages + run: ls -R . + shell: bash + - name: Test bindings + run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine yarn test + test-linux-aarch64-gnu-binding: + name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }} + needs: + - build + strategy: + fail-fast: false + matrix: + node: + - '18' + - '20' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-aarch64-unknown-linux-gnu + path: . + - name: List packages + run: ls -R . + shell: bash + - name: Install dependencies + run: | + yarn config set supportedArchitectures.cpu "arm64" + yarn config set supportedArchitectures.libc "glibc" + yarn install + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - name: Setup and run tests + uses: addnab/docker-run-action@v3 + with: + image: node:${{ matrix.node }}-slim + options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' + run: | + set -e + yarn test + ls -la + test-linux-aarch64-musl-binding: + name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }} + needs: + - build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-aarch64-unknown-linux-musl + path: . + - name: List packages + run: ls -R . + shell: bash + - name: Install dependencies + run: | + yarn config set supportedArchitectures.cpu "arm64" + yarn config set supportedArchitectures.libc "musl" + yarn install + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - name: Setup and run tests + uses: addnab/docker-run-action@v3 + with: + image: node:lts-alpine + options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' + run: | + set -e + yarn test + test-linux-arm-gnueabihf-binding: + name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }} + needs: + - build + strategy: + fail-fast: false + matrix: + node: + - '18' + - '20' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: bindings-armv7-unknown-linux-gnueabihf + path: . + - name: List packages + run: ls -R . + shell: bash + - name: Install dependencies + run: | + yarn config set supportedArchitectures.cpu "arm" + yarn install + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm + - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - name: Setup and run tests + uses: addnab/docker-run-action@v3 + with: + image: node:${{ matrix.node }}-bullseye-slim + options: '--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build' + run: | + set -e + yarn test + ls -la + universal-macOS: + name: Build universal macOS binary + needs: + - build + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: yarn + - name: Install dependencies + run: yarn install + - name: Download macOS x64 artifact + uses: actions/download-artifact@v3 + with: + name: bindings-x86_64-apple-darwin + path: artifacts + - name: Download macOS arm64 artifact + uses: actions/download-artifact@v3 + with: + name: bindings-aarch64-apple-darwin + path: artifacts + - name: Combine binaries + run: yarn universal + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: bindings-universal-apple-darwin + path: ${{ env.APP_NAME }}.*.node + if-no-files-found: error + publish: + name: Publish + runs-on: ubuntu-latest + needs: + - build-freebsd + - test-macOS-windows-binding + - test-linux-x64-gnu-binding + - test-linux-x64-musl-binding + - test-linux-aarch64-gnu-binding + - test-linux-aarch64-musl-binding + - test-linux-arm-gnueabihf-binding + - universal-macOS + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: yarn + - name: Install dependencies + run: yarn install + - name: Download all artifacts + uses: actions/download-artifact@v3 + with: + path: artifacts + - name: Move artifacts + run: yarn artifacts + - name: List packages + run: ls -R ./npm + shell: bash + - name: Publish + run: | + npm config set provenance true + if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$"; + then + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + npm publish --access public + elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+"; + then + echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + npm publish --tag next --access public + else + echo "Not a release, skipping publish" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0cc05d4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +name: CI +on: + push: + pull_request: + workflow_dispatch: + schedule: + - cron: '0 0 * * 0' + +permissions: + contents: write + +jobs: + test: + timeout-minutes: 20 + name: Test for ${{ matrix.os }} on ${{ matrix.toolchain }} + env: + Client_ID: ${{ secrets.CLIENTID }} + Client_Secret: ${{ secrets.CLIENTSECRET }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + toolchain: [stable, beta, nightly] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - run: | + rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + cargo build --verbose + cargo test --verbose + deploy-crate: + name: Deploy to crates.io + needs: test + if: startsWith(github.ref, 'refs/tags/') && contains(github.ref, 'v[0-9]*') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }} + deploy-cli: + name: Deploy CLI to github release + needs: test + env: + BUILD_CMD: cargo + strategy: + matrix: + include: + - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } + - { target: i686-pc-windows-msvc , os: windows-2019 } + - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } + - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } + - { target: x86_64-apple-darwin , os: macos-12 } + - { target: aarch64-apple-darwin , os: macos-12 } + - { target: x86_64-pc-windows-msvc , os: windows-2019 } + - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } + - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } + if: startsWith(github.ref, 'refs/tags/') && contains(github.ref, 'v[0-9]*') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - if: matrix.target == 'aarch64-unknown-linux-gnu' + run: sudo apt-get update && sudo apt-get install gcc-multilib + + - name: Install cross + if: matrix.use-cross + run: | + cargo install cargo-binstall + cargo binstall cross + + - name: Use cross as build tool + if: matrix.use-cross + run: echo "BUILD_CMD=cross" >> $GITHUB_ENV + + - name: Build + run: $BUILD_CMD build --release --target ${{ matrix.target }} --bin minecraft-essentials + + - name: Determine paths + id: paths + run: | + EXE_suffix="" ; case ${{ matrix.target }} in *-pc-windows-*) EXE_suffix=".exe" ;; esac + BIN_PATH="target/${{ matrix.target }}/release/${NAME}${EXE_suffix}" + PKG_NAME=${NAME}-${VERSION}-${{ matrix.target }}${EXE_suffix} + cp ${BIN_PATH} ${PKG_NAME} + echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT + + - name: Compress binary + run: upx ${{ steps.paths.outputs.PKG_NAME }} + + - name: Upload binary + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: ${{ steps.paths.outputs.PKG_NAME }} \ No newline at end of file diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index b7fe2fb..7d56201 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -12,9 +12,10 @@ jobs: steps: - uses: actions/stale@v8 with: + exempt-pr-labels: "release" stale-pr-message: 'This pull request has become stale and will be closed automatically within a period of time. Sorry about that.' stale-issue-message: 'This issue has become stale and will be closed automatically within a period of time. Sorry about that.' close-issue-message: "This pr has been closed automatically" close-pr-message: "This pr has been closed automatically" - days-before-stale: 7 - days-before-close: 14 \ No newline at end of file + days-before-stale: 2 + days-before-close: 7 \ No newline at end of file diff --git a/.github/workflows/deploy-cli.yml b/.github/workflows/deploy-cli.yml deleted file mode 100644 index 3f145b1..0000000 --- a/.github/workflows/deploy-cli.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Deploy to Github -on: - push: - branches: [main] -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [stable, nightly] - steps: - - uses: hecrj/setup-rust-action@v2 - with: - rust-version: ${{ matrix.rust }} - - uses: actions/checkout@master - - name: Run tests - run: cargo test --verbose - env: - Client_ID: ${{secrets.ClientID}} - Client_Secret: ${{secrets.ClientSecret}} -# - name: Build CLI with cli feature -# run: cargo build --release --features cli -# - name: Create tar.gz -# run: | -# tar -czvf cli.tar.gz -C target/release/ cli -# - name: Extract version from CLI -# id: extract_version -# run: | -# echo "::set-output name=version::$(cargo run --features cli -- version | sed -n 's/.*\([0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/p')" -# - name: Create Release -# id: create_release -# uses: actions/create-release@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token -# with: -# tag_name: v${{ steps.extract_version.outputs.version }} -# release_name: CLI v${{ steps.extract_version.outputs.version }} -# draft: false -# prerelease: false -# - name: Upload Release Asset -# id: upload-release-asset -# uses: actions/upload-release-asset@v1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# upload_url: ${{ steps.create_release.outputs.upload_url }} -# asset_path: ./cli.tar.gz -# asset_name: cli.tar.gz -# asset_content_type: application/gzip \ No newline at end of file diff --git a/.github/workflows/deploy-crate.yml b/.github/workflows/deploy-crate.yml deleted file mode 100644 index 04377dd..0000000 --- a/.github/workflows/deploy-crate.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Deploy to Crates -on: - push: - branches: [main] -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [stable, nightly] - steps: - - uses: hecrj/setup-rust-action@v2 - with: - rust-version: ${{ matrix.rust }} - - uses: actions/checkout@master - - name: Run tests - run: cargo test --verbose - env: - Client_ID: ${{secrets.ClientID}} - Client_Secret: ${{secrets.ClientSecret}} - publish: - needs: ["test"] - runs-on: ubuntu-latest - steps: - - uses: hecrj/setup-rust-action@v2 - with: - rust-version: stable - - uses: actions/checkout@master - - name: Cargo Publish - run: cargo publish --token ${{secrets.CRATESIO_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml deleted file mode 100644 index 4744a29..0000000 --- a/.github/workflows/pr.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Pull Request Jobs -on: [pull_request] -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [stable, nightly] - steps: - - uses: hecrj/setup-rust-action@v2 - with: - rust-version: ${{ matrix.rust }} - - uses: actions/checkout@master - - name: Run tests - run: cargo test --verbose - env: - Client_ID: ${{secrets.ClientID}} - Client_Secret: ${{secrets.ClientSecret}} \ No newline at end of file diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml deleted file mode 100644 index 5eca93a..0000000 --- a/.github/workflows/weekly.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Weekly Jobs -on: - schedule: - - cron: '0 0 * * *' - workflow_dispatch: -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [stable, nightly] - steps: - - uses: hecrj/setup-rust-action@v2 - with: - rust-version: ${{ matrix.rust }} - - uses: actions/checkout@master - - name: Run tests - run: cargo test --verbose - env: - Client_ID: ${{secrets.ClientID}} - Client_Secret: ${{secrets.ClientSecret}} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 18a7f71..151ef6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,14 @@ -### RUST FOLDERS ### /target -### ENVIROMENT VIRABLES ### +# environment variables .env* !.env.example -### OPERATING SYSTEM ### +# macOS specific files .DS_STORE -### Nix ### +# nix .pre-commit-config.yaml -.devenv -.direnv -!.envrc \ No newline at end of file +.direnv \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index c62db09..678f620 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,8 @@ { - "rust-analyzer.cargo.features": ["default"], - "biome.enabled": false, + "rust-analyzer.cargo.features": [ + "default" + ], + "cSpell.ignorePaths": [ + "src/**/*" + ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b02d091..eadd3f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,128 +1,154 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] -name = "bitflags" -version = "2.4.1" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "bytes" +name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.0.83" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -133,9 +159,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.4" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -143,9 +169,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -155,9 +181,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", @@ -167,15 +193,24 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "convert_case" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "core-foundation" @@ -189,15 +224,75 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "corelib" +version = "0.2.11" +dependencies = [ + "base64", + "bytes", + "clap", + "displaydoc", + "hex", + "rand", + "reqwest", + "ring", + "serde", + "serde_json", + "serde_urlencoded", + "sha-1", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", @@ -212,9 +307,9 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -227,19 +322,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "fastrand" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fnv" @@ -273,36 +368,36 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-task", @@ -310,11 +405,21 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -323,21 +428,21 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", "indexmap", "slab", @@ -348,9 +453,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -358,12 +463,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" - [[package]] name = "hex" version = "0.4.3" @@ -372,9 +471,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -383,9 +482,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -393,12 +492,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http", "http-body", "pin-project-lite", @@ -406,15 +505,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hyper" -version = "1.3.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ "bytes", "futures-channel", @@ -430,6 +529,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -448,9 +564,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", @@ -461,26 +577,154 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "indexmap" -version = "2.1.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", @@ -488,54 +732,71 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "libc" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] -name = "libc" -version = "0.2.152" +name = "libloading" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "log" -version = "0.4.20" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -547,49 +808,97 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" name = "minecraft-essentials" version = "0.2.11" dependencies = [ - "base64", "clap", - "displaydoc", + "corelib", "dotenv", - "hex", - "rand", - "reqwest", - "ring", "serde", - "serde_json", - "serde_urlencoded", - "thiserror", "tokio", - "url", ] [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.10" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "napi" +version = "2.16.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" +dependencies = [ + "bitflags", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", + "tokio", +] + +[[package]] +name = "napi-build" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28acfa557c083f6e254a786e01ba253fc56f18ee000afcd4f79af735f73a6da" + +[[package]] +name = "napi-derive" +version = "2.16.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" +dependencies = [ + "cfg-if", + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "regex", + "semver", + "syn", +] + +[[package]] +name = "napi-sys" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" +dependencies = [ + "libloading", ] [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -602,37 +911,37 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +name = "node" +version = "0.2.11" dependencies = [ - "hermit-abi", - "libc", + "corelib", + "napi", + "napi-build", + "napi-derive", ] [[package]] name = "object" -version = "0.32.2" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.62" +version = "0.10.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" +checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" dependencies = [ - "bitflags 2.4.1", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -654,15 +963,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.98" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -676,31 +985,11 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -710,30 +999,33 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -769,19 +1061,39 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "regex" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ - "bitflags 1.3.2", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64", "bytes", @@ -793,6 +1105,7 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -811,12 +1124,13 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "windows-registry", ] [[package]] @@ -836,61 +1150,90 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.4.1", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -899,28 +1242,34 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" -version = "1.0.198" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -929,11 +1278,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -950,6 +1300,23 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "slab" version = "0.4.9" @@ -967,12 +1334,12 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -981,17 +1348,29 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.48" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -1000,26 +1379,40 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -1027,31 +1420,32 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "getrandom", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.58" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -1059,42 +1453,36 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.37.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -1111,64 +1499,71 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "tower" -version = "0.4.13" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", + "sync_wrapper", "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] @@ -1180,25 +1575,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "unicode-bidi" -version = "0.3.14" +name = "typenum" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" [[package]] -name = "unicode-normalization" -version = "0.1.22" +name = "unicode-segmentation" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "untrusted" @@ -1208,20 +1600,32 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" @@ -1229,6 +1633,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.1" @@ -1246,23 +1656,24 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -1271,21 +1682,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1293,9 +1705,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -1306,27 +1718,51 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets 0.48.5", + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", ] [[package]] @@ -1335,129 +1771,184 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.0" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_aarch64_msvc" -version = "0.52.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_i686_gnu" -version = "0.52.0" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_i686_msvc" -version = "0.52.0" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] -name = "windows_x86_64_gnu" -version = "0.52.0" +name = "writeable" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "yoke" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.0" +name = "yoke-derive" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" +name = "zerocopy-derive" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "winreg" -version = "0.52.0" +name = "zerofrom" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index f7504e4..6985949 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,56 +1,14 @@ -[package] -name = "minecraft-essentials" +[workspace] +members = ["crates/core", "crates/minecraft-essentails", "crates/node"] +default-members = ["crates/minecraft-essentails"] +features = ["default"] + +[workspace.package] version = "0.2.11" -keywords = ["Minecraft", "MinecraftClient"] +keywords = ["Minecraft", "MinecraftClient", "Essentials"] authors = ["Eveeifyeve "] -description = "A Package that gives all Minecraft client launchers essentials." +description = "A Library that gives all Minecraft client launchers essentials." homepage = "https://minecraft-essentials.github.io" repository = "https://github.com/minecraft-essentials/minecraft-essentials" -documentation = "https://docs.rs/minecraft-essentials" license-file = "LICENSE" -edition = "2021" - -[build] -rustdocflags = ["--theme=ayu"] - -[[bin]] -name = "minecraft-essentials" -path = "src/main.rs" - -[package.metadata.binstall] -pkg-url = "{ repo }/releases/download/v{ version }/{ name }-cli" -bin-dir = "{ name }-{ target }-v{ version }/{ bin }{ binary-ext }" -pkg-fmt = "tgz" - -[dependencies] -tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros", "sync"] } -reqwest = { version = "0.12.4", features = ["json"], optional = true } -serde = { version = "1.0.198", features = ["derive"], optional = true } -clap = { version = "4.5.4", optional = true, features = ["derive"] } -serde_urlencoded = { version = "0.7.1", optional = true } -serde_json = { version = "1.0.116", optional = true } -base64 = { version = "0.22.0", optional = true } -ring = { version = "0.17.8", optional = true } -rand = { version = "0.8.5", optional = true } -hex = { version = "0.4.3", optional = true } -url = { version = "2.5.0", optional = true} -displaydoc = "0.2.4" -thiserror = "1.0.58" - -[dev-dependencies] -dotenv = { version = "0.15.0" } - -[features] -default = ["custom-auth", "custom-launch", "cli"] -custom-auth = [ - "dep:serde_urlencoded", - "dep:serde_json", - "dep:reqwest", - "dep:serde", - "dep:rand", - "dep:url" -] -custom-launch = [] -minecraft-auth = ["dep:ring", "dep:hex"] -cli = ["default", "dep:clap", "custom-auth", "minecraft-auth", "custom-launch"] -deperacted = [] +edition = "2024" diff --git a/README.md b/README.md index be1fcd9..6436bc7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ # Minecraft-Essentials +[![Crates.io Version](https://img.shields.io/crates/v/minecraft-essentials)](https://crates.io/crates/minecraft-essentials) +[![Crates.io Downloads](https://img.shields.io/crates/d/minecraft-essentials)](https://crates.io/crates/minecraft-essentials) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fminecraft-essentials%2Fminecraft-essentials.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fminecraft-essentials%2Fminecraft-essentials?ref=badge_shield) +[![CI](https://github.com/minecraft-essentials/minecraft-essentials/actions/workflows/ci.yml/badge.svg)](https://github.com/minecraft-essentials/minecraft-essentials/actions/workflows/ci.yml) +[![GitHub Repo stars](https://img.shields.io/github/stars/minecraft-essentials/minecraft-essentials)](https://github.com/minecraft-essentials/minecraft-essentials/stargazers) -The offical rust/cargo package that provides essential functionality for Minecraft client launchers. +The official rust/cargo package that provides essential functionality for Minecraft client launchers. + ## Features @@ -32,92 +37,47 @@ Add the following to your `Cargo.toml`: ```toml [dependencies] -minecraft-essentials = "0.2.9" +minecraft-essentials = "0.2.12" ``` ## Usage -### Authentifcation -#### OAuth Custom Authentifcation | OAuth2.0 - -This example demonstrates how to use the OAuth authentication method provided by `minecraft-essentials`, `oauth` feature. - -```rust -use minecraft_essentials::*; - -async fn Oauth(client_id: &str, client_secret: &str, port: Option, bedrockrel: bool) { -// Initialize the OAuth authentication object -let auth = Oauth::new(client_id, port); - -// Print the URL needed for authentication -println!("URL: {}", auth.url()); - -// Launch the authentication process - let auth_info = auth.launch(bedrockrel, client_secret).await; - -// Print the authentication information -println!("{:?}", auth_info) -} - -fn main() { - Oauth("CLientID", "ClientSecret", None, false); -} -``` - -#### Device Code Custom Authentication | DeviceCode - -> [!WARNING] -> This is still work in progress **so it may change**. - - -This example demonstrates how to use the Device Code authentication method provided by `minecraft-essentials`, `devicecode` feature. +### Authentication +This is an example of how to use the authentication builder. +For this example we will be using the Oauth method. ```rust, ignore -use minecraft_essentials::*; - - -async fn deviceCode(client_id: &str) { - // Create a new device code instance - let code = DeviceCode::new(client_id).expect("Expected Code"); - - // Print the device code information - println!("Stuff Here: {}", code.preinfo()); - - // Launch the authentication process - let code_info = code.launch().await?; -} - -fn main() { - // Initialize Device Code authentication with your client ID - deviceCode("111231209837123098712"); +use minecraft_essentials::{AuthenticationBuilder, AuthType}; +use std::env; +use tokio::main; + +#[tokio::main] +async fn main() { + let client_id = "ClientID here"; + let client_secret = "Client_Secret here"; // Recommended to use env to store secrets + let mut builder = AuthenticationBuilder::builder(); + builder + .of_type(AuthType::Oauth) + .client_id(&client_id) + .client_secret(&client_secret) // Only Required for ouath + .port(Some(8000)); // Optional for ouath but defaults to port 8000 + println!("Info: {:?}", builder.get_info().await); // users info + println!("Authentifcation Final Info: {:?}", builder.launch().await.unwrap()); // for your launcher. } ``` -#### Acutal Minecraft Authentfication - -> [!CAUTION] -> This is currently in the [roadmap][Roadmap] for 0.2.12-14 currently it's not avalible. - - ### Launching - -#### Custom Launching -```rust -use minecraft_essentials::Launch; -use std::path::Path; - -let args = vec!["--uuid:LauncherUUID".to_string(), "--token:Beartoken".to_string()]; -let jre_path = Path::new("/path/to/jre").to_path_buf(); -let java_exe = "/your/java/path"; - -// Init the instance of launch -let launch = Launch::new(args, java_exe.to_string(), Some(jre_path.clone()), Some(false)).expect("Expected Launch"); - -// Grab the info to verify that your doing everything currect. -let launch_info = launch.info(); -println!("Launching with: {:?}", launch_info); - -let _ = launch.launch_jre(); +```rust, ignore +use minecraft_essentials::LaunchBuilder; +use std::path::PathBuf; + let args = ["--argexample 123"] + let mut builder = LaunchBuilder::init(); + builder + .args(args) + .java(Some(PathBuf::from("C:\\Program Files\\Java\\jdk-17.0.1\\bin\\java.exe"))) // Custom Java Path for custom java + .client(Some(PathBuf::from("C:\\Users\\User\\Desktop\\Client.jar"))) // Minecraft Client Path for custom client + .mods(Some(vec![PathBuf::from("C:\\Users\\User\\Desktop\\Mod1.jar"), PathBuf::from("C:\\Users\\User\\Desktop\\Mod2.jar")])) // Custom Mods Path for custom mods (Optional) + .launch(None).await // Launches Minecraft ``` ## Contributing diff --git a/contributing.md b/contributing.md index 54f6287..045d0da 100644 --- a/contributing.md +++ b/contributing.md @@ -3,8 +3,10 @@ ## Before you start: - You Read the [conventional commit specification](https://www.conventionalcommits.org/en/v1.0.0/), -- You have basic knowledge of how [rust / A library works](https://doc.rust-lang.org/cargo/index.html) & -- And At least done a [contributed at least once](https://github.com/firstcontributions/first-contributions). +- You have intermediate knowledge of how [rust / A library works](https://doc.rust-lang.org/cargo/index.html) & +- Knowledge of Minecraft Authentification and Launching (if working on authentification or launching) +- Knowledge on Curseforge or Modrinth (if working on modplatform api) + ## Prerequisites: @@ -20,11 +22,11 @@ ## Getting Started -First you want to get started by creating a `.env` and copy and paste the `.env.example` to the `.env`. +First you want to get started by creating a `.env` and copy and paste the `.env.example` to the `.env` or settting the following varaibles below. -The Client_ID is the client id from your [Azure/EntraId App](https://entra.microsoft.com/) -The Client_Secret is the client Secret from your [Azure/EntraId App](https://entra.microsoft.com/) +The `Client_ID` is the client id from your [Azure/EntraId App](https://entra.microsoft.com/) +The `Client_Secret` is the client Secret from your [Azure/EntraId App](https://entra.microsoft.com/) Note in your [Azure/EntraId App](https://entra.microsoft.com/) you need to set the redirect url to `http://localhost:PORT` The Port is `PORT`. Useally you want to set it to the port that you specifyed but if you haven't set it yet the deafult port is 8000 or `http://localhost:8000`. @@ -32,15 +34,15 @@ Note in your [Azure/EntraId App](https://entra.microsoft.com/) you need to set t ### Testing/Benchmarks/Running -In `lib.rs` there is built in tests for testing the library using the `cargo test`. +Thoughout this repo there is built in tests for testing the library using the `cargo test`. -If you want to just try out things you can play around with stuff in `main.rs` then to run it just do a simple `cargo run` to run the main.rs for playaround stuff and testing. +If you want to just try out things you can just do a simple `cargo run` to run the cli for playaround stuff and testing. -To benchmark you must write a test in `lib.rs` based on [cargo-bench](https://doc.rust-lang.org/cargo/commands/cargo-bench.html) and it will tell you in seconds how long your change takes. +To benchmark you must write a test in thought the work you are doing based on [cargo-bench](https://doc.rust-lang.org/cargo/commands/cargo-bench.html) and it will tell you in seconds how long your change takes. ## Submiting a PR -We expect you to follow the PR Template Format and use the check list, We put that there to help you create a PR. \ No newline at end of file +We expect you to follow the PR Template Format and use the check list, We put that there to help you create a PR. diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml new file mode 100644 index 0000000..7e017b9 --- /dev/null +++ b/crates/core/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "corelib" +version.workspace = true +edition.workspace = true +publish = false + +[dependencies] +tokio = { version = "1.43.0", features = ["rt-multi-thread", "macros", "sync"] } +reqwest = { version = "0.12.12", features = ["json"], optional = true } +serde = { version = "1.0.217", features = ["derive"], optional = true } +clap = { version = "4.5.27", optional = true, features = ["derive"] } +serde_urlencoded = { version = "0.7.1", optional = true } +serde_json = { version = "1.0.137", optional = true } +base64 = { version = "0.22.1", optional = true } +ring = { version = "0.17.8", optional = true } +rand = { version = "0.8.5", optional = true } +hex = { version = "0.4.3", optional = true } +url = { version = "2.5.4", optional = true } +displaydoc = "0.2.5" +thiserror = "2.0.11" +bytes = "1.9.0" +sha-1 = "0.10.1" + +[features] +default = ["auth", "cli", "launch", "modrinth", "curseforge"] +auth = [ + "dep:serde_urlencoded", + "dep:serde_json", + "dep:reqwest", + "dep:serde", + "dep:rand", + "dep:url", +] +launch = ["dep:serde_json", "dep:serde", "dep:reqwest"] +refresh = ["auth"] +# Mod Platforms +modrinth = ["dep:reqwest"] +curseforge = [] +cli = ["dep:clap", "auth"] diff --git a/crates/core/src/auth/microsoft.rs b/crates/core/src/auth/microsoft.rs new file mode 100644 index 0000000..41aa821 --- /dev/null +++ b/crates/core/src/auth/microsoft.rs @@ -0,0 +1,305 @@ +#![forbid(unsafe_code)] +#![warn(clippy::pedantic)] + +use std::collections::HashMap; + +use crate::{MOJANG_REDIR_URL, errors::AuthErrors, trait_alias::*}; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use tokio::{io::AsyncReadExt, net::TcpListener, sync::mpsc}; + +pub const SCOPE: &str = "XboxLive.signin%20XboxLive.offline_access"; + +/// Temporary http server Infomation. +#[derive(Deserialize, Debug)] +pub struct OuathInfo { + /// The code + pub code: Option, + /// State of Oauth (Default: 12345) + pub state: Option, + error: Option, + error_description: Option, +} + +pub fn ouath(port: u16) -> Result>, AuthErrors> { + let (tx, mut rx) = mpsc::channel::(1); + + let server = tokio::spawn(async move { + match TcpListener::bind(format!("127.0.0.1:{}", port)).await { + Ok(listener) => { + loop { + match listener.accept().await { + Ok((mut socket, _)) => { + let tx = tx.clone(); + tokio::spawn(async move { + let mut buf = [0; 1024]; + loop { + let n = match socket.read(&mut buf).await { + Ok(n) if n == 0 => break, + Ok(n) => n, + Err(e) => { + let err = AuthErrors::SocketReadError(format!( + "failed to read from socket; err = {:?}", + e + )); + eprintln!("{}", err); + break; + } + }; + + // Here you would parse the received data into your `Info` struct + // For demonstration, let's assume we have a function `parse_info` that does this + match parse_oauth(&buf[..n]) { + Ok(info) => { + if let Err(e) = tx.try_send(info) { + let err = AuthErrors::ChannelSendError(format!( + "failed to send data to channel; err = {:?}", + e + )); + eprintln!("{}", err); + } + } + Err(e) => { + let err = AuthErrors::ParseInfoError(format!( + "failed to parse info; err = {:?}", + e + )); + eprintln!("{}", err); + } + } + } + }); + } + Err(e) => { + let err = AuthErrors::AcceptConnectionError(format!( + "failed to accept connection; err = {:?}", + e + )); + eprintln!("{}", err); + } + } + } + } + Err(e) => { + let err = AuthErrors::BindError(format!("failed to bind listener; err = {:?}", e)); + eprintln!("{}", err); + } + } + }); + + Ok(async move { + let info = rx.recv().await.expect("server did not receive params"); + + if info.error.as_ref().map_or(false, |s| !s.is_empty()) + && info + .error_description + .as_ref() + .map_or(false, |s| !s.is_empty()) + { + let err = AuthErrors::AuthenticationFailure(info.error_description.unwrap()); + Err(err) + } else { + server.abort(); + + Ok(info) + } + }) +} + +fn parse_oauth(data: &[u8]) -> Result { + let data_str = std::str::from_utf8(data) + .map_err(|_| AuthErrors::ParseError("Invalid UTF-8".to_string()))?; + + let mut query_start = None; + while query_start.is_none() { + query_start = data_str.find('?'); + } + let query_start = + query_start.ok_or_else(|| AuthErrors::ParseError("No query string found".to_string()))?; + let query_end = data_str.find('#').unwrap_or_else(|| data_str.len()); + let query_string = &data_str[query_start + 1..query_end]; + + let query_params: Vec<(String, String)> = url::form_urlencoded::parse(query_string.as_bytes()) + .into_owned() + .collect(); + let code = query_params + .iter() + .find_map(|(k, v)| if k == "code" { Some(v.clone()) } else { None }); + let state = query_params.iter().find_map(|(k, v)| { + if k == "state" { + let http_start = v.find(" HTTP/1.1\r\n"); + http_start.map(|pos| v[..pos].to_string()) + } else { + None + } + }); + let error = query_params + .iter() + .find_map(|(k, v)| if k == "error" { Some(v.clone()) } else { None }); + let error_description = query_params.iter().find_map(|(k, v)| { + if k == "error_description" { + Some(v.clone()) + } else { + None + } + }); + + let info = OuathInfo { + code, + state, + error, + error_description, + }; + Ok(info) +} + +/// OAuth Auth Token Infomation. +#[derive(Deserialize, Debug)] +pub struct OuathToken { + pub expires_in: u64, + pub access_token: String, + pub refresh_token: String, +} + +pub fn ouath_token( + client: reqwest::Client, + refresh_token: Option, + code: Option<&str>, + client_id: &str, + scope: &str, + redirect_uri: String, + client_secret: Option<&str>, +) -> impl AsyncSendSync> { + let url = if redirect_uri == MOJANG_REDIR_URL { + "https://login.live.com/oauth20_token.srf" + } else { + "https://login.microsoftonline.com/consumers/oauth2/v2.0/token" + }; + + // Use owned Strings for both keys and values + let mut form = HashMap::new(); + form.insert("client_id".to_string(), client_id.to_string()); + form.insert("scope".to_string(), scope.to_string()); + + if let Some(secret) = client_secret { + form.insert("client_secret".to_string(), secret.to_string()); + } + + if let Some(token) = refresh_token { + form.insert("grant_type".to_string(), "refresh_token".to_string()); + form.insert("refresh_token".to_string(), token); // token is already a String + } else if let Some(auth_code) = code { + form.insert("grant_type".to_string(), "authorization_code".to_string()); + form.insert("code".to_string(), auth_code.to_string()); + form.insert("redirect_uri".to_string(), redirect_uri); + } + + async move { + 'out: { + let result = client.post(url).form(&form).send().await; + + let std::result::Result::Ok(response) = result else { + break 'out Err(AuthErrors::ResponseError( + "Failed to send request".to_string(), + )); + }; + + let text = response + .text() + .await + .map_err(|_| AuthErrors::ResponseError("Failed to send request".to_string()))?; + + let std::result::Result::Ok(token) = serde_json::from_str(&text) else { + break 'out Err(AuthErrors::ResponseError( + "Failed to send request, Check your Client Secret.".to_string(), + )); + }; + std::result::Result::Ok(token) + } + } +} + +// Device Code + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CodeResponse { + pub device_code: String, + pub user_code: String, + pub verification_uri: String, + pub expires_in: u32, + pub interval: u16, + pub message: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct AuthenticationResponse { + pub expires_in: u16, + access_token: String, +} + +/// Defines expiry and token +#[derive(Debug)] +pub struct CodeInfo { + /// Provides expiry + pub expires_in: u16, + /// Provides token + pub token: String, +} + +pub fn device_authentication_code( + client: reqwest::Client, + client_id: &str, +) -> impl AsyncSendSync> { + let request_url = + format!("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode",); + let body = format!("client_id={}&scope={}", client_id, SCOPE); + + device_internal(client, request_url, body) +} + +async fn device_internal( + client: reqwest::Client, + request_url: String, + body: String, +) -> Result { + let response = client.post(request_url).body(body).send().await?; + + let response_data: CodeResponse = response.json().await?; + + Ok(response_data) +} + +pub fn authenticate_device( + client: reqwest::Client, + device_code: &str, + client_id: &str, +) -> impl AsyncSendSync> { + let request_url = format!("https://login.microsoftonline.com/common/consumers/v2.0/token",); + + let body = format!( + "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id={}&device_code={}", + client_id, device_code + ); + + authenticate_device_internal(request_url, body, client) +} + +async fn authenticate_device_internal( + request_url: String, + body: String, + client: reqwest::Client, +) -> Result { + let request = client + .post(request_url) + .body(body) + .header("Content-Type", "application/x-www-form-urlencoded") + .send() + .await?; + + let response_data: AuthenticationResponse = request.json().await?; + + let expires_in = response_data.expires_in; + let token = response_data.access_token; + + Ok(CodeInfo { expires_in, token }) +} diff --git a/src/custom/mojang.rs b/crates/core/src/auth/mod.rs similarity index 88% rename from src/custom/mojang.rs rename to crates/core/src/auth/mod.rs index 361b836..b3f78bd 100644 --- a/src/custom/mojang.rs +++ b/crates/core/src/auth/mod.rs @@ -1,12 +1,13 @@ -#![forbid(unsafe_code, missing_docs)] -#![warn(clippy::pedantic)] +use std::error::Error; use reqwest::Client; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -use std::error::Error; -use crate::async_trait_alias::AsyncSendSync; +use crate::trait_alias::AsyncSendSync; + +pub mod microsoft; +pub mod xbox; /// Defines the custom authentication data received from Mojang. /// @@ -20,14 +21,14 @@ pub struct AuthInfo { /// This token is used for making authenticated requests to Mojang's APIs, such as launching the game /// or accessing user-specific data. It is crucial for the authentication process and should be /// securely stored and used. - pub access_token: String, + pub access_token: Option, /// The UUID of the authenticated user. /// /// This UUID is not the player's Minecraft UUID but a unique identifier generated by Mojang for /// authentication purposes. It is used for launching the game and should be treated as sensitive /// information. - pub uuid: String, + pub uuid: Option, /// The expiry time of the access token in seconds. /// @@ -51,19 +52,19 @@ struct MojangResponse { expires_in: i32, } -pub fn token( +pub fn bearer_token( + client: Client, userhash: &str, xsts_token: &str, ) -> impl AsyncSendSync>> { - let client = Client::new(); let identity_token = format!("XBL3.0 x={};{}", userhash, xsts_token); let body = json!({ "identityToken": identity_token }); - tokeninternal(client, body) + bearer_token_internal(client, body) } -async fn tokeninternal(client: Client, body: Value) -> Result> { +async fn bearer_token_internal(client: Client, body: Value) -> Result> { let res = client .post("https://api.minecraftservices.com/authentication/login_with_xbox") .body(body.to_string()) @@ -81,8 +82,8 @@ async fn tokeninternal(client: Client, body: Value) -> Result impl AsyncSendSync> { - let client = Client::new(); +pub fn xbl( + client: Client, + token: &str, + redirect_url: String, +) -> impl AsyncSendSync> { let url = format!("https://user.auth.xboxlive.com/user/authenticate"); let rps_ticket = format!("d={}", token); let mut headers = HeaderMap::new(); - headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); headers.insert(ACCEPT, HeaderValue::from_static("application/json")); let body = json!({ "Properties": { "AuthMethod": "RPS", "SiteName": "user.auth.xboxlive.com", - "RpsTicket": rps_ticket, + "RpsTicket": if redirect_url == MOJANG_REDIR_URL { + token + } else { + &rps_ticket + }, }, "RelyingParty": "http://auth.xboxlive.com", "TokenType": "JWT" @@ -55,7 +61,7 @@ async fn xbl_internal( url: String, headers: HeaderMap, body: Value, -) -> Result { +) -> Result { let result = client .post(url) .headers(headers) @@ -65,18 +71,19 @@ async fn xbl_internal( let std::result::Result::Ok(response) = result else { println!("Part 1"); - return Err(XboxError::ResponseError( - "Failed to send request".to_string(), + return Err(AuthErrors::ResponseError( + "Failed to send request to xbox".to_string(), )); }; + let text = response .text() .await - .map_err(|_| XboxError::ResponseError("Failed to send request".to_string()))?; + .map_err(|_| AuthErrors::ParseError("Failed to pass text from xbox".to_string()))?; let std::result::Result::Ok(token) = serde_json::from_str::(&text) else { - return Err(XboxError::ResponseError( - "Failed to send request".to_string(), + return Err(AuthErrors::ParseError( + "Failed to pass text to json".to_string(), )); }; std::result::Result::Ok(token) @@ -91,10 +98,11 @@ pub struct XtsOutput { pub display_claims: DisplayClaims, } -pub fn xsts_token( +pub fn xsts( + client: Client, xbl_token: &str, bedrock_rel: bool, -) -> impl AsyncSendSync> { +) -> impl AsyncSendSync> { let url = format!("https://xsts.auth.xboxlive.com/xsts/authorize"); let bedrock_party = "https://pocket.realms.minecraft.net/"; let java_party = "rp://api.minecraftservices.com/"; @@ -104,7 +112,6 @@ pub fn xsts_token( java_party }; - let client = Client::new(); let mut headers = header::HeaderMap::new(); headers.insert( "Content-Type", @@ -133,7 +140,7 @@ async fn xsts_internal( url: String, body: Value, headers: HeaderMap, -) -> Result { +) -> Result { let result = client .post(url) .body(body.to_string()) @@ -143,18 +150,18 @@ async fn xsts_internal( let std::result::Result::Ok(response) = result else { println!("Part 1"); - return Err(XTSError::ResponseError( - "Failed to send request".to_string(), + return Err(AuthErrors::ResponseError( + "Failed to return ok for xts".to_string(), )); }; let text = response .text() .await - .map_err(|_| XTSError::ResponseError("Failed to read response text".to_string()))?; + .map_err(|_| AuthErrors::ParseError("Failed to parse text from xts".to_string()))?; let std::result::Result::Ok(token) = serde_json::from_str::(&text) else { println!("Part 2"); - return Err(XTSError::ResponseError( - "Failed to parse response".to_string(), + return Err(AuthErrors::ParseError( + "Failed to parse text to json from xts".to_string(), )); }; Ok(token) diff --git a/crates/core/src/curseforge/mod.rs b/crates/core/src/curseforge/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/core/src/errors.rs b/crates/core/src/errors.rs new file mode 100644 index 0000000..2ef0939 --- /dev/null +++ b/crates/core/src/errors.rs @@ -0,0 +1,57 @@ +#![forbid(unsafe_code, missing_docs)] +#![warn(clippy::pedantic)] + +use displaydoc::Display; +use thiserror::Error; + +/// The `AuthErrors` represents potential errors that can occur during Authentication. +#[derive(Display, Error, Debug)] +#[cfg(feature = "auth")] +pub enum AuthErrors { + /// Authentcation Failed: {0} + AuthenticationFailure(String), + /// Parsing Failed: {0} + ParseError(String), + /// Binding error: {0} + BindError(String), + /// Socket Read Error: {0} + SocketReadError(String), + /// Failed to send data to channel: {0} + ChannelSendError(String), + /// Failed to parse info: {0} + ParseInfoError(String), + /// Failed to accept connection: {0} + AcceptConnectionError(String), + /// Response Failed: {0} + ResponseError(String), + /// Mismatched authentication type: {0} + MismatchedAuthType(String), +} + +/// The `LaunchErrors` enum represents potential errors that can occur during Launch-related operations. +#[derive(Display, Error, Debug)] +#[cfg(feature = "launch")] +pub enum LaunchErrors { + /// Unsupported Device: {0} + UnsupportedDevice(String), + /// Failed to fetch username {0} + UsernameFetchError(String), + /// Unsuported Archtechure: {0} switch to another cpu archtechure and try again. + UnsupportedArchitecture(String), + /// Response Failed: {0} + ResponseError(String), + /// Requirements not reached: {0} + Requirements(String), +} + +/// Errors that can occur while interacting with the Modplatforms. +#[derive(Display, Debug, thiserror::Error)] +#[cfg(any(feature = "modrinth", feature = "curseforge"))] +pub enum ModPlatformsErrors { + /// The request failed: {0}. + RequestError(String), + /// The response could not be deserialized: {0}. + DeserializationError(String), + /// The response could not be serialized: {0}. + SerializationError(String), +} diff --git a/crates/core/src/launch/java.rs b/crates/core/src/launch/java.rs new file mode 100644 index 0000000..afd178d --- /dev/null +++ b/crates/core/src/launch/java.rs @@ -0,0 +1,147 @@ +use std::{env, fs, path::PathBuf}; + +use super::download_files; +use crate::{HTTP::Client, errors::LaunchErrors, trait_alias::AsyncSendSync}; + +/// Java Runtime Environment (JRE) for Minecraft. +#[derive(Debug, Clone)] +pub enum JRE { + Adoptium, + Zulu, + GraalVM, +} + +struct ArchUrl { + arch: Option<&'static str>, + os: Option<&'static str>, + url: String, +} + +fn java_url(jre: JRE, version: &str) -> Option { + let os = env::consts::OS; + let arch = env::consts::ARCH; + + match jre { + JRE::Adoptium => { + arch_support(&["x86_64", "x86", "aarch64", "arm"]).ok()?; + + let urls = vec![ + ArchUrl { + arch: Some("x86_64"), + os: None, + url: format!( + "https://api.adoptium.net/v3/binary/latest/{}/ga/{}/x64/jre/hotspot/normal/eclipse", + version, os + ), + }, + ArchUrl { + arch: None, + os: None, + url: format!( + "https://api.adoptium.net/v3/binary/latest/{}/ga/{}/{}/jre/hotspot/normal/eclipse", + version, os, arch + ), + }, + ]; + arch_url(urls) + } + + JRE::GraalVM => { + arch_support(&["x86_64", "x86", "aarch64"]).ok()?; + + let urls = vec![ + ArchUrl { + arch: Some("x86"), + os: Some("windows"), + url: format!( + "https://download.oracle.com/graalvm/{}/latest/graalvm-jdk-{}_windows-x64_bin.zip", + version, version + ), + }, + ArchUrl { + arch: None, + os: None, + url: format!( + "https://download.oracle.com/graalvm/{}/latest/graalvm-jdk-{}_{}-{}_bin.tar.gz", + version, version, os, arch + ), + }, + ]; + arch_url(urls) + } + JRE::Zulu => todo!(), + } +} + +pub fn get_java( + client: Client, + dir: &PathBuf, + version: &str, + jre: JRE, + user_agent: &str, +) -> impl AsyncSendSync> { + let output_path = { + let mut path = dir.clone(); + if cfg!(target_os = "windows") { + path.push("jre.zip"); + } else { + path.push("jre.tar.gz"); + } + path + }; + + let user_agent = user_agent.to_owned(); + let version = version.to_owned(); + let jre = jre.clone(); + + async move { + let url = java_url(jre, &version).ok_or_else(|| { + LaunchErrors::Requirements("Unsupported platform or JRE URL not found".to_string()) + })?; + + download_jre(client, url, output_path, &user_agent).await + } +} + +async fn download_jre( + client: Client, + url: String, + path: PathBuf, + user_agent: &str, +) -> Result<(), LaunchErrors> { + download_files(client.clone(), user_agent, &path, url) + .await + .map_err(|e| LaunchErrors::Requirements(format!("Failed to download JRE due to: {}", e)))?; + + if path.exists() && path.is_file() { + fs::remove_file(&path) + .map_err(|e| LaunchErrors::Requirements(format!("Failed to clean up file: {}", e)))?; + } + + Ok(()) +} + +fn arch_url(candidates: Vec) -> Option { + let arch = env::consts::ARCH; + let os = env::consts::OS; + + candidates + .into_iter() + .find_map(|entry| match (entry.arch, entry.os) { + (Some(a), Some(o)) if a == arch && o == os => Some(entry.url), + (Some(a), None) if a == arch => Some(entry.url), + (None, Some(o)) if o == os => Some(entry.url), + (None, None) => Some(entry.url), + _ => None, + }) +} + +fn arch_support(supported: &[&str]) -> Result<(), LaunchErrors> { + if supported.contains(&env::consts::ARCH) { + Ok(()) + } else { + Err(LaunchErrors::UnsupportedArchitecture( + env::consts::ARCH.to_string(), + )) + } +} diff --git a/crates/core/src/launch/minecraft.rs b/crates/core/src/launch/minecraft.rs new file mode 100644 index 0000000..3b8c65f --- /dev/null +++ b/crates/core/src/launch/minecraft.rs @@ -0,0 +1,33 @@ +use crate::{ + structs::{ManifestVersion, VersionManifest}, + MANIFEST_URL, + HTTP::{header::USER_AGENT, Client} +}; + +pub async fn get_version_manifest( + client: Client, + url: &str, + user_agent: &str, +) -> Result> { + let result = client + .get(url) + .header(USER_AGENT, user_agent) + .send() + .await?; + + let version_manifest: VersionManifest = result.json().await?; + + Ok(version_manifest) +} + +pub async fn get_manifest(client: Client, user_agent: &str) -> Result> { + let result = client + .get(MANIFEST_URL) + .header(USER_AGENT, user_agent) + .send() + .await?; + + let manifest: ManifestVersion = result.json().await?; + + Ok(manifest) +} diff --git a/crates/core/src/launch/mod.rs b/crates/core/src/launch/mod.rs new file mode 100644 index 0000000..99c7e25 --- /dev/null +++ b/crates/core/src/launch/mod.rs @@ -0,0 +1,40 @@ +mod java; +mod minecraft; + +pub use java::JRE as JavaJRE; +use reqwest::{header::USER_AGENT, Client}; +use std::{fs::File, io::Write, path::PathBuf}; + +pub(crate) async fn download_files( + client: Client, + user_agent: &str, + dir: &PathBuf, + url: String, +) -> Result<(), Box> { + let response = client + .get(url) + .header(USER_AGENT, user_agent) + .send() + .await?; + + let content = response.bytes().await?; + + let mut dest = File::create(dir)?; + dest.write_all(&content)?; + return Ok(()); +} + +pub(crate) async fn launch_minecraft( + args: Vec, + dir: &PathBuf, + user_agent: &str, +) -> Result<(), Box> { + let client = Client::new(); + let manifest = minecraft::get_manifest(client.clone(), user_agent).await?; + let version_url = manifest.versions.url.clone(); + let version_manifest = minecraft::get_version_manifest(client, &version_url, user_agent).await?; + + println!("Version Manifest: {:#?}", version_manifest); + + Ok(()) +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs new file mode 100644 index 0000000..5410b3a --- /dev/null +++ b/crates/core/src/lib.rs @@ -0,0 +1,46 @@ +#![forbid(unsafe_code)] +#![warn(clippy::pedantic)] + +pub use reqwest as HTTP; + +// Modules +/// Error handling module for the Minecraft-Essentials library. +/// +/// This module contains all the error types and related functionality +/// for error handling within the library. +pub mod errors; + +/// Trait aliases for libraires +pub mod trait_alias; + +/// Structs module for the Minecraft-Essentials library. +/// +/// This module contains all the structs and related functionality +/// for structs within the library. +pub mod structs; +#[cfg(test)] +mod tests; + +#[cfg(feature = "launch")] +/// Launch module for the Minecraft-Essentials library. +pub mod launch; + +#[cfg(feature = "auth")] +pub mod auth; + +#[cfg(feature = "modrinth")] +pub mod modrinth; + +#[cfg(feature = "auth")] +pub use auth::AuthInfo as CustomAuthData; + +// Constants +pub const EXPERIMENTAL_MESSAGE: &str = + "\x1b[33mNOTICE: You are using an experimental feature.\x1b[0m"; + +#[cfg(feature = "launch")] +pub(crate) const MANIFEST_URL: &str = + "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; + +#[cfg(feature = "auth")] +pub(crate) const MOJANG_REDIR_URL: &str = "https://login.live.com/oauth20_desktop.srf"; diff --git a/crates/core/src/modrinth/mod.rs b/crates/core/src/modrinth/mod.rs new file mode 100644 index 0000000..be73a37 --- /dev/null +++ b/crates/core/src/modrinth/mod.rs @@ -0,0 +1,4 @@ +pub(crate) const MODRINTH_API: &str = "https://api.modrinth.com/v2"; + +/// Modrinth Projects Api +pub mod projects; diff --git a/crates/core/src/modrinth/projects.rs b/crates/core/src/modrinth/projects.rs new file mode 100644 index 0000000..0feaa98 --- /dev/null +++ b/crates/core/src/modrinth/projects.rs @@ -0,0 +1,152 @@ +use crate::HTTP::Client; +use crate::errors::ModPlatformsErrors; + +use super::MODRINTH_API; +use serde::{Deserialize, Serialize}; + +/// Represents a complete Modrinth project entity containing all metadata and relationships. +#[derive(Deserialize, Debug)] +pub struct ModrinthProject { + /// The display title of the project. + pub title: String, + + /// Detailed description of the project. + pub description: String, + + /// Primary categories the project belongs to. + pub categories: Vec, + + /// Indicates client-side compatibility requirements. + pub client_side: String, + + /// Indicates server-side compatibility requirements. + pub server_side: String, + + /// Main content body of the project description. + pub body: String, + + /// Current approval status of the project. + pub status: String, + + /// Additional categorizations beyond primary categories. + pub additional_categories: Vec, + + /// URL for reporting issues related to the project. + pub issues_url: Option, + + /// Source code repository URL if available. + pub source_url: Option, + + /// Documentation/wiki URL for the project. + pub wiki_url: Option, + + /// Discord community URL for the project. + pub discord_url: Option, + + /// Collection of donation URLs supporting the project. + pub donation_urls: Vec, + + /// Type classification of the project (mod, resourcepack, etc.). + pub project_type: String, + + /// Total number of downloads for the project. + pub downloads: i64, + + /// URL pointing to the project's icon/image. + pub icon_url: String, + + /// Timestamp when the project was first published. + pub published: String, + + /// Timestamp of the most recent update. + pub updated: String, + + /// Timestamp when the project was approved. + pub approved: String, + + /// Number of users following the project. + pub followers: i64, + + /// Licensing information for the project. + pub license: License, + + /// List of all versions available for the project. + pub versions: Vec, + + /// Compatible Minecraft game versions. + pub game_versions: Vec, + + /// Supported mod loaders for installation. + pub loaders: Vec, + + /// Optional direct link to the project body content. + pub body_url: Option, + + /// Indicates if the project is queued for moderation. + pub queued: Option, + + /// Message from moderators regarding the project. + pub moderator_message: Option, +} + +/// Represents licensing information for a project. +#[derive(Deserialize, Debug)] +pub struct License { + /// Unique identifier for the license. + pub id: String, + + /// Human-readable name of the license. + pub name: String, + + /// Optional URL linking to the full license text or details. + pub url: Option, +} + +/// Represents a single donation URL endpoint for supporting a project. +#[derive(Serialize, Deserialize, Debug)] +pub struct DonationUrl { + /// Unique identifier for this donation platform. + pub id: String, + + /// Name of the donation platform (e.g., Patreon, Ko-fi). + pub platform: String, + + /// Direct URL where users can donate to support the project. + pub url: String, +} + + +/// Get a single project from modrinth. +pub async fn get_project( + client: Client, + project: &str, + user_agent: &str, +) -> Result { + let url = format!("{}/project/{}", MODRINTH_API, project); + let res = client + .get(url) + .header("User-Agent", user_agent) + .send() + .await + .map_err(|err| { + ModPlatformsErrors::RequestError(format!("Failed to request to modrinth: {}", err.to_string())) + })?; + + let modrinth_project: ModrinthProject = res.json().await.map_err(|err| { + ModPlatformsErrors::DeserializationError(format!( + "Failed to deserialize modrinth error: {}", + err + )) + })?; + + Ok(modrinth_project) +} + + +pub async fn get_multiple_projects(client: Client, user_agent: &str, ) -> Result<(), ModPlatformsErrors> { + let mut params: Vec = vec![]; + let url = format!("{}/search", MODRINTH_API); + // Maxium Limit is 100. + + Ok(()) +} diff --git a/crates/core/src/structs.rs b/crates/core/src/structs.rs new file mode 100644 index 0000000..ca96d7d --- /dev/null +++ b/crates/core/src/structs.rs @@ -0,0 +1,125 @@ +use serde::Deserialize; + +// Dry (Do not Repeat Yourself) + +#[derive(Deserialize, Clone, Debug)] +pub(crate) enum ManifestArgs { + Simple(String), + ComplexArgs, +} + +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct ManifestComplexArgs { + pub(crate) rules: Vec, + pub(crate) value: Vec, +} + +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct Rules { + pub(crate) action: String, + pub(crate) features: Option, + pub(crate) os: Option, +} + +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct RulesFutures {} + +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct RulesOS { + pub(crate) name: String, + pub(crate) arch: String, +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct FileIndex { + pub(crate) id: Option, + pub(crate) sha1: Option, + pub(crate) path: Option, + pub(crate) size: Option, + pub(crate) totalsize: Option, + pub(crate) url: Option, +} + +/////////......///// + +// Manifest Json + +#[derive(Deserialize, Debug, Clone)] +pub(crate) struct ManifestVersion { + pub(crate) latest: LatestVersion, + pub(crate) versions: VersionEntry, +} + +#[derive(Deserialize, Debug, Clone)] +pub(crate) struct LatestVersion { + pub(crate) release: String, + pub(crate) snapshot: String, +} + +#[derive(Deserialize, Debug, Clone)] +pub(crate) struct VersionEntry { + pub(crate) id: String, + #[serde(rename = "type")] + pub(crate) version_type: String, + pub(crate) url: String, + pub(crate) time: String, + pub(crate) release_time: String, + pub(crate) sha1: String, + pub(crate) compliance_level: i32, +} + +/////////......///// + +// Version Manifest + +#[derive(Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct VersionManifest { + // #[serde(rename = "type")] + pub(crate) arguments: Arguments, + pub(crate) assetindex: FileIndex, + pub(crate) assets: String, + pub(crate) compliancelevel: i32, + pub(crate) downloads: Downloads, + pub(crate) id: String, + pub(crate) javaversion: JavaVersion, + pub(crate) libraries: Vec, +} + +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct Arguments { + pub(crate) game: ManifestArgs, + pub(crate) jvm: ManifestArgs, +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct JavaVersion { + pub(crate) component: String, + pub(crate) majorversion: i32, + pub(crate) minorversion: i32, +} + +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct Libraries { + pub(crate) donwloads: FileIndex, + pub(crate) name: String, +} + +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct Logging { + pub(crate) argument: String, + pub(crate) file: FileIndex, + #[serde(rename = "type")] + pub(crate) log_type: String, +} + +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct Downloads { + pub(crate) artiface: Option, + pub(crate) client: Option, + pub(crate) client_mappings: Option, + pub(crate) server: Option, + pub(crate) server_mappings: Option, +} diff --git a/crates/core/src/tests.rs b/crates/core/src/tests.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/core/src/tests.rs @@ -0,0 +1 @@ + diff --git a/src/async_trait_alias.rs b/crates/core/src/trait_alias.rs similarity index 70% rename from src/async_trait_alias.rs rename to crates/core/src/trait_alias.rs index f5f0737..b69605e 100644 --- a/src/async_trait_alias.rs +++ b/crates/core/src/trait_alias.rs @@ -1,8 +1,14 @@ -#![forbid(unsafe_code, missing_docs)] +#![forbid(unsafe_code)] #![warn(clippy::pedantic)] use std::future::Future; +// An Optoinal Traits +pub trait Optional: Into> {} +impl Optional for T where T: Into> {} +impl Optional for Option {} + +// Async Traits pub trait Async: Future {} impl Async for T where T: Future {} pub trait AsyncSend: Send + Async {} diff --git a/crates/minecraft-essentails/Cargo.toml b/crates/minecraft-essentails/Cargo.toml new file mode 100644 index 0000000..8c93323 --- /dev/null +++ b/crates/minecraft-essentails/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "minecraft-essentials" +documentation = "https://docs.rs/minecraft-essentials" +version.workspace = true +keywords.workspace = true +authors.workspace = true +description.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true +edition.workspace = true + +[build] +rustdocflags = ["--theme=ayu"] + +[[bin]] +name = "minecraft-essentials" +path = "src/main.rs" + +[package.metadata.binstall] +pkg-url = "{ repo }/releases/download/v{ version }/{ name }-cli" +bin-dir = "{ name }-{ target }-v{ version }/{ bin }{ binary-ext }" +pkg-fmt = "tgz" + +[package.metadata.binstall.overrides.x86_64-pc-windows-msvc] +pkg-fmt = "zip" + +[package.metadata.binstall.overrides.aarch64-pc-windows-msvc] +pkg-fmt = "zip" + + +[dependencies] +corelib = { path = "../core", default-features = false} +clap = { version = "4.5.27", features = ["derive"], optional = true } +tokio = { version = "1.43.0", features = ["macros"] } +serde = "1.0.217" + +[dev-dependencies] +dotenv = "0.15.0" +tokio = { version = "1.43.0", features = ["macros", "tokio-macros"] } + +[features] +default = ["auth", "cli", "launch", "modrinth", "curseforge"] +auth = ["corelib/auth"] +launch = ["corelib/launch"] +refresh = ["corelib/refresh"] +# Mod Platforms +modrinth = ["corelib/modrinth"] +curseforge = ["corelib/curseforge"] +cli = ["dep:clap", "auth"] diff --git a/crates/minecraft-essentails/src/cwd.rs b/crates/minecraft-essentails/src/cwd.rs new file mode 100644 index 0000000..7146ef0 --- /dev/null +++ b/crates/minecraft-essentails/src/cwd.rs @@ -0,0 +1,60 @@ +use std::path::PathBuf; + +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser)] +#[command(version, long_about = None)] +pub(crate) struct Cli { + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand)] +pub(crate) enum Commands { + Version {}, + /// Oauth Check command. + Oauth(OauthArgs), + /// DeviceCode Check command. + DeviceCode(DeviceCodeArgs), + /// Minecraft Launching Check command. + Launch(LaucnhArgs), +} + +#[derive(Args)] +pub(crate) struct OauthArgs { + pub client_id: String, + pub client_secret: String, + pub port: Option, + pub bedrockrelm: Option, +} + +#[derive(Args)] +pub(crate) struct LaucnhArgs { + // Java Args + min_memory: usize, + max_memory: Option, + launcher_name: Option, + launcher_version: Option, + jre: Option, + class_path: Option, + + // Game Args + client_id: Option, + username: Option, + version: Option, + uuid: Option, + game_directory: Option, + width: Option, + height: Option, + access_token: Option, + + // Quick Play Args + quick_play_singleplayer: Option, + quick_play_multiplayer: Option, +} + +#[derive(Args)] +pub(crate) struct DeviceCodeArgs { + pub client_id: String, + pub bedrockrelm: bool, +} diff --git a/crates/minecraft-essentails/src/lib.rs b/crates/minecraft-essentails/src/lib.rs new file mode 100644 index 0000000..acac929 --- /dev/null +++ b/crates/minecraft-essentails/src/lib.rs @@ -0,0 +1,787 @@ +#![doc = include_str!("../../../README.md")] +#![forbid(unsafe_code, missing_docs)] +#![warn(clippy::pedantic)] + +// Modules +/// Error handling module for the Minecraft-Essentials library. +/// +/// This module contains all the error types and related functionality +/// for error handling within the library. +pub use corelib::errors; +pub(crate) use corelib::trait_alias; + +/// Structs module for the Minecraft-Essentials library. +/// +/// This module contains all the structs and related functionality +/// for structs within the library. +pub use corelib::structs; +#[cfg(test)] +mod tests; + +#[cfg(feature = "launch")] +/// Launch module for the Minecraft-Essentials library. +use corelib::launch; + +#[cfg(feature = "auth")] +use corelib::auth; + +#[cfg(feature = "modrinth")] +use corelib::modrinth; + +use std::path::PathBuf; + +use corelib::{ + HTTP::{self, Client}, + auth::microsoft::CodeResponse, +}; + +#[cfg(feature = "auth")] +pub use corelib::auth::AuthInfo as CustomAuthData; + +#[cfg(feature = "auth")] +use auth::{ + bearer_token, + microsoft::{SCOPE, authenticate_device, device_authentication_code, ouath, ouath_token}, + xbox::{xbl, xsts}, +}; + +#[cfg(feature = "launch")] +use launch::JavaJRE; + +use serde::{Deserialize, Serialize}; +use trait_alias::Optional; + +// Constants +pub(crate) const EXPERIMENTAL_MESSAGE: &str = + "\x1b[33mNOTICE: You are using an experimental feature.\x1b[0m"; + +#[cfg(feature = "launch")] +pub(crate) const MANIFEST_URL: &str = + "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"; +#[cfg(feature = "auth")] +pub(crate) const LAUNCHER_CLIENT_ID: &str = "00000000402B5328"; + +/// OAuth 2.0 Authentication +/// +/// This struct represents the OAuth authentication process for Minecraft, specifically designed for use with custom Azure applications. +/// It is used to authenticate a user and obtain a token that can be used to launch Minecraft. +#[cfg(feature = "auth")] +#[deprecated( + note = "The Ouath implementation is deprecated. Please migrate to AuthenticationBuilder and utilize the Oauth type for authentication.", + since = "0.2.12" +)] +pub struct Oauth { + port: u16, + client_id: String, +} + +#[cfg(feature = "auth")] +#[deprecated( + note = "The Ouath implementation is deprecated. Please migrate to AuthenticationBuilder and utilize the Oauth type for authentication.", + since = "0.2.12" +)] +// TODO: REMOVE THIS AT 0.2.14 +impl Oauth { + /// Initializes a new `Oauth` instance. + /// + /// This method sets up the OAuth authentication process by constructing the authorization URL + /// and storing the client ID and port for later use. + /// + /// # Arguments + /// + /// * `client_id` - The client ID obtained from the Minecraft authentication service. + /// * `port` - An optional port number for the local server. Defaults to 8000 if not provided. + /// + /// # Returns + /// + /// * `Self` - A new instance of `Oauth` configured with the provided client ID and port. + pub fn new(client_id: &str, port: Option) -> Self { + let port = port.unwrap_or(8000); + Self { + client_id: client_id.to_string(), + port, + } + } + + /// Retrieves the authorization URL. + /// + /// This method returns the URL that the user needs to visit to authorize the application. + /// + /// # Returns + /// + /// * `&str` - The authorization URL. + pub async fn url(&self) -> String { + let mut builder = AuthenticationBuilder::builder(); + builder + .port(self.port) + .client_id(Some(&self.client_id)) + .of_type(AuthType::Oauth); + let auth_info = builder + .get_info() + .await + .ouath_url + .expect("Expected OAuth URL"); + auth_info.clone() + } + + /// Launches Minecraft using the OAuth authentication process. + /// + /// This method completes the OAuth authentication process by launching a local server to + /// receive the authorization code, exchanging it for an access token, and then using this token + /// to launch Minecraft. The method supports both Bedrock Edition and Java Edition of Minecraft. + /// + /// # Arguments + /// + /// * `bedrock_relm` - A boolean indicating whether to launch the Bedrock Edition of Minecraft. + /// * `client_secret` - The client secret obtained from the Minecraft authentication service. + /// + /// # Returns + /// + /// * `Result>` - A result containing the authentication data or an error if the process fails. + pub async fn launch( + &self, + bedrock_relm: bool, + client_secret: &str, + ) -> Result> { + AuthenticationBuilder::builder() + .bedrockrel(Some(bedrock_relm)) + .of_type(AuthType::Oauth) + .client_secret(Some(client_secret)) + .client_id(Some(&self.client_id)) + .port(Some(self.port)) + .launch(None, None) + .await + } + + /// Launches Oauth using method which fixes 500 issue. + /// + /// This method is intended for use with Tauri applications to launch the Ouath Method. + /// It handles the necessary fixes required + /// to launch the Oauth within a Tauri application. + /// + /// * `bedrock_relm` - A boolean indicating whether to launch the Bedrock Edition of Minecraft. + /// * `client_secret` - The client secret obtained from the Minecraft authentication service. + /// + /// # Returns + /// + /// * `Result>` - A result containing the authentication data or an error if the process fails. + + /// Refreshes the OAuth authentication process. + /// + /// This method is used to refresh the access token using the refresh token. + /// + /// # Arguments + /// + /// * `refresh_token` - The refresh token obtained from the Minecraft authentication service. + /// * `client_id` - The client ID obtained from the Minecraft authentication service. + /// * `port` - An optional port number for the local server. Defaults to 8000 if not provided. + /// * `client_secret` - The client secret obtained from the Minecraft authentication service. + /// + /// # Returns + /// + /// * `Result>` - A result containing the refreshed authentication data or an error if the process fails. + #[cfg(feature = "refresh")] + pub async fn refresh( + &self, + refresh_token: &str, + client_id: &str, + port: Option, + client_secret: &str, + ) -> Result> { + let port = port.unwrap_or(8000); + let token = oauth::token(refresh_token, client_id, port, client_secret).await?; + Ok(token) + } +} + +/// Represents the type of authentication to be used. +/// +/// This enum is used to specify the authentication method for Minecraft client launchers. +/// It supports OAuth, Device Code, Minecraft Device Code, and Minecraft OAuth authentication methods. +#[cfg(feature = "auth")] +#[derive(PartialEq, Debug)] +pub enum AuthType { + /// OAuth authentication method. + /// + /// This variant is used for OAuth 2.0 authentication with Minecraft. + #[cfg(feature = "auth")] + Oauth, + + /// Device Code authentication method. + /// + /// This variant is used for device code authentication with Minecraft. + #[cfg(feature = "auth")] + DeviceCode, + + /// Refreshing token method + /// + /// This is used for refreshing your token with Minecraft. + #[cfg(feature = "auth")] + Refresh, + + /// Minecraft authentification method + /// + /// Uses Minecraft Auth menthod (habdy if you want the minecraft background) + Minecraft, +} + +/// Represents a builder for authentication configurations. +/// +/// This struct is used to build your authentfcation with your type of authenfication options. +/// Deafults to `AuthType::OAuth` if no other option is specified. +/// It supports methods from `AuthType` and can be used to create a new instance of `Authentication` for your minecraft client launchers. +#[cfg(feature = "auth")] +pub struct AuthenticationBuilder { + auth_type: AuthType, + client_id: Option, + scope: Option, + port: Option, + client_secret: Option, + bedrockrel: bool, + refresh_token: Option, +} + +/// Represents authentication information. +/// +/// This struct holds the necessary information for authentication processes, +/// such as device codes and OAuth URLs that you'll recive. +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AuthInfo { + /// Device Code Info that you will recive based on AuthType::DeviceCode. + pub device_code: Option, + /// OAuth URL that you will recive based on AuthType::OAuth. + pub ouath_url: Option, + /// Redirect URL: Only needed when using minecraft auth + pub redirect_url: Option, +} + +#[cfg(feature = "auth")] +impl AuthenticationBuilder { + /// Creates a new instance of `AuthenticationBuilder`. + pub fn builder() -> Self { + Self { + auth_type: AuthType::Oauth, + client_id: None, + scope: Some(SCOPE.to_string()), + port: Some(8000), + client_secret: None, + bedrockrel: false, + refresh_token: None, + } + } + + /// Type of authentication. + /// + /// Sets the type of authentication to be used. + pub fn of_type(&mut self, auth_type: AuthType) -> &mut Self { + self.auth_type = auth_type; + self + } + + /// Client ID from your application Required for `OAuth` & `DeviceCode`. + pub fn client_id(&mut self, client_id: Option<&str>) -> &mut Self { + if client_id.is_some() { + self.client_id = client_id.map(|id| id.to_string()); + } + self + } + + /// Port for the Temporary https Required for `OAuth``. + pub fn port>(&mut self, port: T) -> &mut Self { + let port = match port.into() { + Some(port) => Some(port), + None => None, + }; + self.port = port; + self + } + + /// Client Secret from your application Required for `OAuth` & `DeviceCode`. + pub fn client_secret(&mut self, client_secret: Option<&str>) -> &mut Self { + if client_secret.is_some() { + self.client_secret = client_secret.map(|secret| secret.to_string()); + } + self + } + + /// Ability to set scope for oauth + pub fn scope(&mut self, scope: Option) -> &mut Self { + self.scope = scope; + self + } + + /// Bedrock relm related that only need xts token not bearer. + pub fn bedrockrel>(&mut self, bedrock_rel: T) -> &mut Self { + let bedrock_rel = match bedrock_rel.into() { + Some(bedrock_rel) => bedrock_rel, + None => false, + }; + self.bedrockrel = bedrock_rel; + self + } + + /// Generate a refresh token alongside. + pub fn refresh_token( + &mut self, + refresh_token: Option, + ) -> Result<&mut Self, errors::AuthErrors> { + if self.auth_type != AuthType::Refresh { + return Err(errors::AuthErrors::MismatchedAuthType( + "Expected Refresh Token Flow".to_string(), + )); + } + self.refresh_token = refresh_token; + Ok(self) + } + + /// Gets the code for device code method + pub async fn get_info(&mut self) -> AuthInfo { + let client = Client::new(); + + match self.auth_type { + AuthType::DeviceCode => { + let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID"); + let code = device_authentication_code(client, client_id).await.unwrap(); + AuthInfo { + device_code: Some(code), + ouath_url: None, + redirect_url: None, + } + } + AuthType::Oauth | AuthType::Refresh => { + let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID"); + let url = format!( + "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?client_id={}&response_type=code&redirect_uri=http://localhost:{}&response_mode=query&scope={}&state=12345", + client_id, + self.port.expect("EXPECTED PORT"), + self.scope.clone().expect("Expected Scope") + ); + AuthInfo { + device_code: None, + ouath_url: Some(url), + redirect_url: None, + } + } + AuthType::Minecraft => { + let redirect_url = "https://login.live.com/oauth20_desktop.srf"; + let url = format!( + "https://login.live.com/oauth20_authorize.srf?client_id={LAUNCHER_CLIENT_ID}&redirect_uri={}&response_type=code&scope=service::user.auth.xboxlive.com::MBI_SSL", + redirect_url + ); + + AuthInfo { + device_code: None, + ouath_url: Some(url), + redirect_url: Some(redirect_url.to_string()), + } + } + } + } + + /// Launchs the authentication process. + pub async fn launch( + &mut self, + code: Option<&str>, + redirect_url: Option, + ) -> Result> { + dbg!(&self.auth_type, &self.client_id); + let client = Client::new(); + + match self.auth_type { + AuthType::Oauth => { + dbg!(&self.client_secret, self.port, &self.client_secret); + let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID"); + let client_secret = &self.client_secret.clone().expect("EXPECTED CLIENT_ID"); + + let server = ouath(self.port.expect("EXPECTED PORT"))?.await?; + let server_token = ouath_token( + client.clone(), + None, + Some( + server + .code + .expect("\x1b[31mXbox Expected code.\x1b[0m") + .as_str(), + ), + client_id, + self.scope.as_deref().unwrap_or_default(), + format!("https://localhost:{}", self.port.expect("EXPECTED PORT")), + Some(client_secret), + ) + .await?; + let xbl = xbl( + client.clone(), + &server_token.access_token, + format!("https://localhost:{}", self.port.expect("EXPECTED PORT")), + ) + .await?; + let xts = xsts(client.clone(), &xbl.token, self.bedrockrel).await?; + + if self.bedrockrel { + Ok(CustomAuthData { + access_token: None, + uuid: None, + expires_in: 0, + xts_token: Some(xts.token), + }) + } else { + Ok(bearer_token(client, &xbl.display_claims.xui[0].uhs, &xts.token).await?) + } + } + AuthType::DeviceCode => { + let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID"); + + print!("{} \n Status: WIP (Work In Progress)", EXPERIMENTAL_MESSAGE); + let code = device_authentication_code(client.clone(), client_id).await?; + let code_token = + authenticate_device(client.clone(), &code.device_code, client_id).await?; + let xbl = xbl(client.clone(), &code_token.token, "".to_string()).await?; + let xts = xsts(client.clone(), &xbl.token, self.bedrockrel).await?; + + if self.bedrockrel { + Ok(CustomAuthData { + access_token: None, + uuid: None, + expires_in: 0, + xts_token: Some(xts.token), + }) + } else { + Ok(bearer_token(client, &xbl.display_claims.xui[0].uhs, &xts.token).await?) + } + } + AuthType::Refresh => { + let client_id = &self.client_id.clone().expect("EXPECTED CLIENT_ID"); + let client_secret = &self.client_secret.clone().expect("EXPECTED CLIENT_ID"); + let oauth_url = if self.port.is_some() { + format!("https://localhost:{}", self.port.expect("EXPECTED PORT")) + } else { + redirect_url.expect("EXPECTED REDIRECT URL") + }; + + if self.refresh_token == None { + return Err(Box::new(errors::AuthErrors::AuthenticationFailure( + "Missing Refresh Token".to_string(), + ))); + }; + let server_token = ouath_token( + client.clone(), + self.refresh_token.clone(), + None, + client_id, + self.scope.as_deref().unwrap_or_default(), + oauth_url.clone(), + Some(client_secret), + ) + .await?; + let xbl = xbl( + client.clone(), + &server_token.access_token, + oauth_url.clone(), + ) + .await?; + let xts = xsts(client.clone(), &xbl.token, self.bedrockrel).await?; + + if self.bedrockrel { + Ok(CustomAuthData { + access_token: None, + uuid: None, + expires_in: 0, + xts_token: Some(xts.token), + }) + } else { + Ok(bearer_token(client, &xbl.display_claims.xui[0].uhs, &xts.token).await?) + } + } + AuthType::Minecraft => { + dbg!(self.port); + let url = redirect_url.expect("EXPECTED REDIRECT URL"); + + let server_token = ouath_token( + client.clone(), + None, + code, + LAUNCHER_CLIENT_ID, + "service::user.auth.xboxlive.com::MBI_SSL", + url.clone(), + None, + ) + .await?; + let xbl = xbl(client.clone(), &server_token.access_token, url.clone()).await?; + let xts = xsts(client.clone(), &xbl.token, self.bedrockrel).await?; + + if self.bedrockrel { + Ok(CustomAuthData { + access_token: None, + uuid: None, + expires_in: 0, + xts_token: Some(xts.token), + }) + } else { + Ok(bearer_token(client, &xbl.display_claims.xui[0].uhs, &xts.token).await?) + } + } + } + } +} + +/// A builder that launches minecraft or your own custom client. +#[cfg(feature = "launch")] +pub struct LaunchBuilder { + args: Vec, + java_path: Option, + client: Option, + mods: Option>, +} + +/// Launch Directories for minecraft +#[cfg(feature = "launch")] +pub struct LauncherDirs { + /// Game Directory + pub game_dir: PathBuf, + /// Assets Directory + pub assets_dir: PathBuf, + /// Libraries Directory + pub libraries_dir: PathBuf, + /// Natives Directory + pub natives_dir: PathBuf, + /// Java Directory + pub java_dir: PathBuf, +} + +#[cfg(feature = "launch")] +impl LaunchBuilder { + /// Create a new instanve of `LaunchBuilder`. + pub fn init() -> Self { + Self { + args: Vec::new(), + java_path: None, + client: None, + mods: None, + } + } + + /// Set the Java Arguments for the Minecraft. + pub fn args(&mut self, args: Vec) -> &mut Self { + dbg!(&args); + self.args = args; + self + } + + /// Set the Java Path for the Minecraft. + /// FOR CUSTOM JAVA ONLY + pub fn java(&mut self, path: Option) -> &mut Self { + self.java_path = path; + self + } + + /// Set for Custom Minecraft Client + pub fn client(&mut self, client: Option) -> &mut Self { + self.client = client; + self + } + + /// Set for Custom Minecraft Mods to include in the launch.s + pub fn mods(&mut self, mods: Option>) -> &mut Self { + self.mods = mods; + self + } + + /// Launches the Minecraft/Your Client! + /// `jre` is required if you use custom java. + pub async fn launch(&mut self, jre: Option, dirs: Option) { + if cfg!(target_os = "macos") { + self.args.push(format!("-XstartOnFirstThread")); + } + } +} + +/// Launch Args +/// +/// This struct is used to build the launch arguments for your Minecraft client. +/// It provides a builder-like interface for setting various launch arguments. +#[cfg(feature = "launch")] +pub struct LaunchArgs { + args: Vec, +} + +#[cfg(feature = "launch")] +impl LaunchArgs { + /// Creates a new LaunchArgs builder + pub fn builder() -> Self { + Self { args: Vec::new() } + } + + /// Sets general game settings + /// window_size: width, height + /// launcher_branding: name, version + /// version_input: version, version_type + /// memory: min, max (MB) + pub fn game_settings( + &mut self, + window_size: Option<(u32, u32)>, + launcher_branding: Option<(String, String)>, + mut dirs: LauncherDirs, + version_input: Option<(String, String)>, + memory: (u16, Option), + ) { + self.args.push(format!("--Xms{}m", memory.0)); + if let Some(branding) = launcher_branding { + self.args + .push(format!("-Dminecraft.launcher.brand={}", branding.0)); + self.args + .push(format!("--Dminecraft.launcher.version={}", branding.1)); + } + + if let Some(version) = version_input { + self.args.push(format!("--version {}", version.0)); + self.args.push(format!("--versionType {}", version.1)); + } + + if let Some(window_size) = window_size { + self.args.push(format!("--width {}", window_size.0)); + self.args.push(format!("--height {}", window_size.1)); + } + + if let Some(memory) = memory.1 { + self.args.push(format!("--Xmx{}M", memory)); + } + + self.args + .push(format!("--gameDir {}", dirs.game_dir.display())); + self.args + .push(format!("--assetsDir {}", dirs.assets_dir.display())); + self.args.push(format!( + "--assetsIndex {:?}", + dirs.assets_dir.push("/index.json") + )); + self.args + .push(format!("--librariesDir {}", dirs.libraries_dir.display())); + self.args + .push(format!("--nativesDir {}", dirs.natives_dir.display())); + self.args + .push(format!("--javaDir {}", dirs.java_dir.display())); + } +} + +/// Device Code Authentication +/// +/// This struct represents the device code authentication process for Minecraft, specifically designed for use with custom Azure applications. +/// It is used to authenticate a device and obtain a token that can be used to launch Minecraft. +#[cfg(feature = "auth")] +// TODO: REMOVE THIS AT 0.2.14 +#[deprecated( + note = "The Device implementation is deprecated. Please migrate to AuthenticationBuilder and utilize the DeviceCode type for authentication.", + since = "0.2.13" +)] +pub struct DeviceCode { + url: String, + message: String, + expires_in: u32, + user_code: String, + device_code: String, + client_id: String, +} + +#[cfg(feature = "auth")] +#[deprecated( + note = "The Ouath implementation is deprecated. Please migrate to AuthenticationBuilder and utilize the Oauth type for authentication.", + since = "0.2.12" +)] +impl DeviceCode { + /// Initializes a new `DeviceCode` instance. + /// + /// This method starts the device code authentication process by making an asynchronous request + /// to the authentication server. It returns a future that resolves to a `Result` containing the + /// `DeviceCode` instance on success or an error if the request fails. + /// + /// # Arguments + /// + /// * `client_id` - The client ID obtained from the Minecraft authentication service. + /// + /// # Returns + /// + /// * `impl trait_alias::AsyncSendSync>` - A future that resolves to a `Result` containing the `DeviceCode` instance or an error. + pub fn new(client_id: &str) -> impl trait_alias::AsyncSendSync> { + println!("{}", EXPERIMENTAL_MESSAGE); + let client = Client::new(); + let client_id_str = client_id.to_string(); + async move { + let response_data = device_authentication_code(client, &client_id_str).await?; + + Ok(Self { + url: response_data.verification_uri, + message: response_data.message, + expires_in: response_data.expires_in, + user_code: response_data.user_code, + device_code: response_data.device_code, + client_id: client_id_str, + }) + } + } + + /// Provides pre-launch information. + /// + /// This method returns a tuple containing the verification URL, the message to display to the user, + /// the expiration time of the device code, and the user code. This information is useful for guiding + /// the user through the device code authentication process. + /// + /// # Returns + /// + /// * `(&str, &str, u32, &str)` - A tuple containing the verification URL, the message, the expiration time, and the user code. + pub fn preinfo(&self) -> (&str, &str, u32, &str) { + (&self.url, &self.message, self.expires_in, &self.user_code) + } + + /// Launches Minecraft using the device code authentication process. + /// + /// This method completes the device code authentication process by authenticating the device + /// and obtaining a token. It then uses this token to launch Minecraft. The method supports both + /// Bedrock Edition and Java Edition of Minecraft. + /// + /// # Arguments + /// + /// * `bedrock_relm` - A boolean indicating whether to launch the Bedrock Edition of Minecraft. + /// + /// # Returns + /// + /// * `Result>` - A result containing the authentication data or an error if the process fails. + pub async fn launch( + &self, + bedrock_relm: bool, + ) -> Result> { + let client = Client::new(); + let code = authenticate_device(client.clone(), &self.device_code, &self.client_id).await?; + let xbl = xbl(client.clone(), &code.token, "".to_string()).await?; + let xts = xsts(client.clone(), &xbl.token, bedrock_relm).await?; + + if bedrock_relm { + Ok(CustomAuthData { + access_token: None, + uuid: None, + expires_in: 0, + xts_token: Some(xts.token), + }) + } else { + Ok(bearer_token(client, &xbl.display_claims.xui[0].uhs, &xts.token).await?) + } + } + + /// Refreshes the device code authentication process. + /// + /// This method was previously used to manually refresh the device code authentication process but has been superseded by the `AuthenticationBuilder`. The new approach allows for more flexible and streamlined authentication flow management. + /// + /// To refresh the authentication, users should now utilize the `AuthenticationBuilder` and call the `.refresh_type(AuthType::DeviceCode)` method. This method automatically handles the necessary steps to refresh the authentication state according to the selected authentication type. + /// + /// # Removal Notice + /// + /// Effective immediately, this method is marked as deprecated and will be removed in the next minor version release. Users are strongly encouraged to migrate to using the `AuthenticationBuilder` for refreshing authentication states before upgrading to the next version. + #[deprecated( + note = "Effective immediately, please migrate to using the `AuthenticationBuilder` and its `.refresh_type(AuthType::DeviceCode)` method for refreshing the authentication state. This method will be removed in the next minor version release.", + since = "0.2.12" + )] + pub async fn refresh(&self) { + println!( + "This method is deprecated and will be removed in the next minor version. Please refer to the updated documentation on using the `AuthenticationBuilder`." + ); + } +} diff --git a/crates/minecraft-essentails/src/main.rs b/crates/minecraft-essentails/src/main.rs new file mode 100644 index 0000000..c7dc0f4 --- /dev/null +++ b/crates/minecraft-essentails/src/main.rs @@ -0,0 +1,52 @@ +use clap::Parser; +use cwd::{Cli, Commands, DeviceCodeArgs, OauthArgs}; +use minecraft_essentials::{AuthType, AuthenticationBuilder}; + +mod cwd; + +#[tokio::main] +async fn main() { + let cli = Cli::parse(); + match &cli.command { + Commands::Oauth(oauth_args) => handle_oauth(oauth_args).await, + Commands::DeviceCode(device_code_args) => handle_device_code(device_code_args).await, + Commands::Version {} => println!("{}", env!("CARGO_PKG_VERSION")), + // TODO: HANDLE LAUNCH + Commands::Launch(_arg) => todo!(), + } +} + +async fn handle_oauth(oauth_args: &OauthArgs) { + let mut auth_builder = AuthenticationBuilder::builder(); + auth_builder + .of_type(AuthType::Oauth) + .client_id(Some(&oauth_args.client_id)) + .client_secret(Some(&oauth_args.client_secret)) + .bedrockrel(oauth_args.bedrockrelm) + .port(oauth_args.port); + + println!("{:?}", auth_builder.get_info().await); + + let auth_info = auth_builder.launch(None, None).await.unwrap(); + + println!("{:?}", auth_info) +} + +async fn handle_device_code(device_code_args: &DeviceCodeArgs) { + let mut auth_builder = AuthenticationBuilder::builder(); + auth_builder + .of_type(AuthType::DeviceCode) + .client_id(Some(&device_code_args.client_id)) + .bedrockrel(Some(device_code_args.bedrockrelm)); + + println!("{:?}", auth_builder.get_info().await); + + println!("{:?}", auth_builder.launch(None, None).await); +} + +// async fn handle_launch(arg: &LaucnhArgs) { +// LaunchBuilder::builder() +// .args(launch_args) +// .launch(arg.jre) +// .await; +// } diff --git a/crates/minecraft-essentails/src/tests.rs b/crates/minecraft-essentails/src/tests.rs new file mode 100644 index 0000000..dce503c --- /dev/null +++ b/crates/minecraft-essentails/src/tests.rs @@ -0,0 +1,73 @@ +use super::*; +use dotenv::dotenv; +use std::env; + +#[cfg(feature = "auth")] +#[tokio::test] +async fn test_oauth_url() { + let _ = dotenv(); + let client_id = env::var("Client_ID").expect("Expected Client ID"); + let port = 8000; + let expected_url = format!( + "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?client_id={}&response_type=code&redirect_uri=http://localhost:{}&response_mode=query&scope={}&state=12345", + client_id, port, SCOPE + ); + let oauth = Oauth::new(&client_id, Some(port)); + let url = oauth.url().await; + + assert_eq!(url, expected_url); +} + +#[cfg(feature = "auth")] +#[tokio::test] +async fn test_device_code_prelaunch() { + let _ = dotenv(); + let client_id = env::var("Client_ID").expect("Expected Client ID."); + let device_code = DeviceCode::new(&client_id).await.unwrap(); + + let (url, message, expires_in, user_code) = device_code.preinfo(); + + assert_eq!(url, device_code.url); + assert_eq!(message, device_code.message); + assert_eq!(expires_in, device_code.expires_in); + assert_eq!(user_code, device_code.user_code); +} + +#[cfg(feature = "auth")] +#[tokio::test] +async fn test_authentication_info() { + // Setup for OAuth + let _ = dotenv(); + let client_id = env::var("Client_ID").expect("Expected Client ID."); + let client_secret = env::var("Client_Secret").expect("Expected Client Secret."); + let port = 8000; + let mut builder = AuthenticationBuilder::builder(); + builder + .of_type(AuthType::Oauth) + .client_id(Some(&client_id)) + .client_secret(Some(&client_secret)) + .port(port); + + let assert_url = format!( + "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?client_id={}&response_type=code&redirect_uri=http://localhost:{}&response_mode=query&scope={}&state=12345", + client_id, port, SCOPE + ); + let url = builder.get_info().await.ouath_url.unwrap(); // Note: There seems to be a typo in the method name. It should likely be something like get_oauth_url() + + assert_eq!(assert_url, url); + + builder + .of_type(AuthType::DeviceCode) + .client_id(Some(&client_id)); + + let device_code = builder.get_info().await.device_code.unwrap(); + let url = device_code.verification_uri.clone(); + let message = device_code.message.clone(); + let expires_in = device_code.expires_in.clone(); + let user_code = device_code.user_code.clone(); + + assert_eq!(url, device_code.verification_uri); + assert_eq!(message, device_code.message); + assert_eq!(expires_in, device_code.expires_in); + assert_eq!(user_code, device_code.user_code); +} diff --git a/crates/node/.cargo/config.toml b/crates/node/.cargo/config.toml new file mode 100644 index 0000000..7ede30e --- /dev/null +++ b/crates/node/.cargo/config.toml @@ -0,0 +1,3 @@ +[target.aarch64-unknown-linux-musl] +linker = "aarch64-linux-musl-gcc" +rustflags = ["-C", "target-feature=-crt-static"] \ No newline at end of file diff --git a/crates/node/.gitignore b/crates/node/.gitignore new file mode 100644 index 0000000..b697a29 --- /dev/null +++ b/crates/node/.gitignore @@ -0,0 +1,14 @@ +/target +/dist + +node_modules/ +index.d.ts +index.js +*.node + +# dotenv environment variables file +.env + +# Operating systems +.DS_Store + diff --git a/crates/node/.npmignore b/crates/node/.npmignore new file mode 100644 index 0000000..984ca3a --- /dev/null +++ b/crates/node/.npmignore @@ -0,0 +1,6 @@ +target +Cargo.lock +.cargo +npm +*.node +__test__ diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml new file mode 100644 index 0000000..ad00d89 --- /dev/null +++ b/crates/node/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "node" +edition.workspace = true +version.workspace = true +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +napi = { version = "2.16.13", default-features = false, features = ["napi4", "tokio", "async"] } +napi-derive = "2.16.13" +corelib = { path = "../core" } + +[build-dependencies] +napi-build = "2.1.4" + +[profile.release] +lto = true diff --git a/crates/node/README.md b/crates/node/README.md new file mode 100644 index 0000000..fc56ba0 --- /dev/null +++ b/crates/node/README.md @@ -0,0 +1,26 @@ +# Minecraft-Essentials NPM Bindings. + +Binding for a good package. + + + +## Licensing + +This library is licensed under the [BSD 3.0 License](../../LICENSE). + +But however the @napi/cli uses glibc is licenced under LGPL 2.1: +``` +Copyright (C) 2012-2022 Free Software Foundation, Inc. +This file is part of the GNU C Library. +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. +You should have received a copy of the GNU Lesser General Public +License along with the GNU C Library. If not, see +. +``` diff --git a/crates/node/build.rs b/crates/node/build.rs new file mode 100644 index 0000000..9fc2367 --- /dev/null +++ b/crates/node/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/crates/node/bun.lockb b/crates/node/bun.lockb new file mode 100755 index 0000000..d01cd9b Binary files /dev/null and b/crates/node/bun.lockb differ diff --git a/crates/node/npm/darwin-arm64/README.md b/crates/node/npm/darwin-arm64/README.md new file mode 100644 index 0000000..37e08ec --- /dev/null +++ b/crates/node/npm/darwin-arm64/README.md @@ -0,0 +1,3 @@ +# `minecraft-essentials-darwin-arm64` + +This is the **aarch64-apple-darwin** binary for `minecraft-essentials` diff --git a/crates/node/npm/darwin-arm64/package.json b/crates/node/npm/darwin-arm64/package.json new file mode 100644 index 0000000..feff088 --- /dev/null +++ b/crates/node/npm/darwin-arm64/package.json @@ -0,0 +1,18 @@ +{ + "name": "minecraft-essentials-darwin-arm64", + "version": "0.0.0", + "os": [ + "darwin" + ], + "cpu": [ + "arm64" + ], + "main": "minecraft-essentials.darwin-arm64.node", + "files": [ + "minecraft-essentials.darwin-arm64.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + } +} \ No newline at end of file diff --git a/crates/node/npm/darwin-x64/README.md b/crates/node/npm/darwin-x64/README.md new file mode 100644 index 0000000..73bfd0c --- /dev/null +++ b/crates/node/npm/darwin-x64/README.md @@ -0,0 +1,3 @@ +# `minecraft-essentials-darwin-x64` + +This is the **x86_64-apple-darwin** binary for `minecraft-essentials` diff --git a/crates/node/npm/darwin-x64/package.json b/crates/node/npm/darwin-x64/package.json new file mode 100644 index 0000000..87e927e --- /dev/null +++ b/crates/node/npm/darwin-x64/package.json @@ -0,0 +1,18 @@ +{ + "name": "minecraft-essentials-darwin-x64", + "version": "0.0.0", + "os": [ + "darwin" + ], + "cpu": [ + "x64" + ], + "main": "minecraft-essentials.darwin-x64.node", + "files": [ + "minecraft-essentials.darwin-x64.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + } +} \ No newline at end of file diff --git a/crates/node/npm/linux-arm64-gnu/README.md b/crates/node/npm/linux-arm64-gnu/README.md new file mode 100644 index 0000000..fc7d3041 --- /dev/null +++ b/crates/node/npm/linux-arm64-gnu/README.md @@ -0,0 +1,3 @@ +# `minecraft-essentials-linux-arm64-gnu` + +This is the **aarch64-unknown-linux-gnu** binary for `minecraft-essentials` diff --git a/crates/node/npm/linux-arm64-gnu/package.json b/crates/node/npm/linux-arm64-gnu/package.json new file mode 100644 index 0000000..8ba6b02 --- /dev/null +++ b/crates/node/npm/linux-arm64-gnu/package.json @@ -0,0 +1,21 @@ +{ + "name": "minecraft-essentials-linux-arm64-gnu", + "version": "0.0.0", + "os": [ + "linux" + ], + "cpu": [ + "arm64" + ], + "main": "minecraft-essentials.linux-arm64-gnu.node", + "files": [ + "minecraft-essentials.linux-arm64-gnu.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "libc": [ + "glibc" + ] +} \ No newline at end of file diff --git a/crates/node/npm/linux-arm64-musl/README.md b/crates/node/npm/linux-arm64-musl/README.md new file mode 100644 index 0000000..33b9fd3 --- /dev/null +++ b/crates/node/npm/linux-arm64-musl/README.md @@ -0,0 +1,3 @@ +# `minecraft-essentials-linux-arm64-musl` + +This is the **aarch64-unknown-linux-musl** binary for `minecraft-essentials` diff --git a/crates/node/npm/linux-arm64-musl/package.json b/crates/node/npm/linux-arm64-musl/package.json new file mode 100644 index 0000000..bc25d15 --- /dev/null +++ b/crates/node/npm/linux-arm64-musl/package.json @@ -0,0 +1,21 @@ +{ + "name": "minecraft-essentials-linux-arm64-musl", + "version": "0.0.0", + "os": [ + "linux" + ], + "cpu": [ + "arm64" + ], + "main": "minecraft-essentials.linux-arm64-musl.node", + "files": [ + "minecraft-essentials.linux-arm64-musl.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "libc": [ + "musl" + ] +} \ No newline at end of file diff --git a/crates/node/npm/linux-x64-gnu/README.md b/crates/node/npm/linux-x64-gnu/README.md new file mode 100644 index 0000000..b0bca68 --- /dev/null +++ b/crates/node/npm/linux-x64-gnu/README.md @@ -0,0 +1,3 @@ +# `minecraft-essentials-linux-x64-gnu` + +This is the **x86_64-unknown-linux-gnu** binary for `minecraft-essentials` diff --git a/crates/node/npm/linux-x64-gnu/package.json b/crates/node/npm/linux-x64-gnu/package.json new file mode 100644 index 0000000..37eb461 --- /dev/null +++ b/crates/node/npm/linux-x64-gnu/package.json @@ -0,0 +1,21 @@ +{ + "name": "minecraft-essentials-linux-x64-gnu", + "version": "0.0.0", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "main": "minecraft-essentials.linux-x64-gnu.node", + "files": [ + "minecraft-essentials.linux-x64-gnu.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "libc": [ + "glibc" + ] +} \ No newline at end of file diff --git a/crates/node/npm/linux-x64-musl/README.md b/crates/node/npm/linux-x64-musl/README.md new file mode 100644 index 0000000..76af9b6 --- /dev/null +++ b/crates/node/npm/linux-x64-musl/README.md @@ -0,0 +1,3 @@ +# `minecraft-essentials-linux-x64-musl` + +This is the **x86_64-unknown-linux-musl** binary for `minecraft-essentials` diff --git a/crates/node/npm/linux-x64-musl/package.json b/crates/node/npm/linux-x64-musl/package.json new file mode 100644 index 0000000..dd0f011 --- /dev/null +++ b/crates/node/npm/linux-x64-musl/package.json @@ -0,0 +1,21 @@ +{ + "name": "minecraft-essentials-linux-x64-musl", + "version": "0.0.0", + "os": [ + "linux" + ], + "cpu": [ + "x64" + ], + "main": "minecraft-essentials.linux-x64-musl.node", + "files": [ + "minecraft-essentials.linux-x64-musl.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "libc": [ + "musl" + ] +} \ No newline at end of file diff --git a/crates/node/npm/win32-arm64-msvc/README.md b/crates/node/npm/win32-arm64-msvc/README.md new file mode 100644 index 0000000..a6f360c --- /dev/null +++ b/crates/node/npm/win32-arm64-msvc/README.md @@ -0,0 +1,3 @@ +# `minecraft-essentials-win32-arm64-msvc` + +This is the **aarch64-pc-windows-msvc** binary for `minecraft-essentials` diff --git a/crates/node/npm/win32-arm64-msvc/package.json b/crates/node/npm/win32-arm64-msvc/package.json new file mode 100644 index 0000000..051f7fe --- /dev/null +++ b/crates/node/npm/win32-arm64-msvc/package.json @@ -0,0 +1,18 @@ +{ + "name": "minecraft-essentials-win32-arm64-msvc", + "version": "0.0.0", + "os": [ + "win32" + ], + "cpu": [ + "arm64" + ], + "main": "minecraft-essentials.win32-arm64-msvc.node", + "files": [ + "minecraft-essentials.win32-arm64-msvc.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + } +} \ No newline at end of file diff --git a/crates/node/npm/win32-x64-msvc/README.md b/crates/node/npm/win32-x64-msvc/README.md new file mode 100644 index 0000000..e62ec3a --- /dev/null +++ b/crates/node/npm/win32-x64-msvc/README.md @@ -0,0 +1,3 @@ +# `minecraft-essentials-win32-x64-msvc` + +This is the **x86_64-pc-windows-msvc** binary for `minecraft-essentials` diff --git a/crates/node/npm/win32-x64-msvc/package.json b/crates/node/npm/win32-x64-msvc/package.json new file mode 100644 index 0000000..4512add --- /dev/null +++ b/crates/node/npm/win32-x64-msvc/package.json @@ -0,0 +1,18 @@ +{ + "name": "minecraft-essentials-win32-x64-msvc", + "version": "0.0.0", + "os": [ + "win32" + ], + "cpu": [ + "x64" + ], + "main": "minecraft-essentials.win32-x64-msvc.node", + "files": [ + "minecraft-essentials.win32-x64-msvc.node" + ], + "license": "MIT", + "engines": { + "node": ">= 10" + } +} \ No newline at end of file diff --git a/crates/node/package.json b/crates/node/package.json new file mode 100644 index 0000000..729a04a --- /dev/null +++ b/crates/node/package.json @@ -0,0 +1,35 @@ +{ + "name": "minecraft-essentials", + "version": "0.0.1", + "main": "/dist/index.js", + "types": "/dist/index.d.ts", + "napi": { + "name": "minecraft-essentials", + "triples": { + "additional": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", + "aarch64-pc-windows-msvc", + "x86_64-unknown-linux-musl" + ] + } + }, + "license": "BSD-3-Clause", + "devDependencies": { + "@napi-rs/cli": "^2.17.0" + }, + "packageManager": "bun@1.0.25", + "engines": { + "node": ">= 10" + }, + "scripts": { + "artifacts": "napi artifacts --dir ./dist", + "build": "napi build --platform --release ./dist", + "build:debug": "napi build --platform ./dist", + "prepublishOnly": "napi prepublish -t npm", + "universal": "napi universal", + "version": "napi version", + "clean": "rm -rf ./dist && cargo clean" + } +} diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs new file mode 100644 index 0000000..e88612f --- /dev/null +++ b/crates/node/src/lib.rs @@ -0,0 +1,331 @@ +#![deny(clippy::all)] + +use napi::{Error, Result, bindgen_prelude::*}; +use napi_derive::napi; + +use corelib::EXPERIMENTAL_MESSAGE; +use corelib::HTTP::Client; +use corelib::auth::AuthInfo as CoreAuthInfo; +use corelib::launch::JavaJRE; + +use corelib::auth::{ + bearer_token, + microsoft::{SCOPE, authenticate_device, device_authentication_code, ouath, ouath_token}, + xbox::{XblOutput, XtsOutput, xbl, xsts}, +}; + +#[napi] +#[derive(Debug, PartialEq, Eq)] +pub enum AuthType { + Oauth, + DeviceCode, +} + +#[napi(object)] +pub struct AuthInfo { + pub device_code: Option, + pub ouath_url: Option, +} + +#[napi(object)] +pub struct AuthData { + pub access_token: Option, + pub uuid: Option, + pub expires_in: i64, + pub xts_token: Option, +} + +impl From for AuthData { + fn from(c: CoreAuthInfo) -> Self { + AuthData { + access_token: c.access_token, + uuid: c.uuid, + expires_in: c.expires_in as i64, // safe conversion + xts_token: c.xts_token, + } + } +} + +#[napi] +pub struct AuthBuilder { + auth_type: AuthType, + client_id: String, + port: u16, + client_secret: String, + bedrockrel: bool, +} + +#[napi] +impl AuthBuilder { + #[napi(constructor)] + pub fn new() -> Self { + Self { + auth_type: AuthType::Oauth, + client_id: "".to_string(), + port: 8000, + client_secret: "".to_string(), + bedrockrel: false, + } + } + + #[napi] + pub fn set_type(&mut self, auth_type: AuthType) -> &Self { + self.auth_type = auth_type; + self + } + + #[napi] + pub fn set_client_id(&mut self, client_id: String) -> &Self { + self.client_id = client_id; + self + } + + #[napi] + pub fn set_port(&mut self, port: u16) -> &Self { + self.port = port; + self + } + + #[napi] + pub fn set_client_secret(&mut self, client_secret: String) -> &Self { + self.client_secret = client_secret; + self + } + + #[napi] + pub fn set_bedrockrel(&mut self, bedrockrel: bool) -> &Self { + self.bedrockrel = bedrockrel; + self + } + #[napi] + pub fn get_self(&self, env: Env) -> Result { + let mut obj = env.create_object()?; + + obj.set("type", self.auth_type as i32)?; + obj.set("client_id", self.client_id.clone())?; + obj.set("port", self.port)?; + obj.set("client_secret", self.client_secret.clone())?; + obj.set("bedrockrel", self.bedrockrel)?; + + Ok(obj) + } + + #[napi] + pub async fn get_info(&self) -> Result { + let client = Client::new(); + + match self.auth_type { + AuthType::DeviceCode => { + let code = device_authentication_code(client, &self.client_id) + .await + .map_err(|e| Error::from_reason(format!("Device code error: {}", e)))?; + + Ok(AuthInfo { + device_code: Some(code.user_code), + ouath_url: None, + }) + } + AuthType::Oauth => { + let url = format!( + "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?client_id={}&response_type=code&redirect_uri=http://localhost:{}&response_mode=query&scope={}&state=12345", + self.client_id, self.port, SCOPE + ); + + Ok(AuthInfo { + device_code: None, + ouath_url: Some(url), + }) + } + } + } + + #[napi] + pub async fn launch(&self) -> Result { + let client = Client::new(); + + // Helper function to handle the common response part + async fn process_tokens( + client: &Client, + xbl_resp: &XblOutput, + xts_resp: &XtsOutput, + bedrockrel: bool, + ) -> Result { + if bedrockrel { + Ok(AuthData { + access_token: None, + uuid: None, + expires_in: 0, + xts_token: Some(xts_resp.token.clone()), + }) + } else { + let custom_data = bearer_token( + client.clone(), + &xbl_resp.display_claims.xui[0].uhs, + &xts_resp.token, + ) + .await + .map_err(|e| Error::from_reason(format!("Bearer token failed: {}", e)))?; + + Ok(custom_data.into()) + } + } + + match self.auth_type { + AuthType::Oauth => { + let server = ouath(self.port) + .map_err(|e| Error::from_reason(format!("OAuth server error: {}", e)))? + .await + .map_err(|e| Error::from_reason(format!("OAuth response error: {}", e)))?; + + let code = server.code.ok_or_else(|| { + Error::from_reason("OAuth code missing from response".to_string()) + })?; + + let token = ouath_token( + client.clone(), + &code, + &self.client_id, + self.port, + &self.client_secret, + ) + .await + .map_err(|e| Error::from_reason(format!("Token exchange failed: {}", e)))?; + + let xbl_resp = xbl(client.clone(), &token.access_token) + .await + .map_err(|e| Error::from_reason(format!("XBL auth failed: {}", e)))?; + + let xts_resp = xsts(client.clone(), &xbl_resp.token, self.bedrockrel) + .await + .map_err(|e| Error::from_reason(format!("XSTS auth failed: {}", e)))?; + + process_tokens(&client, &xbl_resp, &xts_resp, self.bedrockrel).await + } + + AuthType::DeviceCode => { + println!("{} \nStatus: WIP (Work In Progress)", EXPERIMENTAL_MESSAGE); + + let code = device_authentication_code(client.clone(), &self.client_id) + .await + .map_err(|e| Error::from_reason(format!("Device code error: {}", e)))?; + + let code_token = + authenticate_device(client.clone(), &code.device_code, &self.client_id) + .await + .map_err(|e| Error::from_reason(format!("Auth device failed: {}", e)))?; + + let xbl_resp = xbl(client.clone(), &code_token.token) + .await + .map_err(|e| Error::from_reason(format!("XBL failed: {}", e)))?; + + let xts_resp = xsts(client.clone(), &xbl_resp.token, self.bedrockrel) + .await + .map_err(|e| Error::from_reason(format!("XSTS failed: {}", e)))?; + + process_tokens(&client, &xbl_resp, &xts_resp, self.bedrockrel).await + } + } + } +} + +#[napi] +pub struct LaunchBuilder { + args: Vec, + java_path: Option, + client: Option, + mods: Option>, +} + +#[napi(object)] +pub struct LauncherDirs { + pub game_dir: String, + pub assets_dir: String, + pub libraries_dir: String, + pub natives_dir: String, + pub java_dir: String, +} + +#[napi] +pub enum JsJavaJRE { + Adoptium, + Zulu, + GraalVM, +} + +impl From for JavaJRE { + fn from(js_java_jre: JsJavaJRE) -> Self { + match js_java_jre { + JsJavaJRE::Adoptium => JavaJRE::Adoptium, + JsJavaJRE::Zulu => JavaJRE::Zulu, + JsJavaJRE::GraalVM => JavaJRE::GraalVM, + } + } +} + +impl From for JsJavaJRE { + fn from(java_jre: JavaJRE) -> Self { + match java_jre { + JavaJRE::Adoptium => JsJavaJRE::Adoptium, + JavaJRE::Zulu => JsJavaJRE::Zulu, + JavaJRE::GraalVM => JsJavaJRE::GraalVM, + } + } +} + +#[napi] +impl LaunchBuilder { + #[napi(constructor)] + pub fn new() -> Self { + LaunchBuilder { + args: vec![], + java_path: None, + client: None, + mods: None, + } + } + + #[napi] + pub fn set_args(&mut self, args: Vec) -> &Self { + self.args = args; + self + } + + #[napi] + pub fn set_java(&mut self, path: Option) -> &Self { + self.java_path = path; + self + } + + #[napi] + pub fn set_client(&mut self, client: Option) -> &Self { + self.client = client; + self + } + + #[napi] + pub fn set_mods(&mut self, mods: Option>) -> &Self { + self.mods = mods; + self + } + + #[napi] + pub fn get_self(&self, env: Env) -> Result { + let mut obj = env.create_object()?; + + // Return all the fields as plain object + obj.set("args", self.args.clone())?; + obj.set("java_path", self.java_path.clone())?; + obj.set("client", self.client.clone())?; + obj.set("mods", self.mods.clone())?; + + Ok(obj) + } + + #[napi] + pub fn launch(&self, jre: Option, dirs: Option) { + let mut args = self.args.clone(); + if cfg!(target_os = "macos") { + args.push("-XstartOnFirstThread".to_string()); + } + } +} diff --git a/crates/node/testing/auth.test.ts b/crates/node/testing/auth.test.ts new file mode 100644 index 0000000..39b7839 --- /dev/null +++ b/crates/node/testing/auth.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, test } from "bun:test"; +import { AuthBuilder, AuthType } from "./index"; + +describe("AuthBuilder", () => { + test("should construct with default values", () => { + const builder = new AuthBuilder(); + expect(builder).toBeDefined(); + }); + + test("should update client_id", () => { + const builder = new AuthBuilder() + .setClientId("test-client-id"); + expect(builder.getSelf()).toMatchObject({ client_id: "test-client-id" }); + }); + + test("should return OAuth URL in get_info", async () => { + const builder = new AuthBuilder() + .setType(AuthType.Oauth) + .setClientId("mock-client-id"); + const info = await builder.getInfo(); + expect(info.ouathUrl).toContain("https://login.microsoftonline.com"); + }); + + test("should return device code in get_info", async () => { + const builder = new AuthBuilder() + .setType(AuthType.DeviceCode) + .setClientId("mock-device-id"); + const info = await builder.getInfo(); + expect(info.deviceCode).toBeDefined(); + }); +}); + diff --git a/crates/node/testing/index.ts b/crates/node/testing/index.ts new file mode 100644 index 0000000..e9213a2 --- /dev/null +++ b/crates/node/testing/index.ts @@ -0,0 +1 @@ +export * from "../dist/index"; diff --git a/crates/node/testing/launching.test.ts b/crates/node/testing/launching.test.ts new file mode 100644 index 0000000..264ced4 --- /dev/null +++ b/crates/node/testing/launching.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, test } from "bun:test"; +import { LaunchBuilder, JsJavaJRE } from "./index"; + +describe("LaunchBuilder", () => { + test("should construct with default values", () => { + const builder = new LaunchBuilder(); + expect(builder).toBeDefined(); + }); + + test("should set arguments correctly", () => { + const builder = new LaunchBuilder() + .setArgs(["--test", "--debug"]); + expect(builder.getSelf()).toMatchObject({ args: ["--test", "--debug"] }); + }); + + test("should set java path", () => { + const builder = new LaunchBuilder() + .setJava("path/to/java"); + expect(builder.getSelf()).toMatchObject({ java_path: "path/to/java" }); + }); + + test("should accept JavaJRE enum conversion", () => { + const builder = new LaunchBuilder() + .launch(JsJavaJRE.Adoptium, null); + expect(true).toBe(true); + }); +}); diff --git a/crates/node/tsconfig.json b/crates/node/tsconfig.json new file mode 100644 index 0000000..2d7bef0 --- /dev/null +++ b/crates/node/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2018", + "strict": true, + "moduleResolution": "node", + "module": "CommonJS", + "noUnusedLocals": true, + "noUnusedParameters": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "include": [ + "." + ], + "exclude": [ + "node_modules" + ] +} diff --git a/flake.lock b/flake.lock index 90f8ac6..fa7cda4 100644 --- a/flake.lock +++ b/flake.lock @@ -1,79 +1,25 @@ { "nodes": { - "cachix": { + "agenix-shell": { "inputs": { - "devenv": "devenv_2", - "flake-compat": "flake-compat_2", - "nixpkgs": [ - "devenv", - "nixpkgs" - ], - "pre-commit-hooks": "pre-commit-hooks" - }, - "locked": { - "lastModified": 1710475558, - "narHash": "sha256-egKrPCKjy/cE+NqCj4hg2fNX/NwLCf0bRDInraYXDgs=", - "owner": "cachix", - "repo": "cachix", - "rev": "661bbb7f8b55722a0406456b15267b5426a3bda6", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "cachix", - "type": "github" - } - }, - "devenv": { - "inputs": { - "cachix": "cachix", - "flake-compat": "flake-compat_4", - "nix": "nix_2", - "nixpkgs": "nixpkgs_2", - "pre-commit-hooks": "pre-commit-hooks_2" - }, - "locked": { - "lastModified": 1712300418, - "narHash": "sha256-tQKGdBAYIPeLNOtkymFQJh47w3R3e8adfgzVZ76qSeY=", - "owner": "cachix", - "repo": "devenv", - "rev": "8827aa19daf1fc3f53e7adcc7201933ef28f8652", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "devenv", - "type": "github" - } - }, - "devenv_2": { - "inputs": { - "flake-compat": [ - "devenv", - "cachix", - "flake-compat" - ], - "nix": "nix", + "flake-parts": "flake-parts", + "flake-root": "flake-root", + "git-hooks-nix": "git-hooks-nix", + "nix-github-actions": "nix-github-actions", "nixpkgs": "nixpkgs", - "poetry2nix": "poetry2nix", - "pre-commit-hooks": [ - "devenv", - "cachix", - "pre-commit-hooks" - ] + "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1708704632, - "narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=", - "owner": "cachix", - "repo": "devenv", - "rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196", + "lastModified": 1744732644, + "narHash": "sha256-WGIcBoRgoAVF2n8P2tu71uKvqxer+BJju6AcFj3zdZo=", + "owner": "aciceri", + "repo": "agenix-shell", + "rev": "145c087b9e76b3f40b4234f98b4e38c44cfdb621", "type": "github" }, "original": { - "owner": "cachix", - "ref": "python-rewrite", - "repo": "devenv", + "owner": "aciceri", + "repo": "agenix-shell", "type": "github" } }, @@ -85,11 +31,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1712384501, - "narHash": "sha256-AZmYmEnc1ZkSlxUJVUtGh9VFAqWPr+xtNIiBqD2eKfc=", + "lastModified": 1746858783, + "narHash": "sha256-oLrH70QIWB/KpaI+nztyP1hG4zAEEpMiNk6sA8QLQ/8=", "owner": "nix-community", "repo": "fenix", - "rev": "99c6241db5ca5363c05c8f4acbdf3a4e8fc42844", + "rev": "4e3cd098060cca21f2a213ce8c086948df946940", "type": "github" }, "original": { @@ -99,22 +45,6 @@ } }, "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_2": { "flake": false, "locked": { "lastModified": 1696426674, @@ -130,171 +60,85 @@ "type": "github" } }, - "flake-compat_3": { - "flake": false, - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_4": { - "flake": false, - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_5": { - "flake": false, - "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1712014858, - "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "lastModified": 1743550720, + "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", - "type": "github" - }, - "original": { - "id": "flake-parts", - "type": "indirect" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1689068808, - "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "rev": "c621e8422220273271f52058f618c94e405bb0f5", "type": "github" }, "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, - "flake-utils_3": { + "flake-parts_2": { "inputs": { - "systems": "systems_3" + "nixpkgs-lib": "nixpkgs-lib_2" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "lastModified": 1743550720, + "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "c621e8422220273271f52058f618c94e405bb0f5", "type": "github" }, "original": { - "owner": "numtide", - "repo": "flake-utils", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, - "flake-utils_4": { - "inputs": { - "systems": "systems_4" - }, + "flake-root": { "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "lastModified": 1723604017, + "narHash": "sha256-rBtQ8gg+Dn4Sx/s+pvjdq3CB2wQNzx9XGFq/JVGCB6k=", + "owner": "srid", + "repo": "flake-root", + "rev": "b759a56851e10cb13f6b8e5698af7b59c44be26e", "type": "github" }, "original": { - "owner": "numtide", - "repo": "flake-utils", + "owner": "srid", + "repo": "flake-root", "type": "github" } }, - "gitignore": { + "git-hooks-nix": { "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", "nixpkgs": [ - "devenv", - "cachix", - "pre-commit-hooks", + "agenix-shell", "nixpkgs" ] }, "locked": { - "lastModified": 1703887061, - "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5", + "lastModified": 1742649964, + "narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82", "type": "github" }, "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", + "owner": "cachix", + "repo": "git-hooks.nix", "type": "github" } }, - "gitignore_2": { + "gitignore": { "inputs": { "nixpkgs": [ - "devenv", - "pre-commit-hooks", + "agenix-shell", + "git-hooks-nix", "nixpkgs" ] }, @@ -312,63 +156,19 @@ "type": "github" } }, - "mk-shell-bin": { - "locked": { - "lastModified": 1677004959, - "narHash": "sha256-/uEkr1UkJrh11vD02aqufCxtbF5YnhRTIKlx5kyvf+I=", - "owner": "rrbutani", - "repo": "nix-mk-shell-bin", - "rev": "ff5d8bd4d68a347be5042e2f16caee391cd75887", - "type": "github" - }, - "original": { - "owner": "rrbutani", - "repo": "nix-mk-shell-bin", - "type": "github" - } - }, - "nix": { - "inputs": { - "flake-compat": "flake-compat", - "nixpkgs": [ - "devenv", - "cachix", - "devenv", - "nixpkgs" - ], - "nixpkgs-regression": "nixpkgs-regression" - }, - "locked": { - "lastModified": 1708577783, - "narHash": "sha256-92xq7eXlxIT5zFNccLpjiP7sdQqQI30Gyui2p/PfKZM=", - "owner": "domenkozar", - "repo": "nix", - "rev": "ecd0af0c1f56de32cbad14daa1d82a132bf298f8", - "type": "github" - }, - "original": { - "owner": "domenkozar", - "ref": "devenv-2.21", - "repo": "nix", - "type": "github" - } - }, "nix-github-actions": { "inputs": { "nixpkgs": [ - "devenv", - "cachix", - "devenv", - "poetry2nix", + "agenix-shell", "nixpkgs" ] }, "locked": { - "lastModified": 1688870561, - "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", + "lastModified": 1737420293, + "narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=", "owner": "nix-community", "repo": "nix-github-actions", - "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", + "rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9", "type": "github" }, "original": { @@ -377,172 +177,59 @@ "type": "github" } }, - "nix2container": { - "inputs": { - "flake-utils": "flake-utils_4", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1711883218, - "narHash": "sha256-XKHhQJ0tk/S/LbYJb31VVvZKWdb+uCFnvEuUQZJkP9o=", - "owner": "nlewo", - "repo": "nix2container", - "rev": "2154ad0459abfa258edaab038b4716f474656e99", - "type": "github" - }, - "original": { - "owner": "nlewo", - "repo": "nix2container", - "type": "github" - } - }, - "nix_2": { - "inputs": { - "flake-compat": "flake-compat_5", - "nixpkgs": [ - "devenv", - "nixpkgs" - ], - "nixpkgs-regression": "nixpkgs-regression_2" - }, - "locked": { - "lastModified": 1710500156, - "narHash": "sha256-zvCqeUO2GLOm7jnU23G4EzTZR7eylcJN+HJ5svjmubI=", - "owner": "domenkozar", - "repo": "nix", - "rev": "c5bbf14ecbd692eeabf4184cc8d50f79c2446549", - "type": "github" - }, - "original": { - "owner": "domenkozar", - "ref": "devenv-2.21", - "repo": "nix", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1692808169, - "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", + "lastModified": 1744463964, + "narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9201b5ff357e781bf014d0330d18555695df7ba8", + "rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650", "type": "github" }, "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-lib": { - "locked": { - "dir": "lib", - "lastModified": 1711703276, - "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", - "type": "github" - }, - "original": { - "dir": "lib", "owner": "NixOS", "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, - "nixpkgs-regression": { - "locked": { - "lastModified": 1643052045, - "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - } - }, - "nixpkgs-regression_2": { - "locked": { - "lastModified": 1643052045, - "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - } - }, - "nixpkgs-stable": { + "nixpkgs-lib": { "locked": { - "lastModified": 1704874635, - "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356", + "lastModified": 1743296961, + "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", "type": "github" }, "original": { - "owner": "NixOS", - "ref": "nixos-23.11", - "repo": "nixpkgs", + "owner": "nix-community", + "repo": "nixpkgs.lib", "type": "github" } }, - "nixpkgs-stable_2": { + "nixpkgs-lib_2": { "locked": { - "lastModified": 1710695816, - "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "614b4613980a522ba49f0d194531beddbb7220d3", + "lastModified": 1743296961, + "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", "type": "github" }, "original": { - "owner": "NixOS", - "ref": "nixos-23.11", - "repo": "nixpkgs", + "owner": "nix-community", + "repo": "nixpkgs.lib", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1710236354, - "narHash": "sha256-vWrciFdq49vve43g4pbi7NjmL4cwG1ifXnQx+dU3T5E=", - "owner": "cachix", - "repo": "devenv-nixpkgs", - "rev": "829e73affeadfb4198a7105cbe3a03153d13edc9", - "type": "github" - }, - "original": { - "owner": "cachix", - "ref": "rolling", - "repo": "devenv-nixpkgs", - "type": "github" - } - }, - "nixpkgs_3": { - "locked": { - "lastModified": 1712439257, - "narHash": "sha256-aSpiNepFOMk9932HOax0XwNxbA38GOUVOiXfUVPOrck=", + "lastModified": 1746663147, + "narHash": "sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ+TCkTRpRc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ff0dbd94265ac470dda06a657d5fe49de93b4599", + "rev": "dda3dcd3fe03e991015e9a74b22d35950f264a54", "type": "github" }, "original": { @@ -552,103 +239,22 @@ "type": "github" } }, - "poetry2nix": { - "inputs": { - "flake-utils": "flake-utils", - "nix-github-actions": "nix-github-actions", - "nixpkgs": [ - "devenv", - "cachix", - "devenv", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1692876271, - "narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=", - "owner": "nix-community", - "repo": "poetry2nix", - "rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "poetry2nix", - "type": "github" - } - }, - "pre-commit-hooks": { - "inputs": { - "flake-compat": "flake-compat_3", - "flake-utils": "flake-utils_2", - "gitignore": "gitignore", - "nixpkgs": [ - "devenv", - "cachix", - "nixpkgs" - ], - "nixpkgs-stable": "nixpkgs-stable" - }, - "locked": { - "lastModified": 1708018599, - "narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, - "pre-commit-hooks_2": { - "inputs": { - "flake-compat": [ - "devenv", - "flake-compat" - ], - "flake-utils": "flake-utils_3", - "gitignore": "gitignore_2", - "nixpkgs": [ - "devenv", - "nixpkgs" - ], - "nixpkgs-stable": "nixpkgs-stable_2" - }, - "locked": { - "lastModified": 1712055707, - "narHash": "sha256-4XLvuSIDZJGS17xEwSrNuJLL7UjDYKGJSbK1WWX2AK8=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "rev": "e35aed5fda3cc79f88ed7f1795021e559582093a", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "pre-commit-hooks.nix", - "type": "github" - } - }, "root": { "inputs": { - "devenv": "devenv", + "agenix-shell": "agenix-shell", "fenix": "fenix", - "flake-parts": "flake-parts", - "mk-shell-bin": "mk-shell-bin", - "nix2container": "nix2container", - "nixpkgs": "nixpkgs_3" + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs_2" } }, "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1712156296, - "narHash": "sha256-St7ZQrkrr5lmQX9wC1ZJAFxL8W7alswnyZk9d1se3Us=", + "lastModified": 1746722075, + "narHash": "sha256-t4ZntWiW4C3lE621lV3XyK3KltC5/SW1V9G+CSz70rQ=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "8e581ac348e223488622f4d3003cb2bd412bf27e", + "rev": "8b624868e4ce2cb5b39559175f0978bee86bdeea", "type": "github" }, "original": { @@ -658,63 +264,24 @@ "type": "github" } }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_3": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "agenix-shell", + "nixpkgs" + ] }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_4": { "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "lastModified": 1743748085, + "narHash": "sha256-uhjnlaVTWo5iD3LXics1rp9gaKgDRQj6660+gbUU3cE=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "815e4121d6a5d504c0f96e5be2dd7f871e4fd99d", "type": "github" }, "original": { - "owner": "nix-systems", - "repo": "default", + "owner": "numtide", + "repo": "treefmt-nix", "type": "github" } } diff --git a/flake.nix b/flake.nix index 604ab71..10ff53c 100644 --- a/flake.nix +++ b/flake.nix @@ -1,97 +1,76 @@ { - description = "Project Description"; # TODO: Project Description + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + agenix-shell.url = "github:aciceri/agenix-shell"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - devenv.url = "github:cachix/devenv"; - nix2container.url = "github:nlewo/nix2container"; - nix2container.inputs.nixpkgs.follows = "nixpkgs"; - mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin"; - fenix = { - url = "github:nix-community/fenix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; + outputs = + inputs@{ + self, + nixpkgs, + flake-parts, + agenix-shell, + ... + }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = nixpkgs.lib.systems.flakeExposed; - nixConfig = { - extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="; - extra-substituters = "https://devenv.cachix.org"; - }; + imports = [ +# agenix-shell.flakeModules.default - outputs = - inputs@{ flake-parts, nixpkgs, ... }: - flake-parts.lib.mkFlake { inherit inputs; } { - imports = [ inputs.devenv.flakeModule ]; - systems = [ - "x86_64-linux" - "i686-linux" - "x86_64-darwin" - "aarch64-linux" - "aarch64-darwin" - ]; + ]; - perSystem = - { - config, - self', - inputs', - lib, - pkgs, - system, - ... - }: - { - devenv.shells.default = { - name = "Project Name"; # TODO: Change Project Name - difftastic.enable = true; - imports = [ ]; - - # https://devenv.sh/reference/options/ - packages = - lib.optionals pkgs.stdenv.isDarwin ( - with pkgs.darwin.apple_sdk.frameworks; - [ - Security - SystemConfiguration - ] - ) - ++ (with pkgs; [ hyperfine ]); - - # Define Enviroment Virables - env = { - - }; +# agenix-shell = { +# secrets = { +# foo.file = ./secrets/foo.age; +# }; +# }; - # https://devenv.sh/scripts/ - # scripts.hello.exec = ""; + perSystem = + { + pkgs, + config, + system, + lib, + ... + }: + let + toolchain = pkgs.rustPlatform; + in + { + _module.args.pkgs = import nixpkgs { + inherit system; + overlays = [ + (inputs.fenix.overlays.default) + ]; + }; - # enterShell = '' - - # ''; - - # https://devenv.sh/languages/ - languages.rust = { - enable = true; - channel = "stable"; - components = [ - "rustc" - "cargo" - "clippy" - "rustfmt" - "rust-analyzer" - ]; - }; - - # https://devenv.sh/pre-commit-hooks/ - pre-commit.hooks = { - nixfmt.package = pkgs.nixfmt-rfc-style; - nixfmt.enable = true; - }; - - # https://devenv.sh/integrations/dotenv/ - dotenv.enable = true; - }; + devShells.default = with pkgs; let + toolchain = pkgs.fenix.stable.withComponents [ + "rustc" + "cargo" + "rustfmt" + "clippy" + ]; + in mkShell + { + packages = with pkgs; [ + openssl + napi-rs-cli # Useful for building node + pkg-config + hyperfine + rust-analyzer + bun # needed for node + toolchain + ] ++ lib.optionals stdenv.isDarwin [ + pkgs.apple-sdk_15 + ]; }; - flake = { }; - }; + }; + }; } diff --git a/src/custom/code.rs b/src/custom/code.rs deleted file mode 100644 index 4429908..0000000 --- a/src/custom/code.rs +++ /dev/null @@ -1,89 +0,0 @@ -#![forbid(unsafe_code)] -#![warn(clippy::pedantic)] - -use crate::{async_trait_alias::*, SCOPE}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct CodeResponse { - pub device_code: String, - pub user_code: String, - pub verification_uri: String, - pub expires_in: u32, - pub interval: u16, - pub message: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AuthenticationResponse { - pub expires_in: u16, - access_token: String, -} - -/// Defines expiry and token -#[derive(Debug)] -pub struct CodeInfo { - /// Provides expiry - pub expires_in: u16, - /// Provides token - pub token: String, -} - -pub fn device_authentication_code( - client_id: &str, -) -> impl AsyncSendSync> { - let request_url = - format!("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode",); - let body = format!("client_id={}&scope={}", client_id, SCOPE); - let client = Client::new(); - - device_internal(client, request_url, body) -} - -pub async fn device_internal( - client: Client, - request_url: String, - body: String, -) -> Result { - let response = client.post(request_url).body(body).send().await?; - - let response_data: CodeResponse = response.json().await?; - - Ok(response_data) -} - -pub fn authenticate_device( - device_code: &str, - client_id: &str, -) -> impl AsyncSendSync> { - let client = Client::new(); - let request_url = format!("https://login.microsoftonline.com/common/consumers/v2.0/token",); - - let body = format!( - "grant_type=urn:ietf:params:oauth:grant-type:device_code&client_id={}&device_code={}", - client_id, device_code - ); - - authenticate_internal(request_url, body, client) -} - -async fn authenticate_internal( - request_url: String, - body: String, - client: Client, -) -> Result { - let request = client - .post(request_url) - .body(body) - .header("Content-Type", "application/x-www-form-urlencoded") - .send() - .await?; - - let response_data: AuthenticationResponse = request.json().await?; - - let expires_in = response_data.expires_in; - let token = response_data.access_token; - - Ok(CodeInfo { expires_in, token }) -} diff --git a/src/custom/mod.rs b/src/custom/mod.rs deleted file mode 100644 index 68ec185..0000000 --- a/src/custom/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod code; -pub mod mojang; -pub mod oauth; -pub mod xbox; diff --git a/src/custom/oauth.rs b/src/custom/oauth.rs deleted file mode 100644 index 66bfc69..0000000 --- a/src/custom/oauth.rs +++ /dev/null @@ -1,197 +0,0 @@ -#![forbid(unsafe_code, missing_docs)] -#![warn(clippy::pedantic)] - -use reqwest::Client; -use serde::Deserialize; -use tokio::{io::AsyncReadExt, net::TcpListener, sync::mpsc}; - -use crate::{ - async_trait_alias::*, - errors::{OAuthError, TokenError}, - SCOPE, -}; - -/// Infomation from the temporary http server. -#[derive(Deserialize, Debug)] -pub struct Info { - /// The code - pub code: Option, - /// The state - pub state: Option, - // Error - error: Option, - // Error description - error_description: Option, -} - -#[derive(Deserialize, Debug)] -pub struct Token { - pub token_type: String, - pub scope: String, - pub expires_in: u16, - pub ext_expires_in: u16, - pub access_token: String, - pub refresh_token: String, -} - -pub fn server(port: u16) -> Result>, OAuthError> { - let (tx, mut rx) = mpsc::channel::(1); - - let server = tokio::spawn(async move { - match TcpListener::bind(format!("127.0.0.1:{}", port)).await { - Ok(listener) => { - loop { - match listener.accept().await { - Ok((mut socket, _)) => { - let tx = tx.clone(); - tokio::spawn(async move { - let mut buf = [0; 1024]; - loop { - let n = match socket.read(&mut buf).await { - Ok(n) if n == 0 => break, - Ok(n) => n, - Err(e) => { - eprintln!("failed to read from socket; err = {:?}", e); - break; - } - }; - - // Here you would parse the received data into your `Info` struct - // For demonstration, let's assume we have a function `parse_info` that does this - match parse_info(&buf[..n]) { - Ok(info) => { - if let Err(e) = tx.try_send(info) { - eprintln!( - "failed to send data to channel; err = {:?}", - e - ); - } - } - Err(e) => { - eprintln!("failed to parse info; err = {:?}", e); - } - } - } - }); - } - Err(e) => { - eprintln!("failed to accept connection; err = {:?}", e); - } - } - } - } - Err(e) => { - eprintln!("failed to bind listener; err = {:?}", e); - } - } - }); - - Ok(async move { - let info = rx.recv().await.expect("server did not receive params"); - - if info.error.as_ref().map_or(false, |s| !s.is_empty()) - && info - .error_description - .as_ref() - .map_or(false, |s| !s.is_empty()) - { - let err = OAuthError::AuthenticationFailure(info.error_description.unwrap()); - Err(err) - } else { - server.abort(); - - Ok(info) - } - }) -} - -fn parse_info(data: &[u8]) -> Result { - let data_str = std::str::from_utf8(data) - .map_err(|_| OAuthError::ParseError("Invalid UTF-8".to_string()))?; - - // Extract the query string from the HTTP request - let mut query_start = None; - while query_start.is_none() { - query_start = data_str.find('?'); - } - let query_start = - query_start.ok_or_else(|| OAuthError::ParseError("No query string found".to_string()))?; - let query_end = data_str.find('#').unwrap_or_else(|| data_str.len()); - let query_string = &data_str[query_start + 1..query_end]; - - // Parse the query string directly - let query_params: Vec<(String, String)> = url::form_urlencoded::parse(query_string.as_bytes()) - .into_owned() - .collect(); - - // Extract the 'code', 'state', 'error', and 'error_description' parameters - let code = query_params - .iter() - .find_map(|(k, v)| if k == "code" { Some(v.clone()) } else { None }); - let state = query_params.iter().find_map(|(k, v)| { - if k == "state" { - // Find the position of "HTTP/1.1\r\n" in the state value - let http_start = v.find(" HTTP/1.1\r\n"); - // If found, slice the string up to that position - http_start.map(|pos| v[..pos].to_string()) - } else { - None - } - }); - let error = query_params - .iter() - .find_map(|(k, v)| if k == "error" { Some(v.clone()) } else { None }); - let error_description = query_params.iter().find_map(|(k, v)| { - if k == "error_description" { - Some(v.clone()) - } else { - None - } - }); - - // Construct the Info struct - let info = Info { - code, - state, - error, - error_description, - }; - Ok(info) -} - -pub fn token( - code: &str, - client_id: &str, - port: u16, - client_secret: &str, -) -> impl AsyncSendSync> { - let url = format!("https://login.microsoftonline.com/consumers/oauth2/v2.0/token"); - let client = Client::new(); - let body = format!( - "client_id={}&scope={}&redirect_uri=http://localhost:{}&grant_type=authorization_code&code={}&client_secret={}", - client_id, SCOPE, port, code, client_secret); - - async move { - 'out: { - let result = client.post(url).body(body).send().await; - - let std::result::Result::Ok(response) = result else { - println!("Part 1"); - break 'out Err(TokenError::ResponseError( - "Failed to send request".to_string(), - )); - }; - - let text = response - .text() - .await - .map_err(|_| TokenError::ResponseError("Failed to send request".to_string()))?; - let std::result::Result::Ok(token) = serde_json::from_str::(&text) else { - break 'out Err(TokenError::ResponseError( - "Failed to send request, Check your Client Secret.".to_string(), - )); - }; - std::result::Result::Ok(token) - } - } -} diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 9378d30..0000000 --- a/src/errors.rs +++ /dev/null @@ -1,43 +0,0 @@ -#![forbid(unsafe_code, missing_docs)] -#![warn(clippy::pedantic)] - -use displaydoc::Display; -use thiserror::Error; -/// The `TokenError` enum represents potential errors that can occur during token operations. -#[derive(Display, Error, Debug)] -pub enum TokenError { - /// Response Failed: {0} - ResponseError(String), -} - -/// The `XboxError` enum represents potential errors that can occur during Xbox-related operations. -#[derive(Display, Error, Debug)] -pub enum XboxError { - /// Response Failed: {0} - ResponseError(String), -} - -/// The `XTSError` enum represents potential errors that can occur during XTS-related operations. -#[derive(Display, Error, Debug)] -pub enum XTSError { - /// Response Failed: {0} - ResponseError(String), -} - -/// The `OAuthError` enum represents potential errors that can occur during OAuth authentication. -#[derive(Display, Error, Debug)] -pub enum OAuthError { - /// Authentcation Failed: {0} - AuthenticationFailure(String), - /// Parsing Failed: {0} - ParseError(String), - /// Binding error: {0} - BindError(String), -} - -/// The `LaunchError` enum represents potential errors that can occur during Launching minecraft. -#[derive(Display, Error, Debug)] -pub enum LaunchError { - /// Launch Requirements Failed: {0} - Requirements(String), -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 7c97a6b..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,366 +0,0 @@ -#![doc = include_str!("../README.md")] -#![forbid(unsafe_code, missing_docs)] -#![warn(clippy::pedantic)] - -// Modules -pub(crate) mod async_trait_alias; -/// Error handling module for the Minecraft-Essentials library. -/// -/// This module contains all the error types and related functionality -/// for error handling within the library. -pub mod errors; -#[cfg(test)] -mod tests; - -#[cfg(feature = "custom-auth")] -mod custom; - -#[cfg(feature = "custom-auth")] -pub use custom::mojang::AuthInfo as CustomAuthData; - -#[cfg(feature = "custom-auth")] -use custom::{code, mojang, oauth, xbox}; - -#[cfg(feature = "custom-launch")] -use std::{ - io::{BufRead, BufReader}, - path::PathBuf, - process::{Command, Stdio}, -}; - -// Constants -pub(crate) const SCOPE: &str = "XboxLive.signin%20XboxLive.offline_access"; -pub(crate) const EXPERIMENTAL_MESSAGE: &str = - "\x1b[33mNOTICE: You are using an experimental feature.\x1b[0m"; - -/// OAuth 2.0 Authentication -/// -/// This struct represents the OAuth authentication process for Minecraft, specifically designed for use with custom Azure applications. -/// It is used to authenticate a user and obtain a token that can be used to launch Minecraft. -#[cfg(feature = "custom-auth")] -pub struct Oauth { - url: String, - port: u16, - client_id: String, -} - -#[cfg(feature = "custom-auth")] -impl Oauth { - /// Initializes a new `Oauth` instance. - /// - /// This method sets up the OAuth authentication process by constructing the authorization URL - /// and storing the client ID and port for later use. - /// - /// # Arguments - /// - /// * `client_id` - The client ID obtained from the Minecraft authentication service. - /// * `port` - An optional port number for the local server. Defaults to 8000 if not provided. - /// - /// # Returns - /// - /// * `Self` - A new instance of `Oauth` configured with the provided client ID and port. - pub fn new(client_id: &str, port: Option) -> Self { - let port = port.unwrap_or(8000); - let params = format!("client_id={}&response_type=code&redirect_uri=http://localhost:{}&response_mode=query&scope={}&state=12345", client_id, port, SCOPE); - let url = format!( - "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?{}", - params - ); - - Self { - url, - port, - client_id: client_id.to_string(), - } - } - - /// Retrieves the authorization URL. - /// - /// This method returns the URL that the user needs to visit to authorize the application. - /// - /// # Returns - /// - /// * `&str` - The authorization URL. - pub fn url(&self) -> &str { - &self.url - } - - /// Launches Minecraft using the OAuth authentication process. - /// - /// This method completes the OAuth authentication process by launching a local server to - /// receive the authorization code, exchanging it for an access token, and then using this token - /// to launch Minecraft. The method supports both Bedrock Edition and Java Edition of Minecraft. - /// - /// # Arguments - /// - /// * `bedrock_relm` - A boolean indicating whether to launch the Bedrock Edition of Minecraft. - /// * `client_secret` - The client secret obtained from the Minecraft authentication service. - /// - /// # Returns - /// - /// * `Result>` - A result containing the authentication data or an error if the process fails. - pub async fn launch( - &self, - bedrock_relm: bool, - client_secret: &str, - ) -> Result> { - let http_server = oauth::server(self.port)?.await?; - let token = oauth::token( - http_server - .code - .expect("\x1b[31mXbox Expected code.\x1b[0m") - .as_str(), - &self.client_id, - self.port, - client_secret, - ) - .await?; - let xbox = xbox::xbl(&token.access_token).await?; - let xts = xbox::xsts_token(&xbox.token, bedrock_relm).await?; - - if bedrock_relm { - Ok(CustomAuthData { - access_token: "null".to_string(), - uuid: "null".to_string(), - expires_in: 0, - xts_token: Some(xts.token), - }) - } else { - Ok(mojang::token(&xbox.display_claims.xui[0].uhs, &xts.token).await?) - } - } - - /// Refreshes the OAuth authentication process. - /// - /// This method is used to refresh the access token using the refresh token. - /// - /// # Arguments - /// - /// * `refresh_token` - The refresh token obtained from the Minecraft authentication service. - /// * `client_id` - The client ID obtained from the Minecraft authentication service. - /// * `port` - An optional port number for the local server. Defaults to 8000 if not provided. - /// * `client_secret` - The client secret obtained from the Minecraft authentication service. - /// - /// # Returns - /// - /// * `Result>` - A result containing the refreshed authentication data or an error if the process fails. - #[cfg(feature = "refresh")] - pub async fn refresh( - &self, - refresh_token: &str, - client_id: &str, - port: Option, - client_secret: &str, - ) -> Result> { - let port = port.unwrap_or(8000); - let token = oauth::token(refresh_token, client_id, port, client_secret).await?; - Ok(token) - } -} - -/// Device Code Authentication -/// -/// This struct represents the device code authentication process for Minecraft, specifically designed for use with custom Azure applications. -/// It is used to authenticate a device and obtain a token that can be used to launch Minecraft. -#[cfg(feature = "custom-auth")] -pub struct DeviceCode { - url: String, - message: String, - expires_in: u32, - user_code: String, - device_code: String, - client_id: String, -} - -#[cfg(feature = "custom-auth")] -impl DeviceCode { - /// Initializes a new `DeviceCode` instance. - /// - /// This method starts the device code authentication process by making an asynchronous request - /// to the authentication server. It returns a future that resolves to a `Result` containing the - /// `DeviceCode` instance on success or an error if the request fails. - /// - /// # Arguments - /// - /// * `client_id` - The client ID obtained from the Minecraft authentication service. - /// - /// # Returns - /// - /// * `impl async_trait_alias::AsyncSendSync>` - A future that resolves to a `Result` containing the `DeviceCode` instance or an error. - pub fn new( - client_id: &str, - ) -> impl async_trait_alias::AsyncSendSync> { - println!("{}", EXPERIMENTAL_MESSAGE); - let client_id_str = client_id.to_string(); - async move { - let response_data = code::device_authentication_code(&client_id_str).await?; - - Ok(Self { - url: response_data.verification_uri, - message: response_data.message, - expires_in: response_data.expires_in, - user_code: response_data.user_code, - device_code: response_data.device_code, - client_id: client_id_str, - }) - } - } - - /// Provides pre-launch information. - /// - /// This method returns a tuple containing the verification URL, the message to display to the user, - /// the expiration time of the device code, and the user code. This information is useful for guiding - /// the user through the device code authentication process. - /// - /// # Returns - /// - /// * `(&str, &str, u32, &str)` - A tuple containing the verification URL, the message, the expiration time, and the user code. - pub fn preinfo(&self) -> (&str, &str, u32, &str) { - (&self.url, &self.message, self.expires_in, &self.user_code) - } - - /// Launches Minecraft using the device code authentication process. - /// - /// This method completes the device code authentication process by authenticating the device - /// and obtaining a token. It then uses this token to launch Minecraft. The method supports both - /// Bedrock Edition and Java Edition of Minecraft. - /// - /// # Arguments - /// - /// * `bedrock_relm` - A boolean indicating whether to launch the Bedrock Edition of Minecraft. - /// - /// # Returns - /// - /// * `Result>` - A result containing the authentication data or an error if the process fails. - pub async fn launch( - &self, - bedrock_relm: bool, - ) -> Result> { - let token = code::authenticate_device(&self.device_code, &self.client_id).await?; - let xbox = xbox::xbl(&token.token).await?; - let xts = xbox::xsts_token(&xbox.token, bedrock_relm).await?; - if bedrock_relm { - Ok(CustomAuthData { - access_token: "null".to_string(), - uuid: "null".to_string(), - expires_in: 0, - xts_token: Some(xts.token), - }) - } else { - Ok(mojang::token(&xbox.display_claims.xui[0].uhs, &xts.token).await?) - } - } - - /// Refreshes the device code authentication process. - /// - /// This method is marked as experimental and currently does not perform any actions. - /// - /// # Note - /// - /// This method is intended for future use when implementing refresh functionality for the device code authentication process. - pub async fn refresh(&self) { - println!("{}", EXPERIMENTAL_MESSAGE); - } -} - -/// `Launch` struct represents the configuration for launching a Minecraft client. -/// -/// This struct holds the arguments required to launch the Minecraft client. The arguments are passed as a single string, -/// which can include various options supported by the Minecraft client. -#[cfg(feature = "custom-launch")] -pub struct Launch { - args: String, - java_exe: String, - jre: Option, -} - -#[cfg(feature = "custom-launch")] -impl Launch { - /// Launches a new instance of the launch function. - pub fn new( - args: Vec, - java_exe: String, - jre: Option, - offline: Option, - ) -> Result { - let args_final = args.join(" "); - print!("{}", args_final); - - if offline == Some(true) - && !args_final.contains("--uuid") - && !args_final.contains("--token") - { - return Err(errors::LaunchError::Requirements( - "Either --uuid or --token is missing in the arguments.".to_string(), - )); - } - - Ok(Self { - args: args_final, - java_exe, - jre, - }) - } - - /// Returns the launch configuration information. - /// - /// This method provides access to the arguments, Java executable path, and the optional Java Runtime Environment (JRE) path - /// that were used to initialize the `Launch` struct. - /// - /// # Returns - /// - /// * `(&str, &str, &Option)` - A tuple containing the final arguments string, the path to the Java executable, - /// and an optional path to the Java Runtime Environment. - pub fn info(&self) -> (&str, &str, &Option) { - (&self.args, &self.java_exe, &self.jre) - } - - /// Launches the Java Runtime Environment (JRE) with the specified arguments. - /// - /// This method is responsible for starting the Java Runtime Environment - /// with the arguments provided during the initialization of the `Launch` struct. - /// It is intended to be used for launching Minecraft or other Java applications. - /// - /// Required Args: - /// - UUID: LauncherUUID - /// - Token: BearerToken - /// - /// # Examples - /// - /// ```rust - /// use minecraft_essentials::Launch; - /// use std::path::Path; - /// - /// let jre_path = Path::new("/path/to/jre").to_path_buf(); - /// - /// let launcher = Launch::new(vec!["-Xmx1024M --uuid --token".to_string()], "/path/to/java".to_string(), Some(jre_path), None).expect("Expected Launch"); - /// launcher.launch_jre(); - /// ``` - pub fn launch_jre(&self) -> std::io::Result<()> { - let command_exe = format!("{} {:?} {}", self.java_exe, self.jre, self.args); - let mut command = Command::new(command_exe) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - - // Optionally, you can handle stdout and stderr in real-time - if let Some(ref mut stdout) = command.stdout { - let reader = BufReader::new(stdout); - for line in reader.lines() { - println!("{}", line?); - } - } - - if let Some(ref mut stderr) = command.stderr { - let reader = BufReader::new(stderr); - for line in reader.lines() { - eprintln!("{}", line?); - } - } - - // Wait for the command to finish - command.wait()?; - - Ok(()) - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 9c7a292..0000000 --- a/src/main.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::path::PathBuf; -use clap::{Args, Parser, Subcommand}; -use minecraft_essentials::{Launch, Oauth}; - -#[derive(Parser)] -#[command(version, long_about = None)] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - Version {}, - /// Oauth Check command. - Oauth(OauthArgs), - /// DeviceCode Check command. - DeviceCode(DeviceCodeArgs), - /// Custom Launch Check command - CustomLaunch(CustomLaunchArgs) -} - -#[derive(Args)] -struct OauthArgs { - client_id: String, - client_secret: String, - port: Option, - bedrockrelm: Option, -} - -#[derive(Args)] -struct CustomLaunchArgs { - token: String, - uuid: String, - optional_args: String, - java_exe: String, - jrepath: Option, - offline: Option -} - -#[derive(Args)] -struct DeviceCodeArgs { - client_id: String, - bedrockrelm: bool, -} - - -pub(crate) const EXPERIMENTAL_MESSAGE: &str = - "\x1b[33mNOTICE: You are using an experimental feature.\x1b[0m"; - -#[tokio::main] -async fn main() { - let cli = Cli::parse(); - match &cli.command { - Commands::Oauth(oauth_args) => handle_oauth(oauth_args).await, - Commands::DeviceCode(device_code_args) => handle_device_code(device_code_args).await, - Commands::Version {} => println!("{}", env!("CARGO_PKG_VERSION")), - Commands::CustomLaunch(handle_custom_launch_args) => handle_custom_launch(handle_custom_launch_args).await, - } -} - -async fn handle_oauth(oauth_args: &OauthArgs) { - let auth = Oauth::new(&oauth_args.client_id, Some(oauth_args.port.unwrap_or(8000))); - println!("URL: {} \nWaiting for Login........", auth.url()); - let auth_info = auth.launch(false, &oauth_args.client_secret).await.unwrap(); - println!( - "Bearer: {:?}, \n UUID: {:?}, \n Expire_in: {:?}, \n XtsToken: {:?}", - auth_info.access_token, auth_info.uuid, auth_info.expires_in, auth_info.xts_token - ); -} - -async fn handle_device_code(_device_code_args: &DeviceCodeArgs) { - println!("{}", EXPERIMENTAL_MESSAGE); -} - - -async fn handle_custom_launch(handle_custom_launch_args: &CustomLaunchArgs) { - let mut args = Vec::new(); - - args.push(format!("--token{}", handle_custom_launch_args.token)); - args.push(format!("--uuid{}", handle_custom_launch_args.uuid)); - - if !handle_custom_launch_args.optional_args.is_empty() { - args.push(handle_custom_launch_args.optional_args.clone()) - } - - - let launch = Launch::new( - args, - handle_custom_launch_args.java_exe.clone(), - handle_custom_launch_args.jrepath.clone(), - handle_custom_launch_args.offline - ).expect("Expected Launch"); - - let launch_info = launch.info(); - - println!("Launching with: {:?}", launch_info); - - let _ = launch.launch_jre(); -} \ No newline at end of file diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index 79e2243..0000000 --- a/src/tests.rs +++ /dev/null @@ -1,52 +0,0 @@ -use super::*; -use dotenv::dotenv; -use std::env; - -#[cfg(feature = "custom-auth")] -#[tokio::test] -async fn test_oauth_url() { - let _ = dotenv(); - let client_id = env::var("Client_ID").expect("Expected Client ID"); - let oauth = Oauth::new(&client_id, None); - let params = format!("client_id={}&response_type=code&redirect_uri=http://localhost:8000&response_mode=query&scope={}&state=12345", client_id, SCOPE); - let expected_url = format!( - "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize/?{}", - params - ); - assert_eq!(oauth.url(), expected_url); -} - -#[cfg(feature = "custom-auth")] -#[tokio::test] -async fn test_device_code_prelaunch() { - let _ = dotenv(); - let client_id = env::var("Client_ID").expect("Expected Client ID."); - let device_code = DeviceCode::new(&client_id).await.unwrap(); - - let (url, message, expires_in, user_code) = device_code.preinfo(); - - assert_eq!(url, device_code.url); - assert_eq!(message, device_code.message); - assert_eq!(expires_in, device_code.expires_in); - assert_eq!(user_code, device_code.user_code); -} - -#[cfg(feature = "custom-launch")] -#[tokio::test] -async fn test_custom_launch() { - let _ = dotenv(); - let args = vec![ - "--uuid:uuidtest".to_string(), - "--token:tokentest".to_string(), - ]; - let args_join = args.join(" "); - let jre = Some(PathBuf::from("/test/java")); - let java_exe = "/test/java"; - - let launch = - Launch::new(args, java_exe.to_string(), jre.clone(), Some(false)).expect("Expected Launch"); - let (launch_args, launch_java_exe, launch_jre) = launch.info(); - assert_eq!(args_join, launch_args); - assert_eq!(java_exe, launch_java_exe); - assert_eq!(jre, launch_jre.clone()); -}