Compare commits

..

38 Commits

Author SHA1 Message Date
scito
9c3edea75c fix/workaround build on macos: exclude 3.10 on macos in ci
See issue https://github.com/actions/setup-python/issues/649
2023-04-15 10:43:26 +02:00
scito
0ec1314b3d add nuitka to local build.sh; bump versions 2023-04-15 10:43:26 +02:00
scito
414e80c469 bump versions 2023-03-25 14:51:58 +01:00
scito
7f6959783f bump versions 2023-03-10 08:48:50 +01:00
scito
419f65fdea improve pyzbar missing warning; bump libs 2023-03-05 11:53:52 +01:00
dependabot[bot]
9783a5f4f2 Bump setuptools-git-versioning from 1.13.1 to 1.13.2
Bumps [setuptools-git-versioning](https://github.com/dolfinus/setuptools-git-versioning) from 1.13.1 to 1.13.2.
- [Release notes](https://github.com/dolfinus/setuptools-git-versioning/releases)
- [Changelog](https://github.com/dolfinus/setuptools-git-versioning/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/dolfinus/setuptools-git-versioning/compare/v1.13.1...v1.13.2)

---
updated-dependencies:
- dependency-name: setuptools-git-versioning
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 17:39:21 +01:00
scito
eccb1948c7 improve README for macOS 2023-02-26 21:19:45 +01:00
scito
01a38e570c optional build of exe and pipenv; update README 2023-02-25 09:24:37 +01:00
Damon McMinn
91b9490afb docs: update README with correct Fedora zbar dep
`zbar` is the available dependency: https://packages.fedoraproject.org/pkgs/zbar/zbar/
2023-02-22 20:06:06 +01:00
scito
11e530e04a docker no-install-recommends 2023-02-18 17:03:18 +01:00
scito
9e334748ac move Dockerfiles to docker/; update README 2023-02-18 17:03:18 +01:00
dependabot[bot]
05f3d89c42 Bump mypy from 1.0.0 to 1.0.1
Bumps [mypy](https://github.com/python/mypy) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-17 21:54:48 +01:00
scito
6bf02d045c write txt file in cv2 and cmd; upgrade protobuf 4.22.0 2023-02-17 19:22:58 +01:00
scito
5555dda9c2 release: update release notes text 2023-02-12 19:51:19 +01:00
scito
eb8ea3330f release: improve macos docs after macos tests
- build macos dmg
    - however, do not upload macos dmg to assets as it cannot be
      executed since the app and installer are not signed and notarized
- upload artifacts
- tested executable on macOS
2023-02-12 19:00:29 +01:00
scito
75e5d2671f fix: rename vscode workspace 2023-02-12 19:00:29 +01:00
scito
5e94c43140 ci_release: fix get tag name (checkout repo); print meta values 2023-02-12 19:00:29 +01:00
kvascev
2ef34a2d69 Fix typo in README.md
Change "form" to "from"
2023-02-08 17:09:05 +01:00
scito
8df61f6ed7 ci: no docker login for dependabot 2023-02-07 10:51:07 +01:00
dependabot[bot]
def5d58af0 Bump mypy from 0.991 to 1.0.0
Bumps [mypy](https://github.com/python/mypy) from 0.991 to 1.0.0.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.991...v1.0.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 21:16:05 +01:00
scito
ca23497c9a docu clean ups 2023-02-06 20:42:54 +01:00
scito
ebfe0dc58d ci_release: add release notes table 2023-02-06 11:54:09 +01:00
scito
78118c73e8 make zbar lib optional
- refactor: global variable renaming
2023-02-05 21:24:57 +01:00
scito
4fc5559e15 add version number to asset executables 2023-02-05 21:24:57 +01:00
scito
7faf530863 reproducible builds, build.sh: linux/arm64 2023-02-05 21:24:57 +01:00
scito
709aa111be ci_release: build linux arm64 exe 2023-02-03 22:14:53 +01:00
scito
fe60acb24f release: upload hashes as binary to keep eol 2023-01-30 20:44:33 +01:00
scito
5c36f07f41 ci_release: build exe without upload on PR 2023-01-30 14:39:50 +01:00
scito
861d7d0da8 build buster image in ci_docker for speeding up releases 2023-01-30 11:28:07 +01:00
scito
63fc21cc90 ci_release: upload hashes and run on pull_request and run scheduled 2023-01-30 11:28:07 +01:00
scito
d234cf4112 cv2: fix save as csv/json/keepass 2023-01-30 11:28:07 +01:00
scito
970dbd3759 cv2: save as csv/json/keppass by key command 2023-01-29 16:50:55 +01:00
scito
197347a3e9 update README 2023-01-27 20:58:33 +01:00
scito
365d5ac432 use hub.docker for linux exe build
since it is more stable and thus faster
2023-01-26 01:14:45 +01:00
scito
88ff584e47 ci: workaround for failing builds 2023-01-26 00:18:41 +01:00
scito
dbb5d8f755 workaround ghcr.io failed to copy
failed to copy: io: read/write on closed pipe

- use DockerHub instead of dhcr.io
- ref: https://github.com/containerd/containerd/issues/7972
2023-01-25 23:46:22 +01:00
scito
580f94256f Add files of Visual C++ 2013 Redistributable Package
- add files of vcredist_x64.exe as binary data to pyzbar
- thus avoid manual installation of vcredist_x64.exe
- test all qr readers in exe smoketests
- improve README
2023-01-25 23:14:38 +01:00
scito
f4bff86a5c fix curl upload windows
- set for pre-releases 99 in windows version
- fix TZ: use TZ=Europe/Zurich
2023-01-25 01:54:20 +01:00
22 changed files with 1649 additions and 832 deletions

View File

@@ -20,7 +20,7 @@ jobs:
strategy: strategy:
matrix: matrix:
python-version: ["3.x", "3.11", "3.10", "3.9", "3.8", "3.7"] python-version: ["3.x", "3.11", "3.9", "3.8", "3.7"]
platform: [ubuntu-latest, macos-latest, windows-latest] platform: [ubuntu-latest, macos-latest, windows-latest]
# exclude: # exclude:
@@ -72,6 +72,6 @@ jobs:
pytest-coverage-path: ./pytest-coverage.txt pytest-coverage-path: ./pytest-coverage.txt
junitxml-path: ./pytest.xml junitxml-path: ./pytest.xml
if: | if: |
matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest' false && matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest'
&& !contains(github.ref, 'refs/tags/') && !contains(github.ref, 'refs/tags/')

View File

@@ -50,15 +50,22 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages - name: Login to Github Packages
uses: docker/login-action@v2 uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -72,7 +79,7 @@ jobs:
# relative path to the place where source code with Dockerfile is located # relative path to the place where source code with Dockerfile is located
# TODO file:, move to docker/ # TODO file:, move to docker/
context: . context: .
file: Dockerfile file: docker/Dockerfile
# builder: ${{ steps.buildx.outputs.name }} # builder: ${{ steps.buildx.outputs.name }}
# Note: tags has to be all lower-case # Note: tags has to be all lower-case
pull: true pull: true
@@ -82,12 +89,19 @@ jobs:
ghcr.io/scito/extract_otp_secrets:latest ghcr.io/scito/extract_otp_secrets:latest
ghcr.io/scito/extract_otp_secrets:bullseye ghcr.io/scito/extract_otp_secrets:bullseye
# build on feature branches, push only on master branch # build on feature branches, push only on master branch
push: ${{ github.ref == 'refs/heads/master' }} push: ${{ github.ref == 'refs/heads/master' && github.secret_source == 'Actions'}}
- name: Image digest - name: Image digest
# TODO upload digests to assets # TODO upload digests to assets
run: | run: |
echo "extract_otp_secrets: ${{ steps.docker_build_qr_reader_latest.outputs.digest }}" echo "extract_otp_secrets digests: ${{ steps.docker_build_qr_reader_latest.outputs.digest }}"
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
with:
name: debian_digests
path: digests.txt
build-and-push-docker-alpine-image: build-and-push-docker-alpine-image:
name: Build Docker Alpine image and push to repositories name: Build Docker Alpine image and push to repositories
@@ -118,12 +132,14 @@ jobs:
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages - name: Login to Github Packages
uses: docker/login-action@v2 uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -136,7 +152,7 @@ jobs:
# relative path to the place where source code with Dockerfile is located # relative path to the place where source code with Dockerfile is located
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
context: . context: .
file: Dockerfile_only_txt file: docker/Dockerfile_only_txt
# builder: ${{ steps.buildx.outputs.name }} # builder: ${{ steps.buildx.outputs.name }}
# Note: tags has to be all lower-case # Note: tags has to be all lower-case
pull: true pull: true
@@ -146,12 +162,97 @@ jobs:
ghcr.io/scito/extract_otp_secrets:only-txt ghcr.io/scito/extract_otp_secrets:only-txt
ghcr.io/scito/extract_otp_secrets:alpine ghcr.io/scito/extract_otp_secrets:alpine
# build on feature branches, push only on master branch # build on feature branches, push only on master branch
push: ${{ github.ref == 'refs/heads/master' }} push: ${{ github.ref == 'refs/heads/master' && github.secret_source == 'Actions'}}
build-args: | build-args: |
RUN_TESTS=true RUN_TESTS=true
- name: Image digest - name: Image digest
# TODO upload digests to assets # TODO upload digests to assets
run: | run: |
echo "extract_otp_secrets:only-txt: ${{ steps.docker_build_only_txt.outputs.digest }}" echo "extract_otp_secrets:only-txt digests: ${{ steps.docker_build_only_txt.outputs.digest }}"
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
with:
name: alpine_digests
path: digests.txt
build-and-push-docker-buster-image:
name: Build Docker Buster image (for PyInstsaller) and push to repositories
# run only when code is compiling and tests are passing
runs-on: ubuntu-latest
# steps to perform in job
steps:
- name: Checkout code
uses: actions/checkout@v3
# avoid building if there are testing errors
- name: Run smoke test
run: |
sudo apt-get install -y libzbar0
python -m pip install --upgrade pip
pip install -U -r requirements-dev.txt
pip install -U .
pytest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
# setup Docker build action
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to DockerHub
uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages
uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_IO_TOKEN }}
- name: "Build image from Buster and push to GitHub Container Registry"
id: docker_build_buster
if: github.ref == 'refs/heads/master'
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64
# relative path to the place where source code with Dockerfile is located
# TODO file:, move to docker/
context: .
file: docker/Dockerfile
# builder: ${{ steps.buildx.outputs.name }}
build-args: |
BASE_IMAGE=python:3.11-slim-buster
# Note: tags has to be all lower-case
pull: true
tags: |
scit0/extract_otp_secrets:buster
push: ${{ github.secret_source == 'Actions' }}
- name: Image digest
# TODO upload digests to assets
run: |
echo "extract_otp_secrets digests: ${{ steps.docker_build_qr_reader_latest.outputs.digest }}"
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
with:
name: buster_digests
path: digests.txt

View File

@@ -1,10 +1,8 @@
name: release name: release
# https://data-dive.com/multi-os-deployment-in-cloud-using-pyinstaller-and-github-actions # https://data-dive.com/multi-os-deployment-in-cloud-using-pyinstaller-and-github-actions
# https://github.com/actions/create-release (archived)
# https://github.com/actions/upload-artifact # https://github.com/actions/upload-artifact
# https://github.com/actions/download-artifact # https://github.com/actions/download-artifact
# https://github.com/actions/upload-release-asset (archived)
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
# https://github.com/marketplace/actions/generate-release-hashes # https://github.com/marketplace/actions/generate-release-hashes
@@ -22,6 +20,10 @@ name: release
# https://peps.python.org/pep-0440/ # https://peps.python.org/pep-0440/
# https://semver.org/ # https://semver.org/
# macOS:
# https://pyinstaller.org/en/stable/usage.html#building-macos-app-bundles
# https://github.com/pyinstaller/pyinstaller/wiki/Recipe-OSX-Code-Signing
# Build matrix: # Build matrix:
# - Linux x86_64 glibc 2.35: ubuntu-latest # - Linux x86_64 glibc 2.35: ubuntu-latest
# - Linux x86_64 glibc 2.34: extract_otp_secrets:buster # - Linux x86_64 glibc 2.34: extract_otp_secrets:buster
@@ -36,27 +38,47 @@ on:
push: push:
tags: tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
pull_request:
schedule:
# Run weekly on default branch
- cron: '47 4 * * 6'
jobs: jobs:
create-release: create-release:
name: Create Release name: Create Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs:
date: ${{ steps.meta.outputs.date }}
version: ${{ steps.meta.outputs.version }}
inline_version: ${{ steps.meta.outputs.inline_version }}
tag_name: ${{ steps.meta.outputs.tag_name }}
tag_message: ${{ steps.meta.outputs.tag_message }}
steps: steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set meta data - name: Set meta data
id: meta id: meta
# Writing to env with >> $GITHUB_ENV is an alternative # Writing to env with >> $GITHUB_ENV is an alternative
run: | run: |
echo "date=$(TZ=Europe/Bern date +'%d.%m.%Y')" >> $GITHUB_OUTPUT echo "date=$(TZ=Europe/Zurich date +'%d.%m.%Y')" >> $GITHUB_OUTPUT
echo "version=${TAG_NAME/v/}" >> $GITHUB_OUTPUT echo "version=${TAG_NAME/v/}" >> $GITHUB_OUTPUT
echo "inline_version=${TAG_NAME/v/_}" >> $GITHUB_OUTPUT
echo "tag_name=${{ github.ref_name }}" >> $GITHUB_OUTPUT echo "tag_name=${{ github.ref_name }}" >> $GITHUB_OUTPUT
echo "tag_message=$(git tag -l --format='%(contents:subject)' ${{ github.ref_name }})" >> $GITHUB_OUTPUT echo "tag_message=$(git tag -l --format='%(contents:subject)' ${{ github.ref_name }})" >> $GITHUB_OUTPUT
env: env:
TAG_NAME: ${{ github.ref_name }} TAG_NAME: ${{ github.ref_name }}
PYTHONHASHSEED: 31
- name: Create Release - name: Create Release
id: create_release id: create_release
if: startsWith(github.ref, 'refs/tags/v')
# https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release
run: | run: |
# https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release echo "date: ${{ steps.meta.outputs.date }}"
echo "version: ${{ steps.meta.outputs.version }}"
echo "inline_version: ${{ steps.meta.outputs.inline_version }}"
echo "tag_name: ${{ steps.meta.outputs.tag_name }}"
echo "tag_message: ${{ steps.meta.outputs.tag_message }}"
response=$(curl \ response=$(curl \
-X POST \ -X POST \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
@@ -65,20 +87,37 @@ jobs:
https://api.github.com/repos/scito/extract_otp_secrets/releases \ https://api.github.com/repos/scito/extract_otp_secrets/releases \
--silent \ --silent \
--show-error \ --show-error \
-d '{"tag_name":"${{ github.ref }}","target_commitish":"master","name":"${{ steps.meta.outputs.version }} - ${{ steps.meta.outputs.date }}","body":"${{ steps.meta.outputs.tag_message }}","draft":true,"prerelease":false,"generate_release_notes":true}') -d '{"tag_name":"${{ github.ref }}","target_commitish":"master","name":"${{ steps.meta.outputs.version }} - ${{ steps.meta.outputs.date }}","body":"${{ steps.meta.outputs.tag_message }}\n\n## Executables\n\nDownload the executable for your platform and execute it, see [README.md](https://github.com/scito/extract_otp_secrets#readme)\n\n | Executable | Description |\n | --- | --- |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_x86_64 | Linux x86_64/amd64 (glibc >= 2.28) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_arm64 | Linux arm64 (glibc >= 2.28) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_x86_64.exe | Windows x86_64/amd64/x64 |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_arm64.exe | N/A |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64.dmg | N/A, see [README.md](https://github.com/scito/extract_otp_secrets#readme) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64.pkg | N/A, see [README.md](https://github.com/scito/extract_otp_secrets#readme) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64 | MacOS x86_64/amd64 (bare executable, see [README.md](https://github.com/scito/extract_otp_secrets#readme); optional libzbar must be installed manually, see [README.md](https://github.com/scito/extract_otp_secrets#readme)) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_arm64 | N/A |\n","draft":true,"prerelease":false,"generate_release_notes":true}')
echo upload_url=$(jq '.upload_url' <<< "$response") >> $GITHUB_OUTPUT echo upload_url=$(jq '.upload_url' <<< "$response") >> $GITHUB_OUTPUT
echo $(jq -r '.upload_url' <<< "$response") > release_url.txt echo $(jq -r '.upload_url' <<< "$response") > release_url.txt
echo $(jq -r '.id' <<< "$response") > release_id.txt
- name: Save Release URL File for publish - name: Save Release URL File for publish
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: release_url name: release_url
path: release_url.txt path: release_url.txt
- name: Save asset upload id for publish
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v3
with:
name: release_id
path: release_id.txt
build-and-push-docker-image: build-linux-executable-in-docker:
name: Build Linux release in docker container name: Build ${{ matrix.PLATFORM }} release in docker container
# run only when code is compiling and tests are passing # run only when code is compiling and tests are passing
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: create-release needs: create-release
strategy:
matrix:
include:
- PLATFORM: linux/amd64
EXE: extract_otp_secrets_linux_x86_64
ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_x86_64
- PLATFORM: linux/arm64
EXE: extract_otp_secrets_linux_arm64
ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_arm64
# steps to perform in job # steps to perform in job
steps: steps:
@@ -101,70 +140,71 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages - name: Login to Github Packages
uses: docker/login-action@v2 uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GHCR_IO_TOKEN }} password: ${{ secrets.GHCR_IO_TOKEN }}
- name: "Build image from Buster and push to GitHub Container Registry"
id: docker_build_buster
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64
# relative path to the place where source code with Dockerfile is located
# TODO file:, move to docker/
context: .
file: Dockerfile
# builder: ${{ steps.buildx.outputs.name }}
build-args: |
BASE_IMAGE=python:3.11-slim-buster
# Note: tags has to be all lower-case
pull: true
tags: |
ghcr.io/scito/extract_otp_secrets:buster
push: true
# # https://stackoverflow.com/a/61155718/1663871
# - name: Build docker images
# run: docker build -t local < .devcontainer/Dockerfile
# - name: Run tests
# run: docker run -it -v $PWD:/srv -w/srv local make test
- name: Image digest - name: Image digest
# TODO upload digests to assets # TODO upload digests to assets
run: | run: |
echo "extract_otp_secrets: ${{ steps.docker_build_buster.outputs.digest }}" echo "extract_otp_secrets: ${{ steps.docker_build_buster.outputs.digest }}"
- name: Run Pyinstaller in container # TODO use local docker image https://stackoverflow.com/a/61155718/1663871
# https://github.com/multiarch/qemu-user-static
# https://hub.docker.com/r/multiarch/qemu-user-static/
- name: Run Pyinstaller in container for ${{ matrix.EXE }}
run: | run: |
# TODO use local docker image https://stackoverflow.com/a/61155718/1663871 docker run --pull always --rm --privileged multiarch/qemu-user-static --reset -p yes
docker run --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files ghcr.io/scito/extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U -r /files/requirements.txt && pip install pyinstaller && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py' docker run --platform ${{ matrix.PLATFORM }} --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files scit0/extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name ${{ matrix.EXE }} --distpath /files/dist/ /files/src/extract_otp_secrets.py'
- name: Smoke tests - name: Smoke tests ${{ matrix.PLATFORM }}
if: matrix.PLATFORM == 'linux/amd64'
run: | run: |
dist/extract_otp_secrets_linux_x86_64 -V dist/${{ matrix.EXE }} -V
dist/extract_otp_secrets_linux_x86_64 -h dist/${{ matrix.EXE }} -h
dist/extract_otp_secrets_linux_x86_64 example_export.png dist/${{ matrix.EXE }} example_export.png
dist/extract_otp_secrets_linux_x86_64 - < example_export.txt dist/${{ matrix.EXE }} - < example_export.txt
dist/${{ matrix.EXE }} --qr ZBAR example_export.png
dist/${{ matrix.EXE }} --qr QREADER example_export.png
dist/${{ matrix.EXE }} --qr QREADER_DEEP example_export.png
dist/${{ matrix.EXE }} --qr CV2 example_export.png
dist/${{ matrix.EXE }} --qr CV2_WECHAT example_export.png
- name: Smoke tests ${{ matrix.PLATFORM }}
if: matrix.PLATFORM == 'linux/arm64'
run: |
docker run --platform ${{ matrix.PLATFORM }} --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files scit0/extract_otp_secrets -c 'dist/${{ matrix.EXE }} -V && dist/${{ matrix.EXE }} -h && dist/${{ matrix.EXE }} example_export.png && dist/${{ matrix.EXE }} - < example_export.txt && dist/${{ matrix.EXE }} --qr ZBAR example_export.png && dist/${{ matrix.EXE }} --qr QREADER example_export.png && dist/${{ matrix.EXE }} --qr QREADER_DEEP example_export.png && dist/${{ matrix.EXE }} --qr CV2 example_export.png && dist/${{ matrix.EXE }} --qr CV2_WECHAT example_export.png'
- name: Load Release URL File from release job - name: Load Release URL File from release job
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: release_url name: release_url
- name: Display structure of files - name: Display structure of files
run: ls -R run: ls -R
- name: Upload EXE to artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.EXE }}
path: dist/${{ matrix.EXE }}
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
# TODO only for tags if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: | run: |
response=$(curl \ response=$(curl \
-X POST \ -X POST \
@@ -174,11 +214,11 @@ jobs:
-H "X-GitHub-Api-Version: 2022-11-28" \ -H "X-GitHub-Api-Version: 2022-11-28" \
--silent \ --silent \
--show-error \ --show-error \
--data-binary @dist/extract_otp_secrets_linux_x86_64 \ --data-binary @dist/${{ matrix.EXE }} \
$(cat release_url.txt)=extract_otp_secrets_linux_x86_64) $(cat release_url.txt)=${{ matrix.ASSET_NAME }})
build: build-native-executables:
name: Build packages name: Build native packages
needs: create-release needs: create-release
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@@ -190,35 +230,41 @@ jobs:
# TODO add --icon # TODO add --icon
# TODO add --manifest # TODO add --manifest
# TODO find more elegant solution for pyzbar\libiconv.dll and pyzbar\libzbar-64.dll # TODO find more elegant solution for pyzbar\libiconv.dll and pyzbar\libzbar-64.dll
CMD_BUILD: | # Files of Visual C++ 2013 Redistributable Package: https://support.microsoft.com/en-us/topic/update-for-visual-c-2013-redistributable-package-d8ccd6a5-4e26-c290-517b-8da6cfdf4f10
pyinstaller -y --add-data "$($Env:pythonLocation)\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libiconv.dll;pyzbar" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libzbar-64.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py EXE: extract_otp_secrets.exe
OUT_FILE_NAME: extract_otp_secrets.exe ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_win_x86_64.exe
ASSET_NAME: extract_otp_secrets_win_x86_64.exe
ASSET_MIME: application/vnd.microsoft.portable-executable ASSET_MIME: application/vnd.microsoft.portable-executable
UPLOAD: true UPLOAD: true
CMD_BUILD: |
pyinstaller -y --add-data "$($Env:pythonLocation)\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libiconv.dll;pyzbar" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libzbar-64.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\msvcr120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\msvcp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vcamp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vcomp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vccorlib120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120u.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120chs.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120cht.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120deu.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120enu.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120esn.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120fra.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120ita.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120jpn.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120kor.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120rus.dll;pyzbar" --onefile --version-file build\win_file_version_info.txt --name extract_otp_secrets.exe src\extract_otp_secrets.py
- os: macos-11 - os: macos-11
TARGET: macos TARGET: macos
# TODO add --icon # https://pyinstaller.org/en/stable/spec-files.html#spec-file-options-for-a-macos-bundle
# TODO add --osx-bundle-identifier EXE: extract_otp_secrets
# TODO add --codesign-identity ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_macos_x86_64
# TODO add --osx-entitlements-file DMG: extract_otp_secrets.dmg
# TODO https://pyinstaller.org/en/stable/spec-files.html#spec-file-options-for-a-macos-bundle ASSET_NAME_DMG: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_macos_x86_64.dmg
# TODO --target-arch universal2 ASSET_MIME: application/octet-stream
CMD_BUILD: |
pyinstaller -y --add-data $macos_python_path/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --argv-emulation src/extract_otp_secrets.py
OUT_FILE_NAME: extract_otp_secrets
ASSET_NAME: extract_otp_secrets_macos_x86_64
ASSET_MIME: application/x-newton-compatible-pkg
UPLOAD: true UPLOAD: true
CMD_BUILD: |
VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2023' envsubst < installer/extract_otp_secrets_macos_template.spec > extract_otp_secrets_macos.spec
pyinstaller -y extract_otp_secrets_macos.spec
installer/build_dmg.sh
- os: ubuntu-latest - os: ubuntu-latest
TARGET: linux TARGET: linux
CMD_BUILD: | EXE: extract_otp_secrets_ubuntu
pyinstaller -y --add-data $pythonLocation/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile src/extract_otp_secrets.py ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_x86_64_ubuntu_latest
OUT_FILE_NAME: extract_otp_secrets
ASSET_NAME: extract_otp_secrets_linux_x86_64_ubuntu_latest
ASSET_MIME: application/x-executable ASSET_MIME: application/x-executable
UPLOAD: false UPLOAD: false
CMD_BUILD: |
pyinstaller -y --add-data $pythonLocation/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_ubuntu src/extract_otp_secrets.py
steps: steps:
- name: Output path
if: runner.os == 'Windows'
run: echo "$($Env:Path)"
- name: List Windir
if: runner.os == 'Windows'
run: ls "$($Env:WinDir)\system32"
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set macos macos_python_path - name: Set macos macos_python_path
# TODO use variable for Python version # TODO use variable for Python version
@@ -235,60 +281,127 @@ jobs:
- name: Install zbar shared lib for QReader (macOS) - name: Install zbar shared lib for QReader (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS'
run: | run: |
brew install zbar brew install zbar create-dmg
- name: Install dependencies - name: Install dependencies
# TODO fix --use-pep517 # TODO fix --use-pep517
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -U -r requirements-dev.txt pip install -U -r requirements-dev.txt
pip install -U . pip install -U .
- name: Create Windows file_version_info.txt - name: Create Windows win_file_version_info.txt
shell: bash shell: bash
run: | run: |
mkdir -p build/ mkdir -p build/
VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E "s/^([0-9]+).*/\1/") VERSION_BUILD=$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) YEARS='2020-2023' envsubst < file_version_info_template.txt > build/file_version_info.txt VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n "s/^([0-9]+).*/\1/p") VERSION_BUILD=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n -e"s/^[0-9]+.+/99/p")$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) COPYRIGHT_YEARS='2020-2023' envsubst < installer/win_file_version_info_template.txt > build/win_file_version_info.txt
- name: Build with pyinstaller for ${{ matrix.TARGET }} - name: Build with pyinstaller for ${{ matrix.TARGET }}
env:
# Reproducible build: https://pyinstaller.org/en/stable/advanced-topics.html#creating-a-reproducible-build
PYTHONHASHSEED: 31
run: ${{ matrix.CMD_BUILD }} run: ${{ matrix.CMD_BUILD }}
- name: Smoke tests for generated exe (general) - name: Smoke tests for generated exe (general)
run: | run: |
dist/${{ matrix.OUT_FILE_NAME }} -V dist/${{ matrix.EXE }} -V
dist/${{ matrix.OUT_FILE_NAME }} -h dist/${{ matrix.EXE }} -h
dist/${{ matrix.OUT_FILE_NAME }} example_export.png dist/${{ matrix.EXE }} example_export.png
dist/${{ matrix.EXE }} --qr ZBAR example_export.png
dist/${{ matrix.EXE }} --qr QREADER example_export.png
dist/${{ matrix.EXE }} --qr QREADER_DEEP example_export.png
dist/${{ matrix.EXE }} --qr CV2 example_export.png
dist/${{ matrix.EXE }} --qr CV2_WECHAT example_export.png
- name: Smoke tests for generated exe (stdin) - name: Smoke tests for generated exe (stdin)
if: runner.os != 'Windows' if: runner.os != 'Windows'
run: | run: |
dist/${{ matrix.OUT_FILE_NAME }} - < example_export.txt dist/${{ matrix.EXE }} - < example_export.txt
- name: Load Release URL File from release job - name: Load Release URL File from release job
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: release_url name: release_url
- name: Load Release Id File from release job
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3
with:
name: release_id
- name: Display structure of files - name: Display structure of files
run: ls -R run: ls -R
- name: Upload Release Asset - name: Set meta data
id: upload-release-asset id: meta
# TODO only for tags if: startsWith(github.ref, 'refs/tags/v')
if: ${{ matrix.UPLOAD }}
shell: bash shell: bash
run: | run: |
curl \ echo "release_id=$(cat release_id.txt)" >> $GITHUB_OUTPUT
-X POST \ echo "upload_url=https://uploads.github.com/repos/scito/extract_otp_secrets/releases/$(cat release_id.txt)/assets?name=" >> $GITHUB_OUTPUT
- name: Upload EXE to artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.EXE }}
path: dist/${{ matrix.EXE }}
- name: Upload DMG to artifacts
uses: actions/upload-artifact@v3
if: runner.os == 'macOS'
with:
name: ${{ matrix.DMG }}
path: dist/${{ matrix.DMG }}
- name: Upload Release Asset
id: upload-release-asset
if: matrix.UPLOAD && startsWith(github.ref, 'refs/tags/v')
run: |
curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: ${{ matrix.ASSET_MIME }}" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data-binary @dist/${{ matrix.EXE }} ${{ steps.meta.outputs.upload_url }}=${{ matrix.ASSET_NAME }}
- name: Upload Release Asset DMG (macOS)
id: upload-release-asset-dmg
if: false && matrix.UPLOAD && startsWith(github.ref, 'refs/tags/v') && runner.os == 'macOS'
run: |
curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: ${{ matrix.ASSET_MIME }}" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data-binary @dist/${{ matrix.DMG }} ${{ steps.meta.outputs.upload_url }}=${{ matrix.ASSET_NAME_DMG }}
upload-hashes:
name: Upload hashes
if: startsWith(github.ref, 'refs/tags/v')
needs:
- build-linux-executable-in-docker
- build-native-executables
runs-on: ubuntu-latest
steps:
- name: Load Release Id File from release job
uses: actions/download-artifact@v3
with:
name: release_id
- name: Set meta data
id: meta
run: |
echo "release_id=$(cat release_id.txt)" >> $GITHUB_OUTPUT
echo "upload_url=https://uploads.github.com/repos/scito/extract_otp_secrets/releases/$(cat release_id.txt)/assets?name=" >> $GITHUB_OUTPUT
- name: Calculate and upload hashes from assets
run: |
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
for asset_url in $(curl \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
-H "Content-Type: ${{ matrix.ASSET_MIME }}" \ -H "Authorization: Bearer $GITHUB_TOKEN"\
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"\
-H "X-GitHub-Api-Version: 2022-11-28" \ -H "X-GitHub-Api-Version: 2022-11-28" \
--silent \
--show-error \ --show-error \
--data-binary @dist/${{ matrix.OUT_FILE_NAME }} \ https://api.github.com/repos/scito/extract_otp_secrets/releases/${{ steps.meta.outputs.release_id }}/assets |
$(cat release_url.txt)=${{ matrix.ASSET_NAME }} jq -r '.[].url'); do
echo "Download $asset_url"
# run: | name=$(curl \
# response=$(curl \ -H "Accept: application/vnd.github+json" \
# -X POST \ -H "Authorization: Bearer $GITHUB_TOKEN"\
# -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \
# -H "Content-Type: ${{ matrix.ASSET_MIME }}" \ --output-dir assets \
# -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"\ -L \
# -H "X-GitHub-Api-Version: 2022-11-28" \ $asset_url |
# --silent \ jq -r '.name')
# --show-error \ curl \
# --data-binary @dist/${{ matrix.OUT_FILE_NAME }} \ -H "Accept: application/octet-stream" \
# $(cat release_url.txt)=${{ matrix.ASSET_NAME }}) -H "Authorization: Bearer $GITHUB_TOKEN"\
-H "X-GitHub-Api-Version: 2022-11-28" \
--create-dirs \
--output-dir assets \
-L \
-o $name \
$asset_url
done
(cd assets/ && sha256sum * > ../sha256_hashes.txt)
curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: text/plain" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data-binary @sha256_hashes.txt ${{ steps.meta.outputs.upload_url }}=sha256_hashes.txt
(cd assets/ && sha512sum * > ../sha512_hashes.txt)
curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: text/plain" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data-binary @sha512_hashes.txt ${{ steps.meta.outputs.upload_url }}=sha512_hashes.txt

11
.gitignore vendored
View File

@@ -20,8 +20,17 @@ dist/
pytest-coverage.txt pytest-coverage.txt
tests/reports/ tests/reports/
dist_*/ dist_*/
*.spec
file_version_info_python.txt file_version_info_python.txt
file_version_info_explorer.txt file_version_info_explorer.txt
file_version_info.txt file_version_info.txt
assets/*
extract_otp_secrets_linux_arm64.spec
extract_otp_secrets_linux_x86_64_bullseye.spec
extract_otp_secrets_linux_x86_64.spec
extract_otp_secrets.spec
test.txt
extract_otp_secrets.build/
extract_otp_secrets.dist/
extract_otp_secrets.onefile-build/
extract_otp_secrets.bin

View File

@@ -9,6 +9,7 @@ opencv-contrib-python = "*"
# for macOS: opencv-contrib-python = "<=4.7.0" # for macOS: opencv-contrib-python = "<=4.7.0"
# for PYTHON <= 3.7: typing_extensions = "*" # for PYTHON <= 3.7: typing_extensions = "*"
pillow = "*" pillow = "*"
pyzbar = "*"
protobuf = "*" protobuf = "*"
qrcode = "*" qrcode = "*"
qreader = "<2.0.0" qreader = "<2.0.0"
@@ -19,6 +20,7 @@ flake8 = "*"
gfm-toc = "*" gfm-toc = "*"
mypy = "*" mypy = "*"
mypy-protobuf = "*" mypy-protobuf = "*"
nuitka = "*"
pylint = "*" pylint = "*"
pytest = "*" pytest = "*"
pytest-cov = "*" pytest-cov = "*"

791
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "41edd4aebe075d6c39d035ec7cb10f0253a3ad21f9b4aa5b9c57deccca87874f" "sha256": "d9664aaa55d180a006624f7c65977a97f23b7c25444e2b2abe1f85ceb0e3337b"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -26,173 +26,161 @@
}, },
"numpy": { "numpy": {
"hashes": [ "hashes": [
"sha256:0044f7d944ee882400890f9ae955220d29b33d809a038923d88e4e01d652acd9", "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22",
"sha256:0e3463e6ac25313462e04aea3fb8a0a30fb906d5d300f58b3bc2c23da6a15398", "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f",
"sha256:179a7ef0889ab769cc03573b6217f54c8bd8e16cef80aad369e1e8185f994cd7", "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9",
"sha256:2386da9a471cc00a1f47845e27d916d5ec5346ae9696e01a8a34760858fe9dd2", "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96",
"sha256:26089487086f2648944f17adaa1a97ca6aee57f513ba5f1c0b7ebdabbe2b9954", "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0",
"sha256:28bc9750ae1f75264ee0f10561709b1462d450a4808cd97c013046073ae64ab6", "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a",
"sha256:28e418681372520c992805bb723e29d69d6b7aa411065f48216d8329d02ba032", "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281",
"sha256:442feb5e5bada8408e8fcd43f3360b78683ff12a4444670a7d9e9824c1817d36", "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04",
"sha256:6ec0c021cd9fe732e5bab6401adea5a409214ca5592cd92a114f7067febcba0c", "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468",
"sha256:7094891dcf79ccc6bc2a1f30428fa5edb1e6fb955411ffff3401fb4ea93780a8", "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253",
"sha256:84e789a085aabef2f36c0515f45e459f02f570c4b4c4c108ac1179c34d475ed7", "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756",
"sha256:87a118968fba001b248aac90e502c0b13606721b1343cdaddbc6e552e8dfb56f", "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a",
"sha256:8e669fbdcdd1e945691079c2cae335f3e3a56554e06bbd45d7609a6cf568c700", "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb",
"sha256:ad2925567f43643f51255220424c23d204024ed428afc5aad0f86f3ffc080086", "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d",
"sha256:b0677a52f5d896e84414761531947c7a330d1adc07c3a4372262f25d84af7bf7", "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0",
"sha256:b07b40f5fb4fa034120a5796288f24c1fe0e0580bbfff99897ba6267af42def2", "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910",
"sha256:b09804ff570b907da323b3d762e74432fb07955701b17b08ff1b5ebaa8cfe6a9", "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978",
"sha256:b162ac10ca38850510caf8ea33f89edcb7b0bb0dfa5592d59909419986b72407", "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5",
"sha256:b31da69ed0c18be8b77bfce48d234e55d040793cebb25398e2a7d84199fbc7e2", "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f",
"sha256:caf65a396c0d1f9809596be2e444e3bd4190d86d5c1ce21f5fc4be60a3bc5b36", "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a",
"sha256:cfa1161c6ac8f92dea03d625c2d0c05e084668f4a06568b77a25a89111621566", "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5",
"sha256:dae46bed2cb79a58d6496ff6d8da1e3b95ba09afeca2e277628171ca99b99db1", "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2",
"sha256:ddc7ab52b322eb1e40521eb422c4e0a20716c271a306860979d450decbb51b8e", "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d",
"sha256:de92efa737875329b052982e37bd4371d52cabf469f83e7b8be9bb7752d67e51", "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95",
"sha256:e274f0f6c7efd0d577744f52032fdd24344f11c5ae668fe8d01aac0422611df1", "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5",
"sha256:ed5fb71d79e771ec930566fae9c02626b939e37271ec285e9efaf1b5d4370e7d", "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d",
"sha256:ef85cf1f693c88c1fd229ccd1055570cb41cdf4875873b7728b6301f12cd05bf", "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780",
"sha256:f1b739841821968798947d3afcefd386fa56da0caf97722a5de53e07c4ccedc7" "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"
], ],
"markers": "python_version >= '3.10'", "markers": "python_version >= '3.10'",
"version": "==1.24.1" "version": "==1.24.2"
}, },
"opencv-contrib-python": { "opencv-contrib-python": {
"hashes": [ "hashes": [
"sha256:1a48c2f24440cfd6e49c84dbe39c39feff5efbc90be8299c76e7141973d403b6", "sha256:641ca83b34a9d3e8ef2da70533c6e4e3f076ffb0db69b963d82899cc53e9b3c2",
"sha256:2b8e3a1a7af31ebed28487d161ca4be0edd0b0e241667c6e9c842ac683313b2f", "sha256:698c6b6203831f6573e04258be197e3bfde97fb7279fb614e39d75a8bd5818fb",
"sha256:2f0c32b0f2f55255632a44bdcfa185f88c7fb6d2616869942aff9d5a39df4997", "sha256:8cad628ea6cc493f6c56140d7edc86f7ed8de528e18e44311e42b390a7d9996e",
"sha256:35e9a3809da10a47189c06d4d78b8e7821b9a3578dec8cbddf6ee1675bd83557", "sha256:ab33fa2385ec7e70b9d484293f6f1f3707933045af4d18bb3b0a0290fa44370f",
"sha256:3a00e12546e5578f6bb7ed408c37fcfea533d74e9691cfaf40926f6b43295577", "sha256:b54c2e8bb636e367d29bde48fae2aa52c43b782265cf65838a1fe852006cdd94",
"sha256:6d1c993811f92ddd7919314ada7b9be1f23db1c73f1384915c834dee8549c0b9", "sha256:d1fef5ae16dfa73022749165e029e85eb0f399503470c0df1f84c95633f4ae52",
"sha256:7a08f9d1f9dd52de63a7bb448ab7d6d4a1a85b767c2358501d968d1e4d95098d", "sha256:fefc5f7f1eef3125f78242afe5c989057b36c2f015619698c741b04f4503f913"
"sha256:7a75f1775790106e54bcfb101c0e00e1f801a57d9baebc82d0b6758fc83a4ca0",
"sha256:86f4b60b9536948f16d2170ba3a9b22d3955a957dc61a9bc56e53692c6db2c7e",
"sha256:9829e6efedde1d1b8419c5bd4d62d289ecbf44ae35b843c6da9e3cbcba1a9a8a",
"sha256:abc6adfa8694f71a4caffa922b279bd9d96954a37eee40b147f613c64310b411",
"sha256:b4033a164b2e2ea0049ba8c1194dab82dca680953ac36f33d1cc2c060906555f",
"sha256:e3967b1f3d74b8c70be724dbc07921faec87e8806cc87b2db5e7057815d6a08c",
"sha256:e770e9f653a0e5e72b973adb8213fae2df4642730ba1faf31e73a54287a4d5d4"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.7.0.68" "version": "==4.7.0.72"
}, },
"opencv-python": { "opencv-python": {
"hashes": [ "hashes": [
"sha256:3a00e12546e5578f6bb7ed408c37fcfea533d74e9691cfaf40926f6b43295577", "sha256:3424794a711f33284581f3c1e4b071cfc827d02b99d6fd9a35391f517c453306",
"sha256:6d1c993811f92ddd7919314ada7b9be1f23db1c73f1384915c834dee8549c0b9", "sha256:7a297e7651e22eb17c265ddbbc80e2ba2a8ff4f4a1696a67c45e5f5798245842",
"sha256:7a08f9d1f9dd52de63a7bb448ab7d6d4a1a85b767c2358501d968d1e4d95098d", "sha256:812af57553ec1c6709060c63f6b7e9ad07ddc0f592f3ccc6d00c71e0fe0e6376",
"sha256:86f4b60b9536948f16d2170ba3a9b22d3955a957dc61a9bc56e53692c6db2c7e", "sha256:cd08343654c6b88c5a8c25bf425f8025aed2e3189b4d7306b5861d32affaf737",
"sha256:9829e6efedde1d1b8419c5bd4d62d289ecbf44ae35b843c6da9e3cbcba1a9a8a", "sha256:d4f8880440c433a0025d78804dda6901d1e8e541a561dda66892d90290aef881",
"sha256:abc6adfa8694f71a4caffa922b279bd9d96954a37eee40b147f613c64310b411", "sha256:ebfc0a3a2f57716e709028b992e4de7fd8752105d7a768531c4f434043c6f9ff",
"sha256:e770e9f653a0e5e72b973adb8213fae2df4642730ba1faf31e73a54287a4d5d4" "sha256:eda115797b114fc16ca6f182b91c5d984f0015c19bec3145e55d33d708e9bae1"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==4.7.0.68" "version": "==4.7.0.72"
}, },
"pillow": { "pillow": {
"hashes": [ "hashes": [
"sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33", "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1",
"sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b", "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba",
"sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e", "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a",
"sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35", "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799",
"sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153", "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51",
"sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9", "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb",
"sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569", "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5",
"sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57", "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270",
"sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8", "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6",
"sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1", "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47",
"sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264", "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf",
"sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157", "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e",
"sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9", "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b",
"sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133", "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66",
"sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9", "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865",
"sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab", "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec",
"sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6", "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c",
"sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5", "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1",
"sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df", "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38",
"sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503", "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906",
"sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b", "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705",
"sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa", "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef",
"sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327", "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc",
"sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493", "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f",
"sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d", "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf",
"sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4", "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392",
"sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4", "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d",
"sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35", "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe",
"sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2", "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32",
"sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c", "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5",
"sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011", "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7",
"sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a", "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44",
"sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e", "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d",
"sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f", "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3",
"sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848", "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625",
"sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57", "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e",
"sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f", "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829",
"sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c", "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089",
"sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9", "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3",
"sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5", "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78",
"sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9", "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96",
"sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d", "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964",
"sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0", "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597",
"sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1", "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99",
"sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e", "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a",
"sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815", "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140",
"sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0", "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7",
"sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b", "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16",
"sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd", "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903",
"sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c", "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1",
"sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3", "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296",
"sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab", "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572",
"sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858", "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115",
"sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5", "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a",
"sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee", "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd",
"sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343", "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4",
"sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb", "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1",
"sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47", "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb",
"sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed", "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa",
"sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837", "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a",
"sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286", "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569",
"sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28", "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c",
"sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628", "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf",
"sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df", "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082",
"sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d", "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062",
"sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d", "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"
"sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a",
"sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6",
"sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336",
"sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132",
"sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070",
"sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe",
"sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a",
"sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd",
"sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391",
"sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a",
"sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"
], ],
"index": "pypi", "index": "pypi",
"version": "==9.4.0" "version": "==9.5.0"
}, },
"protobuf": { "protobuf": {
"hashes": [ "hashes": [
"sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30", "sha256:13233ee2b9d3bd9a5f216c1fa2c321cd564b93d8f2e4f521a85b585447747997",
"sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b", "sha256:23452f2fdea754a8251d0fc88c0317735ae47217e0d27bf330a30eec2848811a",
"sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc", "sha256:52f0a78141078077cfe15fe333ac3e3a077420b9a3f5d1bf9b5fe9d286b4d881",
"sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791", "sha256:70659847ee57a5262a65954538088a1d72dfc3e9882695cab9f0c54ffe71663b",
"sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717", "sha256:7760730063329d42a9d4c4573b804289b738d4931e363ffbe684716b796bde51",
"sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec", "sha256:7cf56e31907c532e460bb62010a513408e6cdf5b03fb2611e4b67ed398ad046d",
"sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7", "sha256:8b54f56d13ae4a3ec140076c9d937221f887c8f64954673d46f63751209e839a",
"sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab", "sha256:d14fc1a41d1a1909998e8aff7e80d2a7ae14772c4a70e4bf7db8a36690b54425",
"sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2", "sha256:d4b66266965598ff4c291416be429cef7989d8fae88b55b62095a2331511b3fa",
"sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5", "sha256:e0e630d8e6a79f48c557cd1835865b593d0547dce221c66ed1b827de59c66c97",
"sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1", "sha256:ecae944c6c2ce50dda6bf76ef5496196aeb1b85acb95df5843cd812615ec4b61",
"sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462", "sha256:f08aa300b67f1c012100d8eb62d47129e53d1150f4469fd78a29fa3cb68c66f2",
"sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97", "sha256:f2f4710543abec186aee332d6852ef5ae7ce2e9e807a3da570f36de5a732d88e"
"sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.21.12" "version": "==4.22.3"
},
"pypng": {
"hashes": [
"sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c",
"sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"
],
"version": "==0.20220715.0"
}, },
"pyzbar": { "pyzbar": {
"hashes": [ "hashes": [
@@ -200,14 +188,16 @@
"sha256:4559628b8192feb25766d954b36a3753baaf5c97c03135aec7e4a026036b475d", "sha256:4559628b8192feb25766d954b36a3753baaf5c97c03135aec7e4a026036b475d",
"sha256:8f4c5264c9c7c6b9f20d01efc52a4eba1ded47d9ba857a94130afe33703eb518" "sha256:8f4c5264c9c7c6b9f20d01efc52a4eba1ded47d9ba857a94130afe33703eb518"
], ],
"index": "pypi",
"version": "==0.1.9" "version": "==0.1.9"
}, },
"qrcode": { "qrcode": {
"hashes": [ "hashes": [
"sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578" "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a",
"sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"
], ],
"index": "pypi", "index": "pypi",
"version": "==7.3.1" "version": "==7.4.2"
}, },
"qreader": { "qreader": {
"hashes": [ "hashes": [
@@ -215,24 +205,24 @@
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.2" "version": "==1.3.2"
},
"typing-extensions": {
"hashes": [
"sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
"sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
],
"markers": "python_version >= '3.7'",
"version": "==4.5.0"
} }
}, },
"develop": { "develop": {
"astroid": { "astroid": {
"hashes": [ "hashes": [
"sha256:14c1603c41cc61aae731cad1884a073c4645e26f126d13ac8346113c95577f3b", "sha256:6e61b85c891ec53b07471aec5878f4ac6446a41e590ede0f2ce095f39f7d49dd",
"sha256:6afc22718a48a689ca24a97981ad377ba7fb78c133f40335dfd16772f29bcfb1" "sha256:dea89d9f99f491c66ac9c04ebddf91e4acf8bd711722175fe6245c0725cc19bb"
], ],
"markers": "python_full_version >= '3.7.2'", "markers": "python_full_version >= '3.7.2'",
"version": "==2.13.3" "version": "==2.15.2"
},
"attrs": {
"hashes": [
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
"sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
],
"markers": "python_version >= '3.6'",
"version": "==22.2.0"
}, },
"build": { "build": {
"hashes": [ "hashes": [
@@ -247,60 +237,60 @@
"toml" "toml"
], ],
"hashes": [ "hashes": [
"sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45", "sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93",
"sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809", "sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013",
"sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4", "sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f",
"sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b", "sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21",
"sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7", "sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462",
"sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0", "sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc",
"sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0", "sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df",
"sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea", "sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1",
"sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2", "sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235",
"sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a", "sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934",
"sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45", "sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9",
"sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b", "sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1",
"sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209", "sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48",
"sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca", "sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4",
"sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab", "sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe",
"sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095", "sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a",
"sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7", "sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b",
"sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6", "sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21",
"sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af", "sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d",
"sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499", "sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa",
"sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831", "sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367",
"sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637", "sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535",
"sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2", "sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152",
"sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb", "sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e",
"sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029", "sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539",
"sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc", "sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1",
"sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8", "sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925",
"sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f", "sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0",
"sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2", "sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2",
"sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d", "sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab",
"sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289", "sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841",
"sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c", "sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30",
"sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded", "sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91",
"sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96", "sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c",
"sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0", "sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257",
"sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904", "sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9",
"sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21", "sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040",
"sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89", "sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911",
"sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78", "sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623",
"sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad", "sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259",
"sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196", "sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c",
"sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd", "sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79",
"sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0", "sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5",
"sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882", "sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4",
"sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757", "sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4",
"sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16", "sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22",
"sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0", "sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd",
"sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47", "sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1",
"sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40", "sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910",
"sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1", "sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859",
"sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3" "sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==7.0.5" "version": "==7.2.3"
}, },
"dill": { "dill": {
"hashes": [ "hashes": [
@@ -336,11 +326,11 @@
}, },
"isort": { "isort": {
"hashes": [ "hashes": [
"sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6", "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504",
"sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b" "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"
], ],
"markers": "python_full_version >= '3.7.0'", "markers": "python_full_version >= '3.8.0'",
"version": "==5.11.4" "version": "==5.12.0"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
"hashes": [ "hashes": [
@@ -394,46 +384,43 @@
}, },
"mypy": { "mypy": {
"hashes": [ "hashes": [
"sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d", "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521",
"sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6", "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140",
"sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf", "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48",
"sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f", "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128",
"sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813", "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336",
"sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33", "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a",
"sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad", "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41",
"sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05", "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f",
"sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297", "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e",
"sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06", "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8",
"sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd", "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238",
"sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243", "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119",
"sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305", "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb",
"sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476", "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d",
"sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711", "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed",
"sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70", "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9",
"sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5", "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e",
"sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461", "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a",
"sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab", "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5",
"sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c", "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950",
"sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d", "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937",
"sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135", "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394",
"sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93", "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6",
"sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648", "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602",
"sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a", "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1",
"sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb", "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba"
"sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3",
"sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372",
"sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb",
"sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.991" "version": "==1.2.0"
}, },
"mypy-extensions": { "mypy-extensions": {
"hashes": [ "hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
], ],
"version": "==0.4.3" "markers": "python_version >= '3.5'",
"version": "==1.0.0"
}, },
"mypy-protobuf": { "mypy-protobuf": {
"hashes": [ "hashes": [
@@ -443,21 +430,36 @@
"index": "pypi", "index": "pypi",
"version": "==3.4.0" "version": "==3.4.0"
}, },
"packaging": { "nuitka": {
"hashes": [ "hashes": [
"sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", "sha256:be385a408202b3afb5c01431bc975935163d644bc70200980f5b80cc4213f0f2"
"sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" ],
"index": "pypi",
"version": "==1.5.6"
},
"ordered-set": {
"hashes": [
"sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562",
"sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==23.0" "version": "==4.1.0"
},
"packaging": {
"hashes": [
"sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
"sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
"version": "==23.1"
}, },
"platformdirs": { "platformdirs": {
"hashes": [ "hashes": [
"sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490", "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08",
"sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2" "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2.6.2" "version": "==3.2.0"
}, },
"pluggy": { "pluggy": {
"hashes": [ "hashes": [
@@ -469,23 +471,22 @@
}, },
"protobuf": { "protobuf": {
"hashes": [ "hashes": [
"sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30", "sha256:13233ee2b9d3bd9a5f216c1fa2c321cd564b93d8f2e4f521a85b585447747997",
"sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b", "sha256:23452f2fdea754a8251d0fc88c0317735ae47217e0d27bf330a30eec2848811a",
"sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc", "sha256:52f0a78141078077cfe15fe333ac3e3a077420b9a3f5d1bf9b5fe9d286b4d881",
"sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791", "sha256:70659847ee57a5262a65954538088a1d72dfc3e9882695cab9f0c54ffe71663b",
"sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717", "sha256:7760730063329d42a9d4c4573b804289b738d4931e363ffbe684716b796bde51",
"sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec", "sha256:7cf56e31907c532e460bb62010a513408e6cdf5b03fb2611e4b67ed398ad046d",
"sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7", "sha256:8b54f56d13ae4a3ec140076c9d937221f887c8f64954673d46f63751209e839a",
"sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab", "sha256:d14fc1a41d1a1909998e8aff7e80d2a7ae14772c4a70e4bf7db8a36690b54425",
"sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2", "sha256:d4b66266965598ff4c291416be429cef7989d8fae88b55b62095a2331511b3fa",
"sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5", "sha256:e0e630d8e6a79f48c557cd1835865b593d0547dce221c66ed1b827de59c66c97",
"sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1", "sha256:ecae944c6c2ce50dda6bf76ef5496196aeb1b85acb95df5843cd812615ec4b61",
"sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462", "sha256:f08aa300b67f1c012100d8eb62d47129e53d1150f4469fd78a29fa3cb68c66f2",
"sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97", "sha256:f2f4710543abec186aee332d6852ef5ae7ce2e9e807a3da570f36de5a732d88e"
"sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.21.12" "version": "==4.22.3"
}, },
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
@@ -505,11 +506,11 @@
}, },
"pylint": { "pylint": {
"hashes": [ "hashes": [
"sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e", "sha256:001cc91366a7df2970941d7e6bbefcbf98694e00102c1f121c531a814ddc2ea8",
"sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5" "sha256:1b647da5249e7c279118f657ca28b6aaebb299f86bf92affc632acf199f7adbb"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.15.10" "version": "==2.17.2"
}, },
"pyproject-hooks": { "pyproject-hooks": {
"hashes": [ "hashes": [
@@ -521,11 +522,11 @@
}, },
"pytest": { "pytest": {
"hashes": [ "hashes": [
"sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5", "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362",
"sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42" "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"
], ],
"index": "pypi", "index": "pypi",
"version": "==7.2.1" "version": "==7.3.1"
}, },
"pytest-cov": { "pytest-cov": {
"hashes": [ "hashes": [
@@ -545,121 +546,189 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b", "sha256:257de92a9d50a60b8e22abfcbb771571fde0dbf3ec234463212027a4eeecbe9a",
"sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8" "sha256:e728ca814a823bf7bf60162daf9db95b93d532948c4c0bea762ce62f60189078"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==66.1.1" "version": "==67.6.1"
}, },
"setuptools-git-versioning": { "setuptools-git-versioning": {
"hashes": [ "hashes": [
"sha256:648481f7e1e9e12ccd2b069d616b909a338b4223956319649351751cbc0207f4", "sha256:0ae47836c30563c30f06d2e1e97fab4455a6b770f7a0496793239eccf4a6dd9f",
"sha256:fde1a7cb3b2566979e5651cfca0d33cd5a82771711cd38a056216391936cf0ff" "sha256:9dfc59a31dcadcae04bcddc50534ccfc07a25a3180ab5cc1b1e3730217971c63"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.13.1" "version": "==1.13.3"
}, },
"tomlkit": { "tomlkit": {
"hashes": [ "hashes": [
"sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b", "sha256:5325463a7da2ef0c6bbfefb62a3dc883aebe679984709aee32a317907d0a8d3c",
"sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73" "sha256:f392ef70ad87a672f02519f99967d28a4d3047133e2d1df936511465fbb3791d"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.7'",
"version": "==0.11.6" "version": "==0.11.7"
}, },
"types-protobuf": { "types-protobuf": {
"hashes": [ "hashes": [
"sha256:6c87c7f8df61d57a53de8221777e4fcc3c7ed24419fbf43b8e9f50887f3773fa", "sha256:031a77403a8952b31869b9ff3883c9a21649dd224ca3673ee4287384e91ea2be",
"sha256:824109e0fe87525a9d2da4cc4eec36ca004f1a0f3d1c0838cfd2873a484cffdd" "sha256:51c23400114461ec96aeca718f1dc3e05d6afb4ef6cc0a94f27af760c576a5bf"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.21.0.3" "version": "==4.22.0.2"
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
"sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
"sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==4.4.0" "version": "==4.5.0"
}, },
"wheel": { "wheel": {
"hashes": [ "hashes": [
"sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac", "sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873",
"sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8" "sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247"
], ],
"index": "pypi", "index": "pypi",
"version": "==0.38.4" "version": "==0.40.0"
}, },
"wrapt": { "wrapt": {
"hashes": [ "hashes": [
"sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0",
"sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420",
"sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a",
"sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c",
"sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079",
"sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923",
"sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f",
"sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1",
"sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8",
"sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86",
"sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0",
"sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364",
"sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e",
"sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c",
"sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e",
"sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c",
"sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727",
"sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff",
"sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e",
"sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29",
"sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7",
"sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72",
"sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475",
"sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a",
"sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317",
"sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2",
"sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd",
"sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640",
"sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98",
"sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248",
"sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e",
"sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d",
"sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec",
"sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1",
"sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e",
"sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9",
"sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92",
"sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb",
"sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094",
"sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46",
"sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29",
"sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd",
"sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705",
"sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8",
"sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975",
"sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb",
"sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e",
"sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b",
"sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418",
"sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019",
"sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1",
"sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba",
"sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6",
"sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2",
"sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3",
"sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7",
"sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752",
"sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416",
"sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f",
"sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1",
"sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc",
"sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145",
"sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee",
"sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a",
"sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7",
"sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b",
"sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653",
"sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0",
"sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90",
"sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29",
"sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6",
"sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034",
"sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09",
"sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559",
"sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"
], ],
"markers": "python_version >= '3.11'", "markers": "python_version >= '3.11'",
"version": "==1.14.1" "version": "==1.15.0"
},
"zstandard": {
"hashes": [
"sha256:0488f2a238b4560828b3a595f3337daac4d3725c2a1637ffe2a0d187c091da59",
"sha256:059316f07e39b7214cd9eed565d26ab239035d2c76835deeff381995f7a27ba8",
"sha256:0aa4d178560d7ee32092ddfd415c2cdc6ab5ddce9554985c75f1a019a0ff4c55",
"sha256:0b815dec62e2d5a1bf7a373388f2616f21a27047b9b999de328bca7462033708",
"sha256:0d213353d58ad37fb5070314b156fb983b4d680ed5f3fce76ab013484cf3cf12",
"sha256:0f32a8f3a697ef87e67c0d0c0673b245babee6682b2c95e46eb30208ffb720bd",
"sha256:29699746fae2760d3963a4ffb603968e77da55150ee0a3326c0569f4e35f319f",
"sha256:2adf65cfce73ce94ef4c482f6cc01f08ddf5e1ca0c1ec95f2b63840f9e4c226c",
"sha256:2eeb9e1ecd48ac1d352608bfe0dc1ed78a397698035a1796cf72f0c9d905d219",
"sha256:302a31400de0280f17c4ce67a73444a7a069f228db64048e4ce555cd0c02fbc4",
"sha256:39ae788dcdc404c07ef7aac9b11925185ea0831b985db0bbc43f95acdbd1c2ce",
"sha256:39cbaf8fe3fa3515d35fb790465db4dc1ff45e58e1e00cbaf8b714e85437f039",
"sha256:40466adfa071f58bfa448d90f9623d6aff67c6d86de6fc60be47a26388f6c74d",
"sha256:489959e2d52f7f1fe8ea275fecde6911d454df465265bf3ec51b3e755e769a5e",
"sha256:4a3c36284c219a4d2694e52b2582fe5d5f0ecaf94a22cf0ea959b527dbd8a2a6",
"sha256:4abf9a9e0841b844736d1ae8ead2b583d2cd212815eab15391b702bde17477a7",
"sha256:4af5d1891eebef430038ea4981957d31b1eb70aca14b906660c3ac1c3e7a8612",
"sha256:5499d65d4a1978dccf0a9c2c0d12415e16d4995ffad7a0bc4f72cc66691cf9f2",
"sha256:5a3578b182c21b8af3c49619eb4cd0b9127fa60791e621b34217d65209722002",
"sha256:613daadd72c71b1488742cafb2c3b381c39d0c9bb8c6cc157aa2d5ea45cc2efc",
"sha256:6179808ebd1ebc42b1e2f221a23c28a22d3bc8f79209ae4a3cc114693c380bff",
"sha256:7041efe3a93d0975d2ad16451720932e8a3d164be8521bfd0873b27ac917b77a",
"sha256:78fb35d07423f25efd0fc90d0d4710ae83cfc86443a32192b0c6cb8475ec79a5",
"sha256:79c3058ccbe1fa37356a73c9d3c0475ec935ab528f5b76d56fc002a5a23407c7",
"sha256:84c1dae0c0a21eea245b5691286fe6470dc797d5e86e0c26b57a3afd1e750b48",
"sha256:862ad0a5c94670f2bd6f64fff671bd2045af5f4ed428a3f2f69fa5e52483f86a",
"sha256:9aca916724d0802d3e70dc68adeff893efece01dffe7252ee3ae0053f1f1990f",
"sha256:9aea3c7bab4276212e5ac63d28e6bd72a79ff058d57e06926dfe30a52451d943",
"sha256:a56036c08645aa6041d435a50103428f0682effdc67f5038de47cea5e4221d6f",
"sha256:a5efe366bf0545a1a5a917787659b445ba16442ae4093f102204f42a9da1ecbc",
"sha256:afbcd2ed0c1145e24dd3df8440a429688a1614b83424bc871371b176bed429f9",
"sha256:b07f391fd85e3d07514c05fb40c5573b398d0063ab2bada6eb09949ec6004772",
"sha256:b0f556c74c6f0f481b61d917e48c341cdfbb80cc3391511345aed4ce6fb52fdc",
"sha256:b671b75ae88139b1dd022fa4aa66ba419abd66f98869af55a342cb9257a1831e",
"sha256:b6d718f1b7cd30adb02c2a46dde0f25a84a9de8865126e0fff7d0162332d6b92",
"sha256:ba4bb4c5a0cac802ff485fa1e57f7763df5efa0ad4ee10c2693ecc5a018d2c1a",
"sha256:ba86f931bf925e9561ccd6cb978acb163e38c425990927feb38be10c894fa937",
"sha256:c1929afea64da48ec59eca9055d7ec7e5955801489ac40ac2a19dde19e7edad9",
"sha256:c28c7441638c472bfb794f424bd560a22c7afce764cd99196e8d70fbc4d14e85",
"sha256:c4efa051799703dc37c072e22af1f0e4c77069a78fb37caf70e26414c738ca1d",
"sha256:cc98c8bcaa07150d3f5d7c4bd264eaa4fdd4a4dfb8fd3f9d62565ae5c4aba227",
"sha256:cd0aa9a043c38901925ae1bba49e1e638f2d9c3cdf1b8000868993c642deb7f2",
"sha256:cdd769da7add8498658d881ce0eeb4c35ea1baac62e24c5a030c50f859f29724",
"sha256:d08459f7f7748398a6cc65eb7f88aa7ef5731097be2ddfba544be4b558acd900",
"sha256:dc47cec184e66953f635254e5381df8a22012a2308168c069230b1a95079ccd0",
"sha256:e3f6887d2bdfb5752d5544860bd6b778e53ebfaf4ab6c3f9d7fd388445429d41",
"sha256:e6b4de1ba2f3028fafa0d82222d1e91b729334c8d65fbf04290c65c09d7457e1",
"sha256:ee2a1510e06dfc7706ea9afad363efe222818a1eafa59abc32d9bbcd8465fba7",
"sha256:f199d58f3fd7dfa0d447bc255ff22571f2e4e5e5748bfd1c41370454723cb053",
"sha256:f1ba6bbd28ad926d130f0af8016f3a2930baa013c2128cfff46ca76432f50669",
"sha256:f847701d77371d90783c0ce6cfdb7ebde4053882c2aaba7255c70ae3c3eb7af0"
],
"markers": "python_version >= '3.6'",
"version": "==0.20.0"
} }
} }
} }

126
README.md
View File

@@ -3,19 +3,20 @@
[![CI tests](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml) [![CI tests](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml)
[![CI docker](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml) [![CI docker](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml)
![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen) ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
<!-- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/protobuf)
[![GitHub Pipenv locked Python version](https://img.shields.io/github/pipenv/locked/python-version/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
![protobuf version](https://img.shields.io/badge/protobuf-4.21.12-informational)-->
![python versions](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue)
[![License](https://img.shields.io/github/license/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/LICENSE) [![License](https://img.shields.io/github/license/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/LICENSE)
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/scito/extract_otp_secrets?sort=semver)](https://github.com/scito/extract_otp_secrets/releases/latest) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/scito/extract_otp_secrets?sort=semver)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![Download executable](https://img.shields.io/badge/download-exe-blue)](https://github.com/scito/extract_otp_secrets/releases/latest) ![python versions](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue)
[![Docker image](https://img.shields.io/badge/docker-image-blue)](https://hub.docker.com/repository/docker/scit0/extract_otp_secrets/general)
[![Linux](https://img.shields.io/badge/os-linux-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest) [![Linux](https://img.shields.io/badge/os-linux-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![Windows](https://img.shields.io/badge/os-windows-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest) [![Windows](https://img.shields.io/badge/os-windows-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![MacOS](https://img.shields.io/badge/os-macos-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest) [![MacOS](https://img.shields.io/badge/os-macos-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![Docker image](https://img.shields.io/badge/docker-image-blue)](https://hub.docker.com/repository/docker/scit0/extract_otp_secrets/general) [![Download executable](https://img.shields.io/badge/download-exe-blue)](https://github.com/scito/extract_otp_secrets/releases/latest)
<!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)
<!-- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/protobuf)
[![GitHub Pipenv locked Python version](https://img.shields.io/github/pipenv/locked/python-version/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
![protobuf version](https://img.shields.io/badge/protobuf-4.22.3-informational)-->
<!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
--- ---
@@ -35,16 +36,17 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
## Table of contents ## Table of contents
- [Download binary executable (🆕 since v2.1)](#download-binary-executable--since-v21) - [Download and run binary executable (🆕 since v2.1)](#download-and-run-binary-executable--since-v21)
- [MacOS](#macos)
- [Usage](#usage) - [Usage](#usage)
- [Capture QR codes from camera (🆕 since version 2.0)](#capture-qr-codes-from-camera--since-version-20) - [Capture QR codes from camera (🆕 since version 2.0)](#capture-qr-codes-from-camera--since-version-20)
- [With builtin QR decoder from image files (🆕 since version 2.0)](#with-builtin-qr-decoder-from-image-files--since-version-20) - [With builtin QR decoder from image files (🆕 since version 2.0)](#with-builtin-qr-decoder-from-image-files--since-version-20)
- [With external QR decoder app from text files](#with-external-qr-decoder-app-from-text-files) - [With external QR decoder app from text files](#with-external-qr-decoder-app-from-text-files)
- [Installation of Python script (recommend for developers or advanced users)](#installation-of-python-script-recommend-for-developers-or-advanced-users) - [Installation of Python script (recommended for developers or advanced users)](#installation-of-python-script-recommended-for-developers-or-advanced-users)
- [Installation of shared system libraries](#installation-of-shared-system-libraries) - [Installation of optional shared system libraries (recommended)](#installation-of-optional-shared-system-libraries-recommended)
- [Program help: arguments and options](#program-help-arguments-and-options) - [Program help: arguments and options](#program-help-arguments-and-options)
- [Examples](#examples) - [Examples](#examples)
- [Printing otp secrets form text file](#printing-otp-secrets-form-text-file) - [Printing otp secrets from text file](#printing-otp-secrets-form-text-file)
- [Printing otp secrets from image file](#printing-otp-secrets-from-image-file) - [Printing otp secrets from image file](#printing-otp-secrets-from-image-file)
- [Writing otp secrets to csv file](#writing-otp-secrets-to-csv-file) - [Writing otp secrets to csv file](#writing-otp-secrets-to-csv-file)
- [Writing otp secrets to json file](#writing-otp-secrets-to-json-file) - [Writing otp secrets to json file](#writing-otp-secrets-to-json-file)
@@ -85,14 +87,46 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
- [Related projects](#related-projects) - [Related projects](#related-projects)
</details> </details>
## Download binary executable (🆕 since v2.1) ## Download and run binary executable (🆕 since v2.1)
1. Download executable for your platform from [latest release](https://github.com/scito/extract_otp_secrets/releases/latest), see assets 1. Download executable for your platform from [latest release](https://github.com/scito/extract_otp_secrets/releases/latest), see assets
2. Start executable by clicking or from command line 2. Linux and macOS: Set executable bit for the downloaded file, e.g in terminal with `chmod +x extract_otp_secrets_X.Y.Z_OS_ARCH`
3. Start executable by clicking or from command line (macOS: startable only from command line, see [below](#macos))
Everything is just packed in one executable. :heavy_check_mark: Everything is just packed in one executable.
No installation needed, neither Python nor dependencies have to be installed. :heavy_check_mark: No installation needed, neither Python nor any dependencies have to be installed.
Easy and convenient :heavy_check_mark: Easy and convenient
:information_source: There is a delay after starting the executable since the files have internally to be unpacked.
:information_source: If you are a developer, you might prefer to run the Python script directly, see [Installation](#installation-of-python-script-recommended-for-developers-or-advanced-users)
> :warning: Some antivirus tools may show a virus or trojan alert for the executable.
> This alert is a false positive.
> This is a known problem for executables generated by PyInstaller.
> If you have any doubt, please use directly the [Python script](#installation-of-python-script-recommended-for-developers-or-advanced-users).
:information_source: The executables are not signed. Thus, the operating system may show a warning about download from unknown source.
### MacOS
> Beginning in macOS 10.15, all software built after June 1, 2019, and distributed with Developer ID must be notarized. However, you arent required to notarize software that you distribute through the Mac App Store because the App Store submission process already includes equivalent security checks. <small>[developer.apple.com](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution)</small>
:x: Unfortunately, I cannot provide a signed and notarized installable application for macOS as .dmg or .pkg. Apple is not Open Source friendly and requires a yearly Developer ID subscription. I am not willing to pay [USD 99 per year](https://developer.apple.com/support/compare-memberships/) to Apple for this little open source tool.
However, the bare executable can be executed from the command line:
1. Download executable for macOS platform from [latest release](https://github.com/scito/extract_otp_secrets/releases/latest), see assets
2. Open `Terminal` application
3. Change to Downloads folder in Terminal: `cd $HOME/Downloads`
4. Set executable bit for the downloaded file: `chmod +x extract_otp_secrets_X.Y.Z_macos_x86_64`
5. Start executable from command line: `./extract_otp_secrets_X.Y.Z_macos_x86_64`
:information_source: Replace `X.Y.Z` in above commands with the version number of your downloaded file, e.g. `extract_otp_secrets_2.4.0_macos_x86_64`
:information_source: If Rosetta2 emulation is installed, these steps work also for M1 and M2 Apple Silicon processors and the program can be executed directly.
> :warning: It seems the GUI mode is not working in Terminal on macOS. In tests no [GUI window](#usage) was opened. (Remarks and hints about macOS are welcome since I do not know macOS.)
## Usage ## Usage
@@ -114,6 +148,14 @@ Detected QR codes are surrounded with a frame. The color of the frame indicates
* Red: The QR code is detected and decoded, but could not be successfully extracted. This is the case if a QR code not containing OTP data is captured. * Red: The QR code is detected and decoded, but could not be successfully extracted. This is the case if a QR code not containing OTP data is captured.
* Magenta: The QR code is detected, but could not be decoded. The QR code should be presented better to the camera or another QR reader could be used. * Magenta: The QR code is detected, but could not be decoded. The QR code should be presented better to the camera or another QR reader could be used.
Key commands:
* Space: change QR code reader
* C: save as csv file (🆕 since v2.2)
* J: save as json file (🆕 since v2.2)
* K: save as KeePass csv file (🆕 since v2.2)
* ESC, ENTER, Q: quit the program
The secrets are printed by default to the console. [Set program parameters](#program-help-arguments-and-options) for other types of output, e.g. `--csv exported_secrets.csv`. The secrets are printed by default to the console. [Set program parameters](#program-help-arguments-and-options) for other types of output, e.g. `--csv exported_secrets.csv`.
### With builtin QR decoder from image files (🆕 since version 2.0) ### With builtin QR decoder from image files (🆕 since version 2.0)
@@ -141,7 +183,7 @@ extract_otp_secrets example_export.txt
``` ```
7. Remove unencrypted files with secrets from your computer and mobile. 7. Remove unencrypted files with secrets from your computer and mobile.
## Installation of Python script (recommend for developers or advanced users) ## Installation of Python script (recommended for developers or advanced users)
```bash ```bash
git clone https://github.com/scito/extract_otp_secrets.git git clone https://github.com/scito/extract_otp_secrets.git
@@ -153,7 +195,7 @@ python src/extract_otp_secrets.py example_export.txt
In case this script is not starting properly, the debug mode can be activated by adding parameter `-d` in the command line. In case this script is not starting properly, the debug mode can be activated by adding parameter `-d` in the command line.
### Installation of shared system libraries ### Installation of optional shared system libraries (recommended)
For reading QR codes with `ZBAR` QR reader, the zbar library must be installed. For reading QR codes with `ZBAR` QR reader, the zbar library must be installed.
If you do not use the `ZBAR` QR reader, you do not need to install the zbar shared library. Note: The `ZBAR` QR reader is the showed for me the best results and is thus default QR Reader. If you do not use the `ZBAR` QR reader, you do not need to install the zbar shared library. Note: The `ZBAR` QR reader is the showed for me the best results and is thus default QR Reader.
@@ -170,7 +212,7 @@ For a detailed installation documentation of [pyzbar](https://github.com/Natural
#### Linux (Fedora) #### Linux (Fedora)
sudo dnf install libzbar0 sudo dnf install zbar
#### Linux (Arch Linux) #### Linux (Arch Linux)
@@ -192,7 +234,7 @@ OpenCV requires [Visual C++ redistributable 2015](https://www.microsoft.com/en-u
## Program help: arguments and options ## Program help: arguments and options
<pre>usage: extract_otp_secrets.py [-h] [--csv FILE] [--keepass FILE] [--json FILE] [--printqr] [--saveqr DIR] [--camera NUMBER] [--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}] [-i] [--no-color] [--version] [-d | -v | -q] [infile ...] <pre>usage: extract_otp_secrets.py [-h] [--csv FILE] [--keepass FILE] [--json FILE] [--txt FILE] [--printqr] [--saveqr DIR] [--camera NUMBER] [--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}] [-i] [--no-color] [--version] [-d | -v | -q] [infile ...]
Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps
If no infiles are provided, a GUI window starts and QR codes are captured from the camera. If no infiles are provided, a GUI window starts and QR codes are captured from the camera.
@@ -206,8 +248,9 @@ options:
--csv FILE, -c FILE export csv file or - for stdout --csv FILE, -c FILE export csv file or - for stdout
--keepass FILE, -k FILE export totp/hotp csv file(s) for KeePass, - for stdout --keepass FILE, -k FILE export totp/hotp csv file(s) for KeePass, - for stdout
--json FILE, -j FILE export json file or - for stdout --json FILE, -j FILE export json file or - for stdout
--printqr, -p print QR code(s) as text to the terminal (requires qrcode module) --txt FILE, -t FILE export txt file or - for stdout
--saveqr DIR, -s DIR save QR code(s) as images to the given folder (requires qrcode module) --printqr, -p print QR code(s) as text to the terminal
--saveqr DIR, -s DIR save QR code(s) as images to directory
--camera NUMBER, -C NUMBER camera number of system (default camera: 0) --camera NUMBER, -C NUMBER camera number of system (default camera: 0)
--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}, -Q {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT} --qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}, -Q {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}
QR reader (default: ZBAR) QR reader (default: ZBAR)
@@ -275,11 +318,15 @@ python extract_otp_secrets.py = < example_export.png</pre>
* Free and open source * Free and open source
* Supports Google Authenticator exports (and compatible apps like Aegis Authenticator) * Supports Google Authenticator exports (and compatible apps like Aegis Authenticator)
* Captures the the QR codes directly from the camera using different QR code libraries (based on OpenCV) (🆕 since v2.0) * Captures the the QR codes directly from the camera using different QR code libraries (based on OpenCV) (🆕 since v2.0)
* ZBAR: [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar) - fast and reliable, good for images and video capture (default and recommended) * ZBAR: [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar) - fast and reliable, good for images and video capture (default and recommended) [if [libzbar](#installation-of-optional-shared-system-libraries-recommended) is installed]
* QREADER: [QReader](https://github.com/Eric-Canas/QReader) * QREADER: [QReader](https://github.com/Eric-Canas/QReader) [if [libzbar](#installation-of-optional-shared-system-libraries-recommended) is installed]
* QREADER_DEEP: [QReader](https://github.com/Eric-Canas/QReader) - very slow in GUI * QREADER_DEEP: [QReader](https://github.com/Eric-Canas/QReader) - very slow in GUI [if [libzbar](#installation-of-optional-shared-system-libraries-recommended) is installed]
* CV2: [QRCodeDetector](https://docs.opencv.org/4.x/de/dc3/classcv_1_1QRCodeDetector.html) * CV2: [QRCodeDetector](https://docs.opencv.org/4.x/de/dc3/classcv_1_1QRCodeDetector.html)
* CV2_WECHAT: [WeChatQRCode](https://docs.opencv.org/4.x/dd/d63/group__wechat__qrcode.html) * CV2_WECHAT: [WeChatQRCode](https://docs.opencv.org/4.x/dd/d63/group__wechat__qrcode.html)
* Program usable as pure GUI application without any command line switches (🆕 since v2.2)
* Save otp secrets as csv file (🆕 since v2.2)
* Save otp secrets as json file (🆕 since v2.2)
* Save otp secrets as KeePass csv file(s) (🆕 since v2.2)
* Supports [TOTP](https://www.ietf.org/rfc/rfc6238.txt) and [HOTP](https://www.ietf.org/rfc/rfc4226.txt) standards * Supports [TOTP](https://www.ietf.org/rfc/rfc6238.txt) and [HOTP](https://www.ietf.org/rfc/rfc4226.txt) standards
* Generates QR codes * Generates QR codes
* Exports to various formats: * Exports to various formats:
@@ -301,8 +348,11 @@ python extract_otp_secrets.py = < example_export.png</pre>
* Prints colored output (🆕 since v2.0) * Prints colored output (🆕 since v2.0)
* Startable as executable (script, Python, and all dependencies packed in one executable) (🆕 since v2.1) * Startable as executable (script, Python, and all dependencies packed in one executable) (🆕 since v2.1)
* extract_otp_secrets_linux_x86_64 (requires glibc >= 2.28) * extract_otp_secrets_linux_x86_64 (requires glibc >= 2.28)
* extract_otp_secrets_linux_arm64 (requires glibc >= 2.28)
* extract_otp_secrets_win_x86_64.exe * extract_otp_secrets_win_x86_64.exe
* extract_otp_secrets_macos_x86_64 (untested) * extract_otp_secrets_macos_x86_64 (optional [libzbar](#installation-of-optional-shared-system-libraries-recommended) needs to be installed manually if needed)
* extract_otp_secrets_macos_x86_64.dmg N/A, see [why](#macos)
* extract_otp_secrets_macos_x86_64.pkg N/A, see [why](#macos)
* Prebuilt Docker images provided for amd64 and arm64 (🆕 since v2.0) * Prebuilt Docker images provided for amd64 and arm64 (🆕 since v2.0)
* Many ways to run the script: * Many ways to run the script:
* Native Python * Native Python
@@ -318,6 +368,7 @@ python extract_otp_secrets.py = < example_export.png</pre>
* Windows * Windows
* Uses UTF-8 on all platforms * Uses UTF-8 on all platforms
* Supports Python >= 3.7 * Supports Python >= 3.7
* Installation of shared system libraries is optional (🆕 since v2.3)
* Provides a debug mode (-d) for analyzing import problems * Provides a debug mode (-d) for analyzing import problems
* Written in modern Python using type hints and following best practices * Written in modern Python using type hints and following best practices
* All these features are backed by tests ran nightly * All these features are backed by tests ran nightly
@@ -577,7 +628,7 @@ pip install -U -r requirements.txt
Build and run the app within the container: Build and run the app within the container:
```bash ```bash
docker build . -t extract_otp_secrets --pull --build-arg RUN_TESTS=false docker build . -t extract_otp_secrets --pull -f docker/Dockerfile --build-arg RUN_TESTS=false
``` ```
Run tests in docker container: Run tests in docker container:
@@ -589,7 +640,7 @@ docker run --entrypoint /extract/run_pytest.sh --rm -v "$(pwd)":/files:ro extrac
#### Alpine (only text file processing) #### Alpine (only text file processing)
```bash ```bash
docker build . -t extract_otp_secrets:only_txt --pull -f Dockerfile_only_txt --build-arg RUN_TESTS=false docker build . -t extract_otp_secrets:only_txt --pull -f docker/Dockerfile_only_txt --build-arg RUN_TESTS=false
``` ```
Run tests in docker container: Run tests in docker container:
@@ -611,7 +662,7 @@ Output is executable `dist/extract_otp_secrets`
#### Windows #### Windows
``` ```
pyinstaller -y --add-data "%pythonLocation%\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "%pythonLocation%\pyzbar\libiconv.dll;pyzbar" --add-binary "%pythonLocation%\pyzbar\libzbar-64.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py pyinstaller -y --add-data "%pythonLocation%\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "%pythonLocation%\pyzbar\libiconv.dll;pyzbar" --add-binary "%pythonLocation%\pyzbar\libzbar-64.dll;pyzbar" --add-binary "%windir%\system32\msvcr120.dll;pyzbar" --add-binary "%windir%\system32\msvcp120.dll;pyzbar" --add-binary "%windir%\system32\vcamp120.dll;pyzbar" --add-binary "%windir%\system32\vcomp120.dll;pyzbar" --add-binary "%windir%\system32\vccorlib120.dll;pyzbar" --add-binary "%windir%\system32\mfc120.dll;pyzbar" --add-binary "%windir%\system32\mfc120u.dll;pyzbar" --add-binary "%windir%\system32\mfc120chs.dll;pyzbar" --add-binary "%windir%\system32\mfc120cht.dll;pyzbar" --add-binary "%windir%\system32\mfc120deu.dll;pyzbar" --add-binary "%windir%\system32\mfc120enu.dll;pyzbar" --add-binary "%windir%\system32\mfc120esn.dll;pyzbar" --add-binary "%windir%\system32\mfc120fra.dll;pyzbar" --add-binary "%windir%\system32\mfc120ita.dll;pyzbar" --add-binary "%windir%\system32\mfc120jpn.dll;pyzbar" --add-binary "%windir%\system32\mfc120kor.dll;pyzbar" --add-binary "%windir%\system32\mfc120rus.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py
``` ```
Output is `dist\extract_otp_secrets.exe` Output is `dist\extract_otp_secrets.exe`
@@ -634,11 +685,18 @@ Build extract_otp_secrets project
Options: Options:
-i Interactive mode, all steps must be confirmed -i Interactive mode, all steps must be confirmed
-C Ignore version check of protobuf/protoc -C Ignore version check of protobuf/protoc
-D Do not build docker -e Build exe
-G Do not start extract_otp_secrets.py in GUI mode -n Build nuitka exe
-L Do not build local (exes)
-d Build docker
-a Build arm
-X Do not build x86_64
-B Do not build base
-V Do not run pipenv
-g Start extract_otp_secrets.py in GUI mode
-c Clean everything -c Clean everything
-r Generate result files -r Generate result files
-h, --help Help -h, --help Show help and quit
``` ```
## Technical background ## Technical background
@@ -650,7 +708,7 @@ Command for regeneration of Python code from proto3 message definition file (onl
protoc --plugin=protoc-gen-mypy=path/to/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python src/google_auth.proto protoc --plugin=protoc-gen-mypy=path/to/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python src/google_auth.proto
The generated protobuf Python code was generated by protoc 21.12 (https://github.com/protocolbuffers/protobuf/releases/tag/v21.12). The generated protobuf Python code was generated by protoc 22.3 (https://github.com/protocolbuffers/protobuf/releases/tag/v22.3).
For Python type hint generation the [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) package is used. For Python type hint generation the [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) package is used.
@@ -694,7 +752,7 @@ FileNotFoundError: Could not find module 'libiconv.dll' (or one of its dependenc
## Related projects ## Related projects
* [ZBar](https://github.com/mchehab/zbar) is an open source software suite for reading bar codes from various sources, including webcams. * [ZBar](https://github.com/mchehab/zbar) is an open source software suite for reading bar codes from various sources, including webcams.
* [Aegis Authenticator](https://github.com/beemdevelopment/Aegis) is a free, secure and open source 2FA app for Android. * [Aegis Authenticator](https://github.com/beemdevelopment/Aegis) is a free, secure and open source 2FA app for Android. This app can scan Google export QR codes and export the secrets, e.g. as JSON. However, a second device is required.
* [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar) is a good QR code reader Python module * [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar) is a good QR code reader Python module
* [OpenCV](https://docs.opencv.org/4.x/) (CV2) Open Source Computer Vision library with [opencv-python](https://github.com/opencv/opencv-python) * [OpenCV](https://docs.opencv.org/4.x/) (CV2) Open Source Computer Vision library with [opencv-python](https://github.com/opencv/opencv-python)
* [Python QReader](https://github.com/Eric-Canas/QReader) Python QR code readers * [Python QReader](https://github.com/Eric-Canas/QReader) Python QR code readers

652
build.sh
View File

@@ -77,9 +77,17 @@ interactive=false
ignore_version_check=true ignore_version_check=true
clean=false clean=false
clean_flag="" clean_flag=""
build_docker=true build_base=true
run_gui=true build_arm=false
build_x86_64=true
build_docker=false
build_local=true
build_exe=false
build_nuitka_exe=false
run_pipenv=true
run_gui=false
generate_result_files=false generate_result_files=false
PYTHONHASHSEED=31
while test $# -gt 0; do while test $# -gt 0; do
case $1 in case $1 in
@@ -91,14 +99,21 @@ while test $# -gt 0; do
echo "Options:" echo "Options:"
echo "-i Interactive mode, all steps must be confirmed" echo "-i Interactive mode, all steps must be confirmed"
echo "-C Ignore version check of protobuf/protoc" echo "-C Ignore version check of protobuf/protoc"
echo "-D Do not build docker" echo "-e Build exe"
echo "-G Do not start extract_otp_secrets.py in GUI mode" echo "-n Build nuitka exe"
echo "-L Do not build local (exe)"
echo "-d Build docker"
echo "-a Build arm"
echo "-X Do not build x86_64"
echo "-B Do not build base"
echo "-V Do not run pipenv"
echo "-g Start extract_otp_secrets.py in GUI mode"
echo "-c Clean everything" echo "-c Clean everything"
echo "-r Generate result files" echo "-r Generate result files"
echo "-h, --help Help" echo "-h, --help Show help and quit"
quit quit
;; ;;
-a) -i)
interactive=true interactive=true
shift shift
;; ;;
@@ -106,12 +121,40 @@ while test $# -gt 0; do
ignore_version_check=false ignore_version_check=false
shift shift
;; ;;
-D) -B)
build_docker=false build_base=false
shift shift
;; ;;
-G) -L)
run_gui=false build_local=false
shift
;;
-a)
build_arm=true
shift
;;
-X)
build_x86_64=false
shift
;;
-e)
build_exe=true
shift
;;
-n)
build_nuitka_exe=true
shift
;;
-d)
build_docker=true
shift
;;
-V)
run_pipenv=false
shift
;;
-g)
run_gui=true
shift shift
;; ;;
-r) -r)
@@ -173,296 +216,411 @@ if $clean; then
cmd="sudo pipenv --rm || true" cmd="sudo pipenv --rm || true"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
fi
cmd="$PIP install --use-pep517 -U -r requirements-dev.txt" cmd="mkdir -p dist"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
echo -e "\n\nChecking Protoc version..."
VERSION=$(curl -sL https://github.com/protocolbuffers/protobuf/releases/latest | grep -E "<title>" | perl -pe's%.*Protocol Buffers v(\d+\.\d+(\.\d+)?).*%\1%')
BASEVERSION=4
echo
OLDVERSION=$(cat $BIN/$DEST/.VERSION.txt || echo "")
echo -e "\nProtoc remote version $VERSION\n"
echo -e "Protoc local version: $OLDVERSION\n"
if [ "$OLDVERSION" != "$VERSION" ] || ! $ignore_version_check; then
echo "Upgrade protoc from $OLDVERSION to $VERSION"
NAME="protoc-$VERSION"
ARCHIVE="$NAME.zip"
mkdir -p $DOWNLOADS
# https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-linux-x86_64.zip
cmd="wget --trust-server-names https://github.com/protocolbuffers/protobuf/releases/download/v$VERSION/protoc-$VERSION-linux-x86_64.zip -O $DOWNLOADS/$ARCHIVE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="echo -e '\nSize [Byte]'; stat --printf='%s\n' $DOWNLOADS/$ARCHIVE; echo -e '\nMD5'; md5sum $DOWNLOADS/$ARCHIVE; echo -e '\nSHA256'; sha256sum $DOWNLOADS/$ARCHIVE;"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="mkdir -p $BIN/$NAME; unzip $DOWNLOADS/$ARCHIVE -d $BIN/$NAME"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="echo $VERSION > $BIN/$NAME/.VERSION.txt; echo $VERSION > $BIN/$NAME/.VERSION_$VERSION.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="[ -d $BIN/$DEST.old ] && rm -rf $BIN/$DEST.old || echo 'No old dir to delete'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="[ -d $BIN/$DEST ] && mv -iT $BIN/$DEST $BIN/$DEST.old || echo 'No previous dir to keep'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="mv -iT $BIN/$NAME $BIN/$DEST"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="rm $DOWNLOADS/$ARCHIVE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$BIN/$DEST/bin/protoc --plugin=protoc-gen-mypy=$HOME/.local/bin/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python --proto_path=src google_auth.proto"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Update README.md
cmd="perl -i -pe 's%proto(buf|c)([- ])(\d\.)?$OLDVERSION%proto\$1\$2\${3}$VERSION%g' README.md && perl -i -pe 's%(protobuf/releases/tag/v)$OLDVERSION%\${1}$VERSION%g' README.md"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
else
echo -e "\nVersion has not changed. Quit"
fi
# Upgrade pip requirements
cmd="pip install -U pip"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
$PIP --version
cmd="$PIP install --use-pep517 -U -r requirements.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Lint
LINT_OUT_FILE="tests/reports/flake8_results.txt"
cmd="$FLAKE8 . --count --select=E9,F63,F7,F82 --show-source --statistics | tee $LINT_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$FLAKE8 . --count --exit-zero --max-complexity=10 --max-line-length=200 --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,protobuf_generated_python | tee -a $LINT_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Type checking
TYPE_CHECK_OUT_FILE="tests/reports/mypy_results.txt"
cmd="$MYPY --install-types --non-interactive src/*.py tests/*.py"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# change to src as python -m mypy adds the current dir Python sys.path
# execute in a subshell in order not to loose the exit code and not to change the dir in the currrent shell
cmd="$MYPY --strict src/*.py tests/*.py | tee $TYPE_CHECK_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Generate results files
if $generate_result_files; then
cmd="for color in '' '-n'; do for level in '' '-v' '-vv' '-vvv'; do $PYTHON src/extract_otp_secrets.py example_export.txt \$color \$level > tests/data/print_verbose_output\$color\$level.txt; done; done"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
fi fi
# pip -e install if $build_local; then
cmd="$PIP install --use-pep517 -U -r requirements-dev.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PIP install -U -e ." echo -e "\n\nChecking Protoc version..."
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi VERSION=$(curl -sL https://github.com/protocolbuffers/protobuf/releases/latest | grep -E "<title>" | perl -pe's%.*Protocol Buffers v(\d+\.\d+(\.\d+)?).*%\1%')
eval "$cmd" BASEVERSION=4
echo
cmd="extract_otp_secrets example_export.txt" OLDVERSION=$(cat $BIN/$DEST/.VERSION.txt || echo "")
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi echo -e "\nProtoc remote version $VERSION\n"
eval "$cmd" echo -e "Protoc local version: $OLDVERSION\n"
cmd="extract_otp_secrets - < example_export.txt" if [ "$OLDVERSION" != "$VERSION" ] || ! $ignore_version_check; then
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi echo "Upgrade protoc from $OLDVERSION to $VERSION"
eval "$cmd"
# Test (needs module) NAME="protoc-$VERSION"
ARCHIVE="$NAME.zip"
cmd="$PYTHON src/extract_otp_secrets.py example_export.txt" mkdir -p $DOWNLOADS
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi # https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-linux-x86_64.zip
eval "$cmd" cmd="wget --trust-server-names https://github.com/protocolbuffers/protobuf/releases/download/v$VERSION/protoc-$VERSION-linux-x86_64.zip -O $DOWNLOADS/$ARCHIVE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PYTHON src/extract_otp_secrets.py - < example_export.txt" cmd="echo -e '\nSize [Byte]'; stat --printf='%s\n' $DOWNLOADS/$ARCHIVE; echo -e '\nMD5'; md5sum $DOWNLOADS/$ARCHIVE; echo -e '\nSHA256'; sha256sum $DOWNLOADS/$ARCHIVE;"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
COVERAGE_OUT_FILE="tests/reports/pytest-coverage.txt" cmd="mkdir -p $BIN/$NAME; unzip $DOWNLOADS/$ARCHIVE -d $BIN/$NAME"
cmd="pytest --cov=extract_otp_secrets_test --junitxml=tests/reports/pytest.xml --cov-report html:tests/reports/html --cov-report=term-missing tests/ | tee $COVERAGE_OUT_FILE" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd"
eval "$cmd"
# Pipenv cmd="echo $VERSION > $BIN/$NAME/.VERSION.txt; echo $VERSION > $BIN/$NAME/.VERSION_$VERSION.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PIP install -U pipenv" cmd="[ -d $BIN/$DEST.old ] && rm -rf $BIN/$DEST.old || echo 'No old dir to delete'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
$PIPENV --version cmd="[ -d $BIN/$DEST ] && mv -iT $BIN/$DEST $BIN/$DEST.old || echo 'No previous dir to keep'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PIPENV update && $PIPENV --rm && $PIPENV install" cmd="mv -iT $BIN/$NAME $BIN/$DEST"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
$PIPENV run python --version cmd="rm $DOWNLOADS/$ARCHIVE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PIPENV run pytest --cov=extract_otp_secrets_test tests/" cmd="$BIN/$DEST/bin/protoc --plugin=protoc-gen-mypy=$HOME/.local/bin/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python --proto_path=src google_auth.proto"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
# Build wheel # Update README.md
cmd="$PIP wheel ." cmd="perl -i -pe 's%proto(buf|c)([- ])(\d\.)?$OLDVERSION%proto\$1\$2\${3}$VERSION%g' README.md && perl -i -pe 's%(protobuf/releases/tag/v)$OLDVERSION%\${1}$VERSION%g' README.md"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
else
echo -e "\nVersion has not changed. Quit"
fi
# Build executable # Upgrade pip requirements
cmd="LOCAL_GLIBC_VERSION=$(ldd --version | sed '1!d' | sed -E 's/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/')" cmd="pip install -U pip"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
echo "local glibc: $LOCAL_GLIBC_VERSION"
cmd="pyinstaller -y --add-data $HOME/.local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile $clean_flag src/extract_otp_secrets.py" $PIP --version
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets -h" cmd="$PIP install --use-pep517 -U -r requirements.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
# Generate README.md TOC if $build_base; then
# Lint
cmd="gfm-toc -s 2 -e 3 -t -o README.md > docs/README_TOC.md" LINT_OUT_FILE="tests/reports/flake8_results.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi cmd="$FLAKE8 . --count --select=E9,F63,F7,F82 --show-source --statistics | tee $LINT_OUT_FILE"
eval "$cmd" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Update Code Coverage in README.md cmd="$FLAKE8 . --count --exit-zero --max-complexity=10 --max-line-length=200 --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,protobuf_generated_python | tee -a $LINT_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# https://github.com/marketplace/actions/pytest-coverage-comment # Type checking
# Coverage-95%25-yellowgreen
echo -e "Update code coverage in README.md" TYPE_CHECK_OUT_FILE="tests/reports/mypy_results.txt"
TOTAL_COVERAGE=$(cat $COVERAGE_OUT_FILE | grep 'TOTAL' | perl -ne 'print "$&" if /\b(\d{1,3})%/') && perl -i -pe "s/coverage-(\d{1,3}%)25-/coverage-${TOTAL_COVERAGE}25-/" README.md cmd="$MYPY --install-types --non-interactive src/*.py tests/*.py"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# change to src as python -m mypy adds the current dir Python sys.path
# execute in a subshell in order not to loose the exit code and not to change the dir in the currrent shell
cmd="$MYPY --strict src/*.py tests/*.py | tee $TYPE_CHECK_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Generate results files
if $generate_result_files; then
cmd="for color in '' '-n'; do for level in '' '-v' '-vv' '-vvv'; do $PYTHON src/extract_otp_secrets.py example_export.txt \$color \$level > tests/data/print_verbose_output\$color\$level.txt; done; done"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# pip -e install
cmd="$PIP install -U -e ."
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="extract_otp_secrets example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="extract_otp_secrets - < example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Test (needs module)
cmd="$PYTHON src/extract_otp_secrets.py example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PYTHON src/extract_otp_secrets.py - < example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
COVERAGE_OUT_FILE="tests/reports/pytest-coverage.txt"
cmd="pytest --cov=extract_otp_secrets_test --junitxml=tests/reports/pytest.xml --cov-report html:tests/reports/html --cov-report=term-missing tests/ | tee $COVERAGE_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Pipenv
if $run_pipenv; then
cmd="$PIP install -U pipenv"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
$PIPENV --version
cmd="$PIPENV --rm && $PIPENV update && $PIPENV install"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
$PIPENV run python --version
cmd="$PIPENV run pytest --cov=extract_otp_secrets_test tests/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Build wheel
cmd="$PIP wheel ."
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Build executable
if $build_exe; then
cmd="LOCAL_GLIBC_VERSION=$(ldd --version | sed '1!d' | sed -E 's/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/')"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
echo "local glibc: $LOCAL_GLIBC_VERSION"
cmd="pyinstaller -y --specpath installer --add-data $HOME/.local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile $clean_flag src/extract_otp_secrets.py"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Build compiled executable
if $build_nuitka_exe; then
cmd="LOCAL_GLIBC_VERSION=$(ldd --version | sed '1!d' | sed -E 's/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/')"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
echo "local glibc: $LOCAL_GLIBC_VERSION"
cmd="$PIP install -U pyqt5"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PYTHON -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=$HOME/.local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=build/nuitka --output-filename=extract_otp_secrets_compiled src/extract_otp_secrets.py"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="cp build/nuitka/extract_otp_secrets_compiled dist/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets_compiled -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Generate README.md TOC
cmd="gfm-toc -s 2 -e 3 -t -o README.md > docs/README_TOC.md"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Update Code Coverage in README.md
# https://github.com/marketplace/actions/pytest-coverage-comment
# Coverage-95%25-yellowgreen
echo -e "Update code coverage in README.md"
TOTAL_COVERAGE=$(cat $COVERAGE_OUT_FILE | grep 'TOTAL' | perl -ne 'print "$&" if /\b(\d{1,3})%/') && perl -i -pe "s/coverage-(\d{1,3}%)25-/coverage-${TOTAL_COVERAGE}25-/" README.md
# create Windows win_file_version_info.txt
cmd="VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") VERSION_BUILD=$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) COPYRIGHT_YEARS='2020-2023' envsubst < installer/win_file_version_info_template.txt > build/win_file_version_info.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# create macOS extract_otp_secrets_macos.spec from extract_otp_secrets_macos_template.spec
cmd="VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2023' envsubst < installer/extract_otp_secrets_macos_template.spec > build/extract_otp_secrets_macos.spec"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
if $build_docker; then if $build_docker; then
# Build docker # Build docker
# Build Dockerfile_only_txt (Alpine) if $build_x86_64; then
cmd="docker build . -t extract_otp_secrets_only_txt -t extract_otp_secrets:only-txt -t extract_otp_secrets:alpine -f Dockerfile_only_txt --pull --build-arg RUN_TESTS=false" # Build Dockerfile_only_txt (Alpine)
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi cmd="docker build . -t extract_otp_secrets_only_txt -t extract_otp_secrets:only-txt -t extract_otp_secrets:alpine -f docker/Dockerfile_only_txt --pull --build-arg RUN_TESTS=false"
eval "$cmd" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker run --rm -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt example_export.txt" cmd="docker run --rm -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt - < example_export.txt" cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt - < example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt extract_otp_secrets_test.py -k 'not qreader' -vvv --relaxed" cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt extract_otp_secrets_test.py -k 'not qreader' -vvv --relaxed"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
# Build extract_otp_secrets (Debian Bullseye) # Build extract_otp_secrets (Debian Bullseye)
cmd="docker build . -t extract_otp_secrets -t extract_otp_secrets:bullseye --pull --build-arg RUN_TESTS=false" cmd="docker build . -t extract_otp_secrets -t extract_otp_secrets:bullseye --pull -f docker/Dockerfile --build-arg RUN_TESTS=false"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --rm -v \"$(pwd)\":/files:ro extract_otp_secrets example_export.txt" cmd="docker run --rm -v \"$(pwd)\":/files:ro extract_otp_secrets example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="cat example_export.txt | docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets - -c - > example_output.csv" cmd="cat example_export.txt | docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets - -c - > example_output.csv"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets = < example_export.png" cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets = < example_export.png"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets" cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
# Build extract_otp_secrets (Debian Buster) # Build extract_otp_secrets (Debian Buster)
cmd="docker build . -t extract_otp_secrets:buster --pull --build-arg RUN_TESTS=false --build-arg BASE_IMAGE=python:3.11-slim-buster" cmd="docker build . -t extract_otp_secrets:buster -t extract_otp_secrets:buster-x86_64 --pull -f docker/Dockerfile --build-arg RUN_TESTS=false --build-arg BASE_IMAGE=python:3.11-slim-buster"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --rm -v \"$(pwd)\":/files:ro extract_otp_secrets:buster example_export.txt" cmd="docker run --rm -v \"$(pwd)\":/files:ro extract_otp_secrets:buster example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="cat example_export.txt | docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets:buster - -c - > example_output.csv" cmd="cat example_export.txt | docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets:buster - -c - > example_output.csv"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets:buster = < example_export.png" cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets:buster = < example_export.png"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets:buster" cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets:buster"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
# Build executable from Docker latest # Build executable from Docker latest
# sed "1!d" is workaround for head -n 1 since it head procduces exit code != 0 # sed "1!d" is workaround for head -n 1 since it head procduces exit code != 0
BULLSEYE_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"') BULLSEYE_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
echo "Bullseye glibc: $BULLSEYE_GLIBC_VERSION" echo "Bullseye glibc: $BULLSEYE_GLIBC_VERSION"
fi
cmd="docker run --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils && pip install -U -r /files/requirements.txt && pip install pyinstaller && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64_bullseye --distpath /files/dist/ /files/src/extract_otp_secrets.py'" if $build_arm; then
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi # build linux/arm64
eval "$cmd" cmd="docker run --pull always --rm --privileged multiarch/qemu-user-static --reset -p yes"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets_linux_x86_64_bullseye -h" # Build extract_otp_secrets (Debian Buster)
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi cmd="docker buildx build --platform=linux/arm64 . -t extract_otp_secrets:buster-arm64 --pull -f docker/Dockerfile --build-arg RUN_TESTS=false --build-arg BASE_IMAGE=python:3.11-slim-buster"
eval "$cmd" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Build executable from Docker buster if $build_exe; then
BUSTER_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets:buster -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"') if $build_x86_64; then
echo "Bullseye glibc: $BUSTER_GLIBC_VERSION" cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64_bullseye --distpath /files/dist/ /files/src/extract_otp_secrets.py'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker run --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U -r /files/requirements.txt && pip install pyinstaller && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'" cmd="dist/extract_otp_secrets_linux_x86_64_bullseye -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="dist/extract_otp_secrets_linux_x86_64 -h" # Build executable from Docker buster
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi BUSTER_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets:buster -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
eval "$cmd" echo "Bullseye glibc: $BUSTER_GLIBC_VERSION"
# create Windows file_version_info.txt cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'"
cmd="VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") VERSION_BUILD=$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) YEARS='2020-2023' envsubst < file_version_info_template.txt > build/file_version_info.txt" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd"
eval "$cmd"
cmd="dist/extract_otp_secrets_linux_x86_64 -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
if $build_arm; then
# build linux/arm64
cmd="docker run --platform linux/arm64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster-arm64 -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller --specpath installer -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_arm64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="PLATFORM='linux/arm64' && EXE='dist/extract_otp_secrets_linux_arm64' && docker run --platform \"\$PLATFORM\" --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster-arm64 -c \"\$EXE -V && \$EXE -h && \$EXE example_export.png && \$EXE - < example_export.txt && \$EXE --qr ZBAR example_export.png && \$EXE --qr QREADER example_export.png && \$EXE --qr QREADER_DEEP example_export.png && \$EXE --qr CV2 example_export.png && \$EXE --qr CV2_WECHAT example_export.png\""
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
fi
if $build_nuitka_exe; then
if $build_x86_64; then
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils build-essential patchelf && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_x86_64_bullseye_compiled /files/src/extract_otp_secrets.py'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="build/docker/nuitka/extract_otp_secrets_linux_x86_64_bullseye_compiled -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="cp build/docker/nuitka/extract_otp_secrets_linux_x86_64_bullseye_compiled dist/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Build executable from Docker buster
BUSTER_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets:buster -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
echo "Bullseye glibc: $BUSTER_GLIBC_VERSION"
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils build-essential patchelf && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_x86_64_buster_compiled /files/src/extract_otp_secrets.py'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="build/docker/nuitka/extract_otp_secrets_linux_x86_64_buster_compiled -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="cp build/docker/nuitka/extract_otp_secrets_linux_x86_64_buster_compiled dist/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
if $build_arm; then
# build linux/arm64
cmd="docker run --platform linux/arm64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster-arm64 -c 'apt-get update && apt-get -y install binutils build-essential patchelf qt5-default && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_arm64_compiled /files/src/extract_otp_secrets.py'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="PLATFORM='linux/arm64' && EXE='build/docker/nuitka/extract_otp_secrets_linux_arm64_compiled' && docker run --platform \"\$PLATFORM\" --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster-arm64 -c \"\$EXE -V && \$EXE -h && \$EXE example_export.png && \$EXE - < example_export.txt && \$EXE --qr ZBAR example_export.png && \$EXE --qr QREADER example_export.png && \$EXE --qr QREADER_DEEP example_export.png && \$EXE --qr CV2 example_export.png && \$EXE --qr CV2_WECHAT example_export.png\""
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="cp build/docker/nuitka/extract_otp_secrets_linux_arm64_compiled dist/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
fi
# Run GUI from Docker # Run GUI from Docker
if $run_gui; then if $build_x86_64 && $run_gui; then
cmd="docker run --rm -v "$(pwd)":/files:ro --device=\"/dev/video0:/dev/video0\" --env=\"DISPLAY\" -v /tmp/.X11-unix:/tmp/.X11-unix:ro extract_otp_secrets &" cmd="docker run --rm -v "$(pwd)":/files:ro --device=\"/dev/video0:/dev/video0\" --env=\"DISPLAY\" -v /tmp/.X11-unix:/tmp/.X11-unix:ro extract_otp_secrets &"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
@@ -485,4 +643,6 @@ eval "$cmd"
line=$(printf '#%.0s' $(eval echo {1..$(( ($COLUMNS - 10) / 2))})) line=$(printf '#%.0s' $(eval echo {1..$(( ($COLUMNS - 10) / 2))}))
echo -e "\n${greenBold}$line SUCCESS $line${reset}" echo -e "\n${greenBold}$line SUCCESS $line${reset}"
git status
quit quit

View File

@@ -16,13 +16,14 @@ COPY requirements*.txt src/ run_pytest.sh pytest.ini tests/ example_*.txt exampl
ARG RUN_TESTS=true ARG RUN_TESTS=true
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y --no-install-recommends \
libgl1 \ libgl1 \
libglib2.0-0 \ libglib2.0-0 \
libsm6 \ libsm6 \
libzbar0 \ libzbar0 \
python3-tk \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U -r requirements.txt \ && pip install --no-cache-dir -U pip -r requirements.txt \
&& if [ "$RUN_TESTS" = "true" ]; then /extract/run_pytest.sh; else echo "Not running tests..."; fi \ && if [ "$RUN_TESTS" = "true" ]; then /extract/run_pytest.sh; else echo "Not running tests..."; fi \
&& echo 'test -s /extract/.alias && . /extract/.alias || true' >> ~/.bashrc && echo 'test -s /extract/.alias && . /extract/.alias || true' >> ~/.bashrc

View File

@@ -30,6 +30,7 @@ RUN apk add --no-cache \
zlib-dev \ zlib-dev \
; fi \ ; fi \
&& pip install --no-cache-dir -U \ && pip install --no-cache-dir -U \
pip \
colorama \ colorama \
Pillow \ Pillow \
protobuf \ protobuf \

View File

@@ -3,13 +3,14 @@ Generate from file: README.md
## Table of contents ## Table of contents
- [Table of contents](#table-of-contents) - [Table of contents](#table-of-contents)
- [Download binary executable (🆕 since v2.1)](#download-binary-executable--since-v21) - [Download and run binary executable (🆕 since v2.1)](#download-and-run-binary-executable--since-v21)
- [MacOS](#macos)
- [Usage](#usage) - [Usage](#usage)
- [Capture QR codes from camera (🆕 since version 2.0)](#capture-qr-codes-from-camera--since-version-20) - [Capture QR codes from camera (🆕 since version 2.0)](#capture-qr-codes-from-camera--since-version-20)
- [With builtin QR decoder from image files (🆕 since version 2.0)](#with-builtin-qr-decoder-from-image-files--since-version-20) - [With builtin QR decoder from image files (🆕 since version 2.0)](#with-builtin-qr-decoder-from-image-files--since-version-20)
- [With external QR decoder app from text files](#with-external-qr-decoder-app-from-text-files) - [With external QR decoder app from text files](#with-external-qr-decoder-app-from-text-files)
- [Installation of Python script (recommend for developers or advanced users)](#installation-of-python-script-recommend-for-developers-or-advanced-users) - [Installation of Python script (recommended for developers or advanced users)](#installation-of-python-script-recommended-for-developers-or-advanced-users)
- [Installation of shared system libraries](#installation-of-shared-system-libraries) - [Installation of optional shared system libraries (recommended)](#installation-of-optional-shared-system-libraries-recommended)
- [Program help: arguments and options](#program-help-arguments-and-options) - [Program help: arguments and options](#program-help-arguments-and-options)
- [Examples](#examples) - [Examples](#examples)
- [Printing otp secrets form text file](#printing-otp-secrets-form-text-file) - [Printing otp secrets form text file](#printing-otp-secrets-form-text-file)

3
docs/meta.md Normal file
View File

@@ -0,0 +1,3 @@
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=scito/extract_otp_secrets&type=Date)](https://star-history.com/#scito/extract_otp_secrets&Date)

22
installer/build_dmg.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
# Create a folder (named dmg) to prepare our DMG in (if it doesn't already exist).
# https://www.pythonguis.com/tutorials/packaging-pyqt5-applications-pyinstaller-macos-dmg/
mkdir -p dist/dmg
# Empty the dmg folder.
rm -r dist/dmg/*
# Copy the app bundle to the dmg folder.
cp -r "dist/extract_otp_secrets.app" dist/dmg
# If the DMG already exists, delete it.
test -f "dist/extract_otp_secrets.dmg" && rm "dist/extract_otp_secrets.dmg"
create-dmg \
--volname "Extract OTP Secrets" \
--window-pos 200 120 \
--window-size 600 300 \
--icon-size 100 \
--hide-extension "extract_otp_secrets.app" \
--app-drop-link 425 120 \
"dist/extract_otp_secrets.dmg" \
"dist/dmg/"

View File

@@ -0,0 +1,111 @@
# -*- mode: python ; coding: utf-8 -*-
# https://www.pythonguis.com/tutorials/packaging-pyqt5-applications-pyinstaller-macos-dmg/
# https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html
block_cipher = None
a = Analysis(
['src/extract_otp_secrets.py'],
pathex=[],
binaries=[],
datas=[('$macos_python_path/__yolo_v3_qr_detector/', '__yolo_v3_qr_detector/')],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='extract_otp_secrets',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=True,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
app = BUNDLE(
exe,
name='extract_otp_secrets.app',
icon=None,
bundle_identifier='ch.scito.tools.extract_otp_secrets',
version='$VERSION_STR',
info_plist={
'NSPrincipalClass': 'NSApplication',
'NSAppleScriptEnabled': False,
'NSHumanReadableCopyright': 'Copyright © $COPYRIGHT_YEARS Scito.',
'CFBundleDocumentTypes': [
# Reference: https://chromium.googlesource.com/chromium/src/+/lkgr/chrome/app/app-Info.plist
# https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html#//apple_ref/doc/uid/TP40001319-CH202-SW6
# https://developer.apple.com/documentation/uniformtypeidentifiers/system-declared_uniform_type_identifiers
{
'CFBundleTypeName': 'GIF image',
'CFBundleTypeIconFile': 'document.icns',
'LSItemContentTypes': ['com.compuserve.gif'],
'LSHandlerRank': 'Alternate',
'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'],
},
{
'CFBundleTypeName': 'JPEG image',
'CFBundleTypeIconFile': 'document.icns',
'LSItemContentTypes': ['public.jpeg'],
'LSHandlerRank': 'Alternate',
'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'],
},
{
'CFBundleTypeName': 'PNG image',
'CFBundleTypeIconFile': 'document.icns',
'LSItemContentTypes': ['public.png'],
'LSHandlerRank': 'Alternate',
'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'],
},
{
'CFBundleTypeName': 'WebP image',
'CFBundleTypeIconFile': 'document.icns',
'LSItemContentTypes': ['org.webmproject.webp'],
'LSHandlerRank': 'Alternate',
'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'],
},
{
'CFBundleTypeName': 'Tiff image',
'CFBundleTypeIconFile': 'document.icns',
'LSItemContentTypes': ['public.tiff'],
'LSHandlerRank': 'Alternate',
'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'],
},
{
'CFBundleTypeName': 'Bmp image',
'CFBundleTypeIconFile': 'document.icns',
'LSItemContentTypes': ['com.microsoft.bmp'],
'LSHandlerRank': 'Alternate',
'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'],
},
{
'CFBundleTypeName': 'Plain text document',
'CFBundleTypeIconFile': 'document.icns',
'LSItemContentTypes': ['public.plain-text'],
'LSHandlerRank': 'Alternate',
'NSExportableTypes': ['public.json','public.comma-separated-values-text','public.plain-text'],
},
],
},
)

View File

@@ -1,8 +1,11 @@
[build-system] [build-system]
requires = [ requires = [
"setuptools>=64.0.0", "wheel>=0.37.0", "pip", "pip",
"nuitka",
# https://setuptools-git-versioning.readthedocs.io/en/latest/differences.html # https://setuptools-git-versioning.readthedocs.io/en/latest/differences.html
"setuptools>=64.0.0",
"setuptools-git-versioning", "setuptools-git-versioning",
"wheel>=0.37.0",
] ]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"

View File

@@ -3,6 +3,7 @@ flake8
gfm-toc gfm-toc
mypy mypy
mypy-protobuf mypy-protobuf
nuitka
pyinstaller pyinstaller
pylint pylint
pytest pytest

View File

@@ -1,4 +1,5 @@
colorama>=0.4.6 colorama>=0.4.6
importlib_metadata; python_version<='3.7'
opencv-contrib-python; sys_platform != 'darwin' opencv-contrib-python; sys_platform != 'darwin'
opencv-contrib-python<=4.7.0; sys_platform == 'darwin' opencv-contrib-python<=4.7.0; sys_platform == 'darwin'
Pillow Pillow
@@ -7,4 +8,3 @@ pyzbar
qrcode qrcode
qreader<2.0.0 qreader<2.0.0
typing_extensions; python_version<='3.7' typing_extensions; python_version<='3.7'
importlib_metadata; python_version<='3.7'

View File

@@ -43,7 +43,7 @@ import re
import sys import sys
import urllib.parse as urlparse import urllib.parse as urlparse
from enum import Enum, IntEnum from enum import Enum, IntEnum
from typing import Any, List, Optional, Sequence, TextIO, Tuple, Union from typing import Any, List, Optional, Sequence, TextIO, Tuple, Union, TYPE_CHECKING
import colorama import colorama
from pkg_resources import DistributionNotFound, get_distribution from pkg_resources import DistributionNotFound, get_distribution
@@ -65,21 +65,34 @@ else:
debug_mode = '-d' in sys.argv[1:] or '--debug' in sys.argv[1:] debug_mode = '-d' in sys.argv[1:] or '--debug' in sys.argv[1:]
quiet = '-q' in sys.argv[1:] or '--quiet' in sys.argv[1:]
headless: bool = False
try: try:
import cv2 # type: ignore # TODO use cv2 types if available import cv2 # type: ignore # TODO use cv2 types if available
import numpy as np # TODO use numpy types if available import numpy as np # TODO use numpy types if available
try:
import tkinter
import tkinter.filedialog
import tkinter.messagebox
except ImportError:
headless = True
try: try:
import pyzbar.pyzbar as zbar # type: ignore import pyzbar.pyzbar as zbar # type: ignore
from qreader import QReader # type: ignore from qreader import QReader # type: ignore
except ImportError as e: zbar_available = True
print(f""" except Exception as e:
ERROR: Cannot import QReader module. This problem is probably due to the missing zbar shared library. if not quiet:
On Linux and macOS libzbar0 must be installed. print(f"""
See in README.md for the installation of the libzbar0. WARN: Cannot import pyzbar module. This problem is probably due to the missing zbar shared library. (The zbar library is optional.)
See in README.md for the installation of the zbar shared library.
Exception: {e}\n""", file=sys.stderr) Exception: {e}\n""", file=sys.stderr)
raise e zbar_available = False
if debug_mode:
raise e
# Types # Types
# workaround for PYTHON <= 3.9: Final[tuple[int]] # workaround for PYTHON <= 3.9: Final[tuple[int]]
@@ -112,9 +125,9 @@ Exception: {e}\n""", file=sys.stderr)
TextPosition = Enum('TextPosition', ['LEFT', 'RIGHT']) TextPosition = Enum('TextPosition', ['LEFT', 'RIGHT'])
qreader_available = True cv2_available = True
except ImportError as e: except ImportError as e:
qreader_available = False cv2_available = False
if debug_mode: if debug_mode:
raise e raise e
@@ -136,13 +149,14 @@ LogLevel = IntEnum('LogLevel', ['QUIET', 'NORMAL', 'VERBOSE', 'MORE_VERBOSE', 'D
# Constants # Constants
CAMERA: Final[str] = 'camera' CAMERA: Final[str] = 'camera'
CV2_QRMODES: List[str] = [QRMode.CV2.name, QRMode.CV2_WECHAT.name]
# Global variable declaration # Global variable declaration
verbose: IntEnum = LogLevel.NORMAL verbose: IntEnum = LogLevel.NORMAL
quiet: bool = False
colored: bool = True colored: bool = True
executable: bool = False executable: bool = False
__version__: str __version__: str
tk_root: tkinter.Tk
def sys_main() -> None: def sys_main() -> None:
@@ -150,7 +164,7 @@ def sys_main() -> None:
def main(sys_args: list[str]) -> None: def main(sys_args: list[str]) -> None:
global executable global executable, tk_root, headless
# allow to use sys.stdout with with (avoid closing) # allow to use sys.stdout with with (avoid closing)
sys.stdout.close = lambda: None # type: ignore sys.stdout.close = lambda: None # type: ignore
# set encoding to utf-8, needed for Windows # set encoding to utf-8, needed for Windows
@@ -164,6 +178,13 @@ def main(sys_args: list[str]) -> None:
# https://pyinstaller.org/en/stable/runtime-information.html#run-time-information # https://pyinstaller.org/en/stable/runtime-information.html#run-time-information
executable = getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS') executable = getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')
if cv2_available and not headless:
try:
tk_root = tkinter.Tk()
tk_root.withdraw()
except tkinter.TclError:
headless = True
args = parse_args(sys_args) args = parse_args(sys_args)
if colored: if colored:
@@ -174,9 +195,11 @@ def main(sys_args: list[str]) -> None:
sys.exit(0 if do_debug_checks() else 1) sys.exit(0 if do_debug_checks() else 1)
otps = extract_otps(args) otps = extract_otps(args)
write_csv(args, otps)
write_keepass_csv(args, otps) write_csv(args.csv, otps)
write_json(args, otps) write_keepass_csv(args.keepass, otps)
write_json(args.json, otps)
write_txt(args.txt, otps, True)
# workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None # workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None
@@ -239,9 +262,9 @@ def extract_otp_from_otp_url(otpauth_migration_url: str, otps: Otps, urls_count:
if not quiet: if not quiet:
print_otp(otp) print_otp(otp)
if args.printqr: if args.printqr:
print_qr(args, otp_url) print_qr(otp_url)
if args.saveqr: if args.saveqr:
save_qr(otp, args, len(otps)) save_qr_image(otp, args.saveqr, len(otps))
if not quiet: if not quiet:
print() print()
elif args.ignore and not quiet: elif args.ignore and not quiet:
@@ -257,7 +280,7 @@ def parse_args(sys_args: list[str]) -> Args:
name = os.path.basename(sys.argv[0]) name = os.path.basename(sys.argv[0])
cmd = f"python {name}" if name.endswith('.py') else f"{name}" cmd = f"python {name}" if name.endswith('.py') else f"{name}"
description_text = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps" description_text = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps"
if qreader_available: if cv2_available:
description_text += "\nIf no infiles are provided, a GUI window starts and QR codes are captured from the camera." description_text += "\nIf no infiles are provided, a GUI window starts and QR codes are captured from the camera."
example_text = f"""examples: example_text = f"""examples:
{cmd} {cmd}
@@ -270,15 +293,19 @@ def parse_args(sys_args: list[str]) -> Args:
description=description_text, description=description_text,
epilog=example_text) epilog=example_text)
arg_parser.add_argument('infile', help="""a) file or - for stdin with 'otpauth-migration://...' URLs separated by newlines, lines starting with # are ignored; arg_parser.add_argument('infile', help="""a) file or - for stdin with 'otpauth-migration://...' URLs separated by newlines, lines starting with # are ignored;
b) image file containing a QR code or = for stdin for an image containing a QR code""", nargs='*' if qreader_available else '+') b) image file containing a QR code or = for stdin for an image containing a QR code""", nargs='*' if cv2_available else '+')
arg_parser.add_argument('--csv', '-c', help='export csv file or - for stdout', metavar=('FILE')) arg_parser.add_argument('--csv', '-c', help='export csv file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--keepass', '-k', help='export totp/hotp csv file(s) for KeePass, - for stdout', metavar=('FILE')) arg_parser.add_argument('--keepass', '-k', help='export totp/hotp csv file(s) for KeePass, - for stdout', metavar=('FILE'))
arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE')) arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal (requires qrcode module)', action='store_true') arg_parser.add_argument('--txt', '-t', help='export txt file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to the given folder (requires qrcode module)', metavar=('DIR')) arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal', action='store_true')
if qreader_available: arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to directory', metavar=('DIR'))
if cv2_available:
arg_parser.add_argument('--camera', '-C', help='camera number of system (default camera: 0)', default=0, type=int, metavar=('NUMBER')) arg_parser.add_argument('--camera', '-C', help='camera number of system (default camera: 0)', default=0, type=int, metavar=('NUMBER'))
arg_parser.add_argument('--qr', '-Q', help=f'QR reader (default: {QRMode.ZBAR.name})', type=str, choices=[mode.name for mode in QRMode], default=QRMode.ZBAR.name) if not zbar_available:
arg_parser.add_argument('--qr', '-Q', help=f'QR reader (default: {QRMode.CV2.name})', type=str, choices=[QRMode.CV2.name, QRMode.CV2_WECHAT.name], default=QRMode.CV2.name)
else:
arg_parser.add_argument('--qr', '-Q', help=f'QR reader (default: {QRMode.ZBAR.name})', type=str, choices=[mode.name for mode in QRMode], default=QRMode.ZBAR.name)
arg_parser.add_argument('-i', '--ignore', help='ignore duplicate otps', action='store_true') arg_parser.add_argument('-i', '--ignore', help='ignore duplicate otps', action='store_true')
arg_parser.add_argument('--no-color', '-n', help='do not use ANSI colors in console output', action='store_true') arg_parser.add_argument('--no-color', '-n', help='do not use ANSI colors in console output', action='store_true')
arg_parser.add_argument('--version', '-V', help='print version and quit', action=PrintVersionAction) arg_parser.add_argument('--version', '-V', help='print version and quit', action=PrintVersionAction)
@@ -288,7 +315,7 @@ b) image file containing a QR code or = for stdin for an image containing a QR c
output_group.add_argument('-q', '--quiet', help='no stdout output, except output set by -', action='store_true') output_group.add_argument('-q', '--quiet', help='no stdout output, except output set by -', action='store_true')
args = arg_parser.parse_args(sys_args) args = arg_parser.parse_args(sys_args)
colored = not args.no_color colored = not args.no_color
if args.csv == '-' or args.json == '-' or args.keepass == '-': if args.csv == '-' or args.json == '-' or args.keepass == '-' or args.txt == '-':
args.quiet = args.q = True args.quiet = args.q = True
verbose = args.verbose if args.verbose else LogLevel.NORMAL verbose = args.verbose if args.verbose else LogLevel.NORMAL
@@ -296,8 +323,8 @@ b) image file containing a QR code or = for stdin for an image containing a QR c
verbose = LogLevel.DEBUG verbose = LogLevel.DEBUG
log_debug('Debug mode start') log_debug('Debug mode start')
quiet = True if args.quiet else False quiet = True if args.quiet else False
if verbose: print(f"QReader installed: {qreader_available}") if verbose: print(f"QReader installed: {cv2_available}")
if qreader_available: if cv2_available:
if verbose >= LogLevel.VERBOSE: print(f"CV2 version: {cv2.__version__}") if verbose >= LogLevel.VERBOSE: print(f"CV2 version: {cv2.__version__}")
if verbose: print(f"QR reading mode: {args.qr}\n") if verbose: print(f"QR reading mode: {args.qr}\n")
@@ -321,7 +348,8 @@ def extract_otps_from_camera(args: Args) -> Otps:
cam = cv2.VideoCapture(args.camera) cam = cv2.VideoCapture(args.camera)
cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_AUTOSIZE) cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_AUTOSIZE)
qreader = QReader() if zbar_available:
qreader = QReader()
cv2_qr = cv2.QRCodeDetector() cv2_qr = cv2.QRCodeDetector()
cv2_qr_wechat = cv2.wechat_qrcode.WeChatQRCode() cv2_qr_wechat = cv2.wechat_qrcode.WeChatQRCode()
while True: while True:
@@ -362,15 +390,16 @@ def extract_otps_from_camera(args: Args) -> Otps:
qr_mode = next_qr_mode(qr_mode) qr_mode = next_qr_mode(qr_mode)
continue continue
cv2_print_text(img, f"Mode: {qr_mode.name} (Hit space to change)", 0, TextPosition.LEFT, FONT_COLOR, 20) cv2_print_text(img, f"Mode: {qr_mode.name} (Hit SPACE to change)", 0, TextPosition.LEFT, FONT_COLOR, 20)
cv2_print_text(img, "Hit ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17) cv2_print_text(img, "Press ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17)
cv2_print_text(img, "Press C/J/K/T to save as csv/json/keepass/txt file", 2, TextPosition.LEFT, FONT_COLOR, None)
cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR) cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR)
cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR) cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR)
cv2.imshow(WINDOW_NAME, img) cv2.imshow(WINDOW_NAME, img)
quit, qr_mode = cv2_handle_pressed_keys(qr_mode) quit, qr_mode = cv2_handle_pressed_keys(qr_mode, otps)
if quit: if quit:
break break
@@ -416,14 +445,62 @@ def cv2_print_text(img: Any, text: str, line_number: int, position: TextPosition
cv2.putText(img, out_text, pos, FONT, FONT_SCALE, color, FONT_THICKNESS, FONT_LINE_STYLE) cv2.putText(img, out_text, pos, FONT, FONT_SCALE, color, FONT_THICKNESS, FONT_LINE_STYLE)
def cv2_handle_pressed_keys(qr_mode: QRMode) -> Tuple[bool, QRMode]: def cv2_handle_pressed_keys(qr_mode: QRMode, otps: Otps) -> Tuple[bool, QRMode]:
key = cv2.waitKey(1) & 0xFF key = cv2.waitKey(1) & 0xFF
quit = False quit = False
if key == 27 or key == ord('q') or key == 13: if key == 27 or key == ord('q') or key == ord('Q') or key == 13:
# ESC or Enter or q pressed # ESC or Enter or q pressed
quit = True quit = True
elif (key == ord('c') or key == ord('C')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as CSV",
defaultextension='.csv',
filetypes=[('CSV', '*.csv'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_csv(file_name, otps)
elif (key == ord('j') or key == ord('J')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as JSON",
defaultextension='.json',
filetypes=[('JSON', '*.json'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_json(file_name, otps)
elif (key == ord('k') or key == ord('K')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as KeePass CSV file(s)",
defaultextension='.csv',
filetypes=[('CSV', '*.csv'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_keepass_csv(file_name, otps)
elif (key == ord('t') or key == ord('T')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as text",
defaultextension='.txt',
filetypes=[('Text', '*.txt'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_txt(file_name, otps, True)
elif key == 32: elif key == 32:
qr_mode = next_qr_mode(qr_mode) qr_mode = next_valid_qr_mode(qr_mode, zbar_available)
if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}") if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}")
if cv2.getWindowProperty(WINDOW_NAME, cv2.WND_PROP_VISIBLE) < 1: if cv2.getWindowProperty(WINDOW_NAME, cv2.WND_PROP_VISIBLE) < 1:
# Window close clicked # Window close clicked
@@ -471,7 +548,7 @@ def get_otp_urls_from_file(filename: str, args: Args) -> OtpUrls:
return lines return lines
# could not process text file, try reading as image # could not process text file, try reading as image
if filename != '-' and qreader_available: if filename != '-' and cv2_available:
return convert_img_to_otp_urls(filename, args) return convert_img_to_otp_urls(filename, args)
return [] return []
@@ -591,28 +668,27 @@ def build_otp_url(secret: str, raw_otp: pb.MigrationPayload.OtpParameters) -> st
return otp_url return otp_url
def print_otp(otp: Otp) -> None: def print_otp(otp: Otp, out: Optional[TextIO] = None) -> None:
print(f"Name: {otp['name']}") print(f"Name: {otp['name']}", file=out)
print(f"Secret: {otp['secret']}") print(f"Secret: {otp['secret']}", file=out)
if otp['issuer']: print(f"Issuer: {otp['issuer']}") if otp['issuer']: print(f"Issuer: {otp['issuer']}", file=out)
print(f"Type: {otp['type']}") print(f"Type: {otp['type']}", file=out)
if otp['type'] == 'hotp': if otp['type'] == 'hotp':
print(f"Counter: {otp['counter']}") print(f"Counter: {otp['counter']}", file=out)
if verbose: if verbose:
print(otp['url']) print(otp['url'], file=out)
def save_qr(otp: Otp, args: Args, j: int) -> str: def save_qr_image(otp: Otp, dir: str, j: int) -> str:
dir = args.saveqr
if not (os.path.exists(dir)): os.makedirs(dir, exist_ok=True) if not (os.path.exists(dir)): os.makedirs(dir, exist_ok=True)
pattern = re.compile(r'[\W_]+') pattern = re.compile(r'[\W_]+')
file_otp_name = pattern.sub('', otp['name']) file_otp_name = pattern.sub('', otp['name'])
file_otp_issuer = pattern.sub('', otp['issuer']) file_otp_issuer = pattern.sub('', otp['issuer'])
save_qr_file(args, otp['url'], f"{dir}/{j}-{file_otp_name}{'-' + file_otp_issuer if file_otp_issuer else ''}.png") save_qr_image_file(otp['url'], f"{dir}/{j}-{file_otp_name}{'-' + file_otp_issuer if file_otp_issuer else ''}.png")
return file_otp_name return file_otp_name
def save_qr_file(args: Args, otp_url: OtpUrl, name: str) -> None: def save_qr_image_file(otp_url: OtpUrl, name: str) -> None:
qr = QRCode() qr = QRCode()
qr.add_data(otp_url) qr.add_data(otp_url)
img = qr.make_image(fill_color='black', back_color='white') img = qr.make_image(fill_color='black', back_color='white')
@@ -620,28 +696,38 @@ def save_qr_file(args: Args, otp_url: OtpUrl, name: str) -> None:
img.save(name) img.save(name)
def print_qr(args: Args, otp_url: str) -> None: def print_qr(otp_url: str, out: Optional[TextIO] = None) -> None:
qr = QRCode() qr = QRCode()
qr.add_data(otp_url) qr.add_data(otp_url)
qr.print_ascii() qr.print_ascii(out)
def write_csv(args: Args, otps: Otps) -> None: def write_txt(file: str, otps: Otps, write_qr: bool = False) -> None:
if args.csv and len(otps) > 0: if file and len(file) > 0 and len(otps) > 0:
with open_file_or_stdout_for_csv(args.csv) as outfile: with open_file_or_stdout(file) as outfile:
for otp in otps:
print_otp(otp, outfile)
if write_qr:
print_qr(otp['url'], outfile)
print(file=outfile)
def write_csv(file: str, otps: Otps) -> None:
if file and len(file) > 0 and len(otps) > 0:
with open_file_or_stdout_for_csv(file) as outfile:
writer = csv.DictWriter(outfile, otps[0].keys()) writer = csv.DictWriter(outfile, otps[0].keys())
writer.writeheader() writer.writeheader()
writer.writerows(otps) writer.writerows(otps)
if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to csv {args.csv}") if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to csv {file}")
def write_keepass_csv(args: Args, otps: Otps) -> None: def write_keepass_csv(file: str, otps: Otps) -> None:
if args.keepass and len(otps) > 0: if file and len(file) > 0 and len(otps) > 0:
has_totp = has_otp_type(otps, 'totp') has_totp = has_otp_type(otps, 'totp')
has_hotp = has_otp_type(otps, 'hotp') has_hotp = has_otp_type(otps, 'hotp')
if args.keepass != '-': if file != '-':
otp_filename_totp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "totp") otp_filename_totp = file if has_totp != has_hotp else add_pre_suffix(file, "totp")
otp_filename_hotp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "hotp") otp_filename_hotp = file if has_totp != has_hotp else add_pre_suffix(file, "hotp")
else: else:
otp_filename_totp = otp_filename_hotp = '-' otp_filename_totp = otp_filename_hotp = '-'
if has_totp: if has_totp:
@@ -653,9 +739,9 @@ def write_keepass_csv(args: Args, otps: Otps) -> None:
if count_hotp_entries: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}") if count_hotp_entries: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}")
def write_keepass_totp_csv(otp_filename: str, otps: Otps) -> int: def write_keepass_totp_csv(file: str, otps: Otps) -> int:
count_entries = 0 count_entries = 0
with open_file_or_stdout_for_csv(otp_filename) as outfile: with open_file_or_stdout_for_csv(file) as outfile:
writer = csv.DictWriter(outfile, ["Title", "User Name", "TimeOtp-Secret-Base32", "Group"]) writer = csv.DictWriter(outfile, ["Title", "User Name", "TimeOtp-Secret-Base32", "Group"])
writer.writeheader() writer.writeheader()
for otp in otps: for otp in otps:
@@ -670,9 +756,9 @@ def write_keepass_totp_csv(otp_filename: str, otps: Otps) -> int:
return count_entries return count_entries
def write_keepass_htop_csv(otp_filename: str, otps: Otps) -> int: def write_keepass_htop_csv(file: str, otps: Otps) -> int:
count_entries = 0 count_entries = 0
with open_file_or_stdout_for_csv(otp_filename) as outfile: with open_file_or_stdout_for_csv(file) as outfile:
writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"]) writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"])
writer.writeheader() writer.writeheader()
for otp in otps: for otp in otps:
@@ -688,11 +774,11 @@ def write_keepass_htop_csv(otp_filename: str, otps: Otps) -> int:
return count_entries return count_entries
def write_json(args: Args, otps: Otps) -> None: def write_json(file: str, otps: Otps) -> None:
if args.json: if file and len(file) > 0:
with open_file_or_stdout(args.json) as outfile: with open_file_or_stdout(file) as outfile:
json.dump(otps, outfile, indent=4) json.dump(otps, outfile, indent=4)
if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to json {args.json}") if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to json {file}")
def has_otp_type(otps: Otps, otp_type: str) -> bool: def has_otp_type(otps: Otps, otp_type: str) -> bool:
@@ -729,6 +815,13 @@ def check_file_exists(filename: str) -> None:
f"\ninput file: {filename}") f"\ninput file: {filename}")
def has_no_otps_show_warning(otps: Otps) -> bool:
if len(otps) == 0:
tkinter.messagebox.showinfo(title="No data", message="There are no otp secrets to write")
tk_root.update() # dispose dialog
return len(otps) == 0
def is_binary(line: str) -> bool: def is_binary(line: str) -> bool:
try: try:
line.startswith('#') line.startswith('#')
@@ -737,6 +830,14 @@ def is_binary(line: str) -> bool:
return True return True
def next_valid_qr_mode(qr_mode: QRMode, with_zbar: bool = True) -> QRMode:
ok = False
while not ok:
qr_mode = next_qr_mode(qr_mode)
ok = True if with_zbar else qr_mode.name in CV2_QRMODES
return qr_mode
def next_qr_mode(qr_mode: QRMode) -> QRMode: def next_qr_mode(qr_mode: QRMode) -> QRMode:
return QRMode((qr_mode.value + 1) % len(QRMode)) return QRMode((qr_mode.value + 1) % len(QRMode))
@@ -747,10 +848,20 @@ def do_debug_checks() -> bool:
import cv2 # noqa: F401 # This is only a debug import import cv2 # noqa: F401 # This is only a debug import
log_debug('Try: import numpy as np') log_debug('Try: import numpy as np')
import numpy as np # noqa: F401 # This is only a debug import import numpy as np # noqa: F401 # This is only a debug import
log_debug('Try: import pyzbar.pyzbar as zbar')
import pyzbar.pyzbar as zbar # noqa: F401 # This is only a debug import
log_debug('Try: from qreader import QReader')
from qreader import QReader # noqa: F401 # This is only a debug import
print(color('\nDebug checks passed', colorama.Fore.GREEN)) print(color('\nDebug checks passed', colorama.Fore.GREEN))
return True return True
def is_not_headless() -> bool:
if headless:
log_warn("Cannot open dialog in headless mode")
return not headless
class PrintVersionAction(argparse.Action): class PrintVersionAction(argparse.Action):
def __init__(self, option_strings: Sequence[str], dest: str, nargs: int = 0, **kwargs: Any) -> None: def __init__(self, option_strings: Sequence[str], dest: str, nargs: int = 0, **kwargs: Any) -> None:
super().__init__(option_strings, dest, nargs, **kwargs) super().__init__(option_strings, dest, nargs, **kwargs)

View File

@@ -15,17 +15,18 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11google_auth.proto\"\xb7\x03\n\x10MigrationPayload\x12\x37\n\x0eotp_parameters\x18\x01 \x03(\x0b\x32\x1f.MigrationPayload.OtpParameters\x12\x0f\n\x07version\x18\x02 \x01(\x05\x12\x12\n\nbatch_size\x18\x03 \x01(\x05\x12\x13\n\x0b\x62\x61tch_index\x18\x04 \x01(\x05\x12\x10\n\x08\x62\x61tch_id\x18\x05 \x01(\x05\x1a\xb7\x01\n\rOtpParameters\x12\x0e\n\x06secret\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06issuer\x18\x03 \x01(\t\x12.\n\talgorithm\x18\x04 \x01(\x0e\x32\x1b.MigrationPayload.Algorithm\x12\x0e\n\x06\x64igits\x18\x05 \x01(\x05\x12\'\n\x04type\x18\x06 \x01(\x0e\x32\x19.MigrationPayload.OtpType\x12\x0f\n\x07\x63ounter\x18\x07 \x01(\x03\",\n\tAlgorithm\x12\x10\n\x0c\x41LGO_INVALID\x10\x00\x12\r\n\tALGO_SHA1\x10\x01\"6\n\x07OtpType\x12\x0f\n\x0bOTP_INVALID\x10\x00\x12\x0c\n\x08OTP_HOTP\x10\x01\x12\x0c\n\x08OTP_TOTP\x10\x02\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11google_auth.proto\"\xb7\x03\n\x10MigrationPayload\x12\x37\n\x0eotp_parameters\x18\x01 \x03(\x0b\x32\x1f.MigrationPayload.OtpParameters\x12\x0f\n\x07version\x18\x02 \x01(\x05\x12\x12\n\nbatch_size\x18\x03 \x01(\x05\x12\x13\n\x0b\x62\x61tch_index\x18\x04 \x01(\x05\x12\x10\n\x08\x62\x61tch_id\x18\x05 \x01(\x05\x1a\xb7\x01\n\rOtpParameters\x12\x0e\n\x06secret\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06issuer\x18\x03 \x01(\t\x12.\n\talgorithm\x18\x04 \x01(\x0e\x32\x1b.MigrationPayload.Algorithm\x12\x0e\n\x06\x64igits\x18\x05 \x01(\x05\x12\'\n\x04type\x18\x06 \x01(\x0e\x32\x19.MigrationPayload.OtpType\x12\x0f\n\x07\x63ounter\x18\x07 \x01(\x03\",\n\tAlgorithm\x12\x10\n\x0c\x41LGO_INVALID\x10\x00\x12\r\n\tALGO_SHA1\x10\x01\"6\n\x07OtpType\x12\x0f\n\x0bOTP_INVALID\x10\x00\x12\x0c\n\x08OTP_HOTP\x10\x01\x12\x0c\n\x08OTP_TOTP\x10\x02\x62\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _globals = globals()
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', globals()) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
_MIGRATIONPAYLOAD._serialized_start=22 _globals['_MIGRATIONPAYLOAD']._serialized_start=22
_MIGRATIONPAYLOAD._serialized_end=461 _globals['_MIGRATIONPAYLOAD']._serialized_end=461
_MIGRATIONPAYLOAD_OTPPARAMETERS._serialized_start=176 _globals['_MIGRATIONPAYLOAD_OTPPARAMETERS']._serialized_start=176
_MIGRATIONPAYLOAD_OTPPARAMETERS._serialized_end=359 _globals['_MIGRATIONPAYLOAD_OTPPARAMETERS']._serialized_end=359
_MIGRATIONPAYLOAD_ALGORITHM._serialized_start=361 _globals['_MIGRATIONPAYLOAD_ALGORITHM']._serialized_start=361
_MIGRATIONPAYLOAD_ALGORITHM._serialized_end=405 _globals['_MIGRATIONPAYLOAD_ALGORITHM']._serialized_end=405
_MIGRATIONPAYLOAD_OTPTYPE._serialized_start=407 _globals['_MIGRATIONPAYLOAD_OTPTYPE']._serialized_start=407
_MIGRATIONPAYLOAD_OTPTYPE._serialized_end=461 _globals['_MIGRATIONPAYLOAD_OTPTYPE']._serialized_end=461
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -45,11 +45,11 @@ except ImportError:
# ignore # ignore
pass pass
qreader_available: bool = extract_otp_secrets.qreader_available cv2_available: bool = extract_otp_secrets.cv2_available
# Quickfix comment # Quickfix comment
# @pytest.mark.skipif(sys.platform.startswith("win") or not qreader_available or sys.implementation.name == 'pypy' or sys.version_info >= (3, 10), reason="Quickfix") # @pytest.mark.skipif(sys.platform.startswith("win") or not cv2 or sys.implementation.name == 'pypy' or sys.version_info >= (3, 10), reason="Quickfix")
def test_extract_stdout(capsys: pytest.CaptureFixture[str]) -> None: def test_extract_stdout(capsys: pytest.CaptureFixture[str]) -> None:
@@ -122,7 +122,7 @@ def test_extract_stdin_only_comments(capsys: pytest.CaptureFixture[str], monkeyp
def test_extract_empty_file_no_qreader(capsys: pytest.CaptureFixture[str]) -> None: def test_extract_empty_file_no_qreader(capsys: pytest.CaptureFixture[str]) -> None:
if qreader_available: if cv2_available:
# Act # Act
with pytest.raises(SystemExit) as e: with pytest.raises(SystemExit) as e:
extract_otp_secrets.main(['-n', 'tests/data/empty_file.txt']) extract_otp_secrets.main(['-n', 'tests/data/empty_file.txt'])
@@ -376,6 +376,48 @@ def test_extract_json_stdout_only_comments(capsys: pytest.CaptureFixture[str]) -
assert captured.err == '' assert captured.err == ''
def test_extract_txt(capsys: pytest.CaptureFixture[str], tmp_path: pathlib.Path) -> None:
# Arrange
output_file = str(tmp_path / 'test_example_output.txt')
# Act
extract_otp_secrets.main(['-q', '-t', output_file, 'example_export.txt'])
# Assert
expected_txt = read_file_to_str('tests/data/printqr_output.txt')
actual_txt = read_file_to_str(output_file)
assert actual_txt == expected_txt
captured = capsys.readouterr()
assert captured.out == ''
assert captured.err == ''
def test_extract_txt_stdout(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-t', '-', 'example_export.txt'])
# Assert
expected_txt = read_file_to_str('tests/data/printqr_output.txt')
captured = capsys.readouterr()
assert captured.out == expected_txt
assert captured.err == ''
def test_extract_txt_stdout_only_comments(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-t', '-', 'tests/data/only_comments.txt'])
# Assert
captured = capsys.readouterr()
assert captured.out == ''
assert captured.err == ''
def test_extract_not_encoded_plus(capsys: pytest.CaptureFixture[str]) -> None: def test_extract_not_encoded_plus(capsys: pytest.CaptureFixture[str]) -> None:
# Act # Act
extract_otp_secrets.main(['tests/data/test_plus_problem_export.txt']) extract_otp_secrets.main(['tests/data/test_plus_problem_export.txt'])
@@ -510,7 +552,7 @@ def test_extract_verbose(verbose_level: str, color: str, capsys: pytest.CaptureF
def normalize_verbose_text(text: str, relaxed: bool) -> str: def normalize_verbose_text(text: str, relaxed: bool) -> str:
normalized = re.sub('^.*version: .+$', '', text, flags=re.MULTILINE | re.IGNORECASE) normalized = re.sub('^.*version: .+$', '', text, flags=re.MULTILINE | re.IGNORECASE)
if not qreader_available: if not cv2_available:
normalized = normalized \ normalized = normalized \
.replace('QReader installed: True', 'QReader installed: False') \ .replace('QReader installed: True', 'QReader installed: False') \
.replace('\nQR reading mode: ZBAR\n\n', '') .replace('\nQR reading mode: ZBAR\n\n', '')
@@ -564,7 +606,7 @@ def test_extract_version(capsys: pytest.CaptureFixture[str]) -> None:
def test_extract_no_arguments(capsys: pytest.CaptureFixture[str], mocker: MockerFixture) -> None: def test_extract_no_arguments(capsys: pytest.CaptureFixture[str], mocker: MockerFixture) -> None:
if qreader_available: if cv2_available:
# Arrange # Arrange
otps = read_json('example_output.json') otps = read_json('example_output.json')
mocker.patch('extract_otp_secrets.extract_otps_from_camera', return_value=otps) mocker.patch('extract_otp_secrets.extract_otps_from_camera', return_value=otps)
@@ -648,7 +690,7 @@ class MockCam:
('CV2_WECHAT', 'tests/data/lena_std.tif', None), ('CV2_WECHAT', 'tests/data/lena_std.tif', None),
]) ])
def test_extract_otps_from_camera(qr_reader: Optional[str], file: str, success: bool, capsys: pytest.CaptureFixture[str], mocker: MockerFixture) -> None: def test_extract_otps_from_camera(qr_reader: Optional[str], file: str, success: bool, capsys: pytest.CaptureFixture[str], mocker: MockerFixture) -> None:
if qreader_available: if cv2_available:
# Arrange # Arrange
mockCam = MockCam([file]) mockCam = MockCam([file])
mocker.patch('cv2.VideoCapture', return_value=mockCam) mocker.patch('cv2.VideoCapture', return_value=mockCam)
@@ -724,8 +766,10 @@ def test_verbose_and_quiet(capsys: pytest.CaptureFixture[str]) -> None:
('-k', 'outfile', False, False), ('-k', 'outfile', False, False),
('-k', '-', True, False), ('-k', '-', True, False),
('-j', 'outfile', False, False), ('-j', 'outfile', False, False),
('-s', 'outfile', False, False),
('-j', '-', True, False), ('-j', '-', True, False),
('-t', 'outfile', False, False),
('-t', '-', True, False),
('-s', 'outfile', False, False),
('-i', None, False, False), ('-i', None, False, False),
('-p', None, True, False), ('-p', None, True, False),
('-Q', 'CV2', False, False), ('-Q', 'CV2', False, False),
@@ -733,7 +777,7 @@ def test_verbose_and_quiet(capsys: pytest.CaptureFixture[str]) -> None:
('-n', None, False, False), ('-n', None, False, False),
]) ])
def test_quiet(parameter: str, parameter_value: Optional[str], stdout_expected: bool, stderr_expected: bool, capsys: pytest.CaptureFixture[str], tmp_path: pathlib.Path) -> None: def test_quiet(parameter: str, parameter_value: Optional[str], stdout_expected: bool, stderr_expected: bool, capsys: pytest.CaptureFixture[str], tmp_path: pathlib.Path) -> None:
if parameter in ['-Q', '-C'] and not qreader_available: if parameter in ['-Q', '-C'] and not cv2_available:
return return
# Arrange # Arrange
@@ -1076,6 +1120,12 @@ url: This is just a text file masquerading as an image file.
assert captured.out == '' assert captured.out == ''
def test_next_valid_qr_mode() -> None:
assert extract_otp_secrets.next_valid_qr_mode(extract_otp_secrets.QRMode.CV2, True) == extract_otp_secrets.QRMode.CV2_WECHAT
assert extract_otp_secrets.next_valid_qr_mode(extract_otp_secrets.QRMode.CV2_WECHAT, True) == extract_otp_secrets.QRMode.ZBAR
assert extract_otp_secrets.next_valid_qr_mode(extract_otp_secrets.QRMode.CV2_WECHAT, False) == extract_otp_secrets.QRMode.CV2
EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT = '''Name: pi@raspberrypi EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT = '''Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi Issuer: raspberrypi