Compare commits

...

11 Commits

Author SHA1 Message Date
scito
b9c552ab45 use latest cv2 also for macos 2023-02-25 10:24:56 +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
19 changed files with 575 additions and 294 deletions

View File

@@ -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

@@ -79,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
@@ -152,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
@@ -235,7 +235,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 }}
build-args: | build-args: |
BASE_IMAGE=python:3.11-slim-buster BASE_IMAGE=python:3.11-slim-buster

View File

@@ -20,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
@@ -51,6 +55,8 @@ jobs:
tag_name: ${{ steps.meta.outputs.tag_name }} tag_name: ${{ steps.meta.outputs.tag_name }}
tag_message: ${{ steps.meta.outputs.tag_message }} 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
@@ -66,8 +72,13 @@ jobs:
- name: Create Release - name: Create Release
id: create_release id: create_release
if: startsWith(github.ref, 'refs/tags/v') 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" \
@@ -76,7 +87,7 @@ 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 }}\n\nJust download the executable and execute it.\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 | MacOS x86_64/amd64 (barely tested due to no access to Apple environment; libzbar must be installed separately, see README.md) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_arm64 | N/A |\n ","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 echo $(jq -r '.id' <<< "$response") > release_id.txt
@@ -186,6 +197,11 @@ jobs:
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
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
@@ -215,34 +231,33 @@ jobs:
# 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
# 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 # 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
OUT_FILE_NAME: extract_otp_secrets.exe EXE: extract_otp_secrets.exe
ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_win_x86_64.exe ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_win_x86_64.exe
ASSET_MIME: application/vnd.microsoft.portable-executable ASSET_MIME: application/vnd.microsoft.portable-executable
UPLOAD: true UPLOAD: true
CMD_BUILD: | 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\file_version_info.txt src\extract_otp_secrets.py 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
# TODO add --osx-entitlements-file
# TODO https://pyinstaller.org/en/stable/spec-files.html#spec-file-options-for-a-macos-bundle
# TODO --target-arch universal2
OUT_FILE_NAME: extract_otp_secrets
ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_macos_x86_64 ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_macos_x86_64
ASSET_MIME: application/x-newton-compatible-pkg DMG: extract_otp_secrets.dmg
ASSET_NAME_DMG: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_macos_x86_64.dmg
ASSET_MIME: application/octet-stream
UPLOAD: true UPLOAD: true
CMD_BUILD: | 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 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
OUT_FILE_NAME: extract_otp_secrets EXE: extract_otp_secrets_ubuntu
ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_x86_64_ubuntu_latest ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_x86_64_ubuntu_latest
ASSET_MIME: application/x-executable ASSET_MIME: application/x-executable
UPLOAD: false UPLOAD: false
CMD_BUILD: | CMD_BUILD: |
pyinstaller -y --add-data $pythonLocation/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile src/extract_otp_secrets.py 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 - name: Output path
if: runner.os == 'Windows' if: runner.os == 'Windows'
@@ -266,18 +281,18 @@ 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 -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))) 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: env:
# Reproducible build: https://pyinstaller.org/en/stable/advanced-topics.html#creating-a-reproducible-build # Reproducible build: https://pyinstaller.org/en/stable/advanced-topics.html#creating-a-reproducible-build
@@ -285,18 +300,18 @@ jobs:
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.OUT_FILE_NAME }} --qr ZBAR example_export.png dist/${{ matrix.EXE }} --qr ZBAR example_export.png
dist/${{ matrix.OUT_FILE_NAME }} --qr QREADER example_export.png dist/${{ matrix.EXE }} --qr QREADER example_export.png
dist/${{ matrix.OUT_FILE_NAME }} --qr QREADER_DEEP example_export.png dist/${{ matrix.EXE }} --qr QREADER_DEEP example_export.png
dist/${{ matrix.OUT_FILE_NAME }} --qr CV2 example_export.png dist/${{ matrix.EXE }} --qr CV2 example_export.png
dist/${{ matrix.OUT_FILE_NAME }} --qr CV2_WECHAT 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') if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
@@ -316,11 +331,27 @@ jobs:
run: | run: |
echo "release_id=$(cat release_id.txt)" >> $GITHUB_OUTPUT 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 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 - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
if: matrix.UPLOAD && startsWith(github.ref, 'refs/tags/v') if: matrix.UPLOAD && startsWith(github.ref, 'refs/tags/v')
run: | 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.OUT_FILE_NAME }} ${{ steps.meta.outputs.upload_url }}=${{ matrix.ASSET_NAME }} 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: upload-hashes:
name: Upload hashes name: Upload hashes

6
.gitignore vendored
View File

@@ -20,9 +20,13 @@ 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/* 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

297
Pipfile.lock generated
View File

@@ -60,36 +60,29 @@
}, },
"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": [
@@ -176,23 +169,22 @@
}, },
"protobuf": { "protobuf": {
"hashes": [ "hashes": [
"sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30", "sha256:1669cb7524221a8e2d9008d0842453dbefdd0fcdd64d67672f657244867635fb",
"sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b", "sha256:29288813aacaa302afa2381db1d6e0482165737b0afdf2811df5fa99185c457b",
"sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc", "sha256:47d31bdf58222dd296976aa1646c68c6ee80b96d22e0a3c336c9174e253fd35e",
"sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791", "sha256:652d8dfece122a24d98eebfef30e31e455d300efa41999d1182e015984ac5930",
"sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717", "sha256:7c535d126e7dcc714105ab20b418c4fedbd28f8b8afc42b7350b1e317bbbcc71",
"sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec", "sha256:86c3d20428b007537ba6792b475c0853bba7f66b1f60e610d913b77d94b486e4",
"sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7", "sha256:a33a273d21852f911b8bda47f39f4383fe7c061eb1814db2c76c9875c89c2491",
"sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab", "sha256:ab4d043865dd04e6b09386981fe8f80b39a1e46139fb4a3c206229d6b9f36ff6",
"sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2", "sha256:b2fea9dc8e3c0f32c38124790ef16cba2ee0628fe2022a52e435e1117bfef9b1",
"sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5", "sha256:c27f371f0159feb70e6ea52ed7e768b3f3a4c5676c1900a7e51a24740381650e",
"sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1", "sha256:c3325803095fb4c2a48649c321d2fbde59f8fbfcb9bfc7a86df27d112831c571",
"sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462", "sha256:e474b63bab0a2ea32a7b26a4d8eec59e33e709321e5e16fb66e766b61b82a95e",
"sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97", "sha256:e894e9ae603e963f0842498c4cd5d39c6a60f0d7e4c103df50ee939564298658"
"sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.21.12" "version": "==4.22.0"
}, },
"pypng": { "pypng": {
"hashes": [ "hashes": [
@@ -227,21 +219,21 @@
}, },
"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"
} }
}, },
"develop": { "develop": {
"astroid": { "astroid": {
"hashes": [ "hashes": [
"sha256:23c718921acab5f08cbbbe9293967f1f8fec40c336d19cd75dc12a9ea31d2eb2", "sha256:0e0e3709d64fbffd3037e4ff403580550f14471fd3eaae9fa11cc9a5c7901153",
"sha256:bd1aa4f9915c98e8aaebcd4e71930154d4e8c9aaf05d35ac0a63d1956091ae3f" "sha256:a3cf9f02c53dd259144a7e8f3ccd75d67c9a8c716ef183e0c1f291bc5d7bb3cf"
], ],
"markers": "python_full_version >= '3.7.2'", "markers": "python_full_version >= '3.7.2'",
"version": "==2.14.1" "version": "==2.14.2"
}, },
"attrs": { "attrs": {
"hashes": [ "hashes": [
@@ -264,60 +256,60 @@
"toml" "toml"
], ],
"hashes": [ "hashes": [
"sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab", "sha256:049806ae2df69468c130f04f0fab4212c46b34ba5590296281423bb1ae379df2",
"sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851", "sha256:08e3dd256b8d3e07bb230896c8c96ec6c5dffbe5a133ba21f8be82b275b900e8",
"sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265", "sha256:0f03c229f1453b936916f68a47b3dfb5e84e7ad48e160488168a5e35115320c8",
"sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0", "sha256:171dd3aa71a49274a7e4fc26f5bc167bfae5a4421a668bc074e21a0522a0af4b",
"sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a", "sha256:1856a8c4aa77eb7ca0d42c996d0ca395ecafae658c1432b9da4528c429f2575c",
"sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5", "sha256:28563a35ef4a82b5bc5160a01853ce62b9fceee00760e583ffc8acf9e3413753",
"sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6", "sha256:2c15bd09fd5009f3a79c8b3682b52973df29761030b692043f9834fc780947c4",
"sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311", "sha256:2c9fffbc39dc4a6277e1525cab06c161d11ee3995bbc97543dc74fcec33e045b",
"sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada", "sha256:2d7daf3da9c7e0ed742b3e6b4de6cc464552e787b8a6449d16517b31bbdaddf5",
"sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f", "sha256:32e6a730fd18b2556716039ab93278ccebbefa1af81e6aa0c8dba888cf659e6e",
"sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8", "sha256:34d7211be69b215ad92298a962b2cd5a4ef4b17c7871d85e15d3d1b6dc8d8c96",
"sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc", "sha256:358d3bce1468f298b19a3e35183bdb13c06cdda029643537a0cc37e55e74e8f1",
"sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73", "sha256:3713a8ec18781fda408f0e853bf8c85963e2d3327c99a82a22e5c91baffcb934",
"sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf", "sha256:40785553d68c61e61100262b73f665024fd2bb3c6f0f8e2cd5b13e10e4df027b",
"sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e", "sha256:4655ecd813f4ba44857af3e9cffd133ab409774e9d2a7d8fdaf4fdfd2941b789",
"sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352", "sha256:465ea431c3b78a87e32d7d9ea6d081a1003c43a442982375cf2c247a19971961",
"sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c", "sha256:4b8fd32f85b256fc096deeb4872aeb8137474da0c0351236f93cbedc359353d6",
"sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c", "sha256:4c1153a6156715db9d6ae8283480ae67fb67452aa693a56d7dae9ffe8f7a80da",
"sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c", "sha256:577a8bc40c01ad88bb9ab1b3a1814f2f860ff5c5099827da2a3cafc5522dadea",
"sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda", "sha256:59a427f8a005aa7254074719441acb25ac2c2f60c1f1026d43f846d4254c1c2f",
"sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d", "sha256:5e29a64e9586194ea271048bc80c83cdd4587830110d1e07b109e6ff435e5dbc",
"sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0", "sha256:74cd60fa00f46f28bd40048d6ca26bd58e9bee61d2b0eb4ec18cea13493c003f",
"sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3", "sha256:7efa21611ffc91156e6f053997285c6fe88cfef3fb7533692d0692d2cb30c846",
"sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d", "sha256:7f992b32286c86c38f07a8b5c3fc88384199e82434040a729ec06b067ee0d52c",
"sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038", "sha256:875b03d92ac939fbfa8ae74a35b2c468fc4f070f613d5b1692f9980099a3a210",
"sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c", "sha256:88ae5929f0ef668b582fd7cad09b5e7277f50f912183cf969b36e82a1c26e49a",
"sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8", "sha256:8d5302eb84c61e758c9d68b8a2f93a398b272073a046d07da83d77b0edc8d76b",
"sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa", "sha256:90e7a4cbbb7b1916937d380beb1315b12957b8e895d7d9fb032e2038ac367525",
"sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09", "sha256:9240a0335365c29c968131bdf624bb25a8a653a9c0d8c5dbfcabf80b59c1973c",
"sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b", "sha256:932048364ff9c39030c6ba360c31bf4500036d4e15c02a2afc5a76e7623140d4",
"sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c", "sha256:93db11da6e728587e943dff8ae1b739002311f035831b6ecdb15e308224a4247",
"sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a", "sha256:971b49dbf713044c3e5f6451b39f65615d4d1c1d9a19948fa0f41b0245a98765",
"sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52", "sha256:9cc9c41aa5af16d845b53287051340c363dd03b7ef408e45eec3af52be77810d",
"sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3", "sha256:9dbb21561b0e04acabe62d2c274f02df0d715e8769485353ddf3cf84727e31ce",
"sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146", "sha256:a6ceeab5fca62bca072eba6865a12d881f281c74231d2990f8a398226e1a5d96",
"sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a", "sha256:ad12c74c6ce53a027f5a5ecbac9be20758a41c85425c1bbab7078441794b04ee",
"sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f", "sha256:b09dd7bef59448c66e6b490cc3f3c25c14bc85d4e3c193b81a6204be8dd355de",
"sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4", "sha256:bd67df6b48db18c10790635060858e2ea4109601e84a1e9bfdd92e898dc7dc79",
"sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c", "sha256:bf9e02bc3dee792b9d145af30db8686f328e781bd212fdef499db5e9e4dd8377",
"sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75", "sha256:bfa065307667f1c6e1f4c3e13f415b0925e34e56441f5fda2c84110a4a1d8bda",
"sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040", "sha256:c160e34e388277f10c50dc2c7b5e78abe6d07357d9fe7fcb2f3c156713fd647e",
"sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063", "sha256:c243b25051440386179591a8d5a5caff4484f92c980fb6e061b9559da7cc3f64",
"sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050", "sha256:c3c4beddee01c8125a75cde3b71be273995e2e9ec08fbc260dd206b46bb99969",
"sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7", "sha256:cd38140b56538855d3d5722c6d1b752b35237e7ea3f360047ce57f3fade82d98",
"sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222", "sha256:d7f2a7df523791e6a63b40360afa6792a11869651307031160dc10802df9a252",
"sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912", "sha256:da32526326e8da0effb452dc32a21ffad282c485a85a02aeff2393156f69c1c3",
"sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801", "sha256:dc4f9a89c82faf6254d646180b2e3aa4daf5ff75bdb2c296b9f6a6cf547e26a7",
"sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d", "sha256:f0557289260125a6c453ad5673ba79e5b6841d9a20c9e101f758bfbedf928a77",
"sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06", "sha256:f332d61fbff353e2ef0f3130a166f499c3fad3a196e7f7ae72076d41a6bfb259",
"sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8", "sha256:f3ff4205aff999164834792a3949f82435bc7c7655c849226d5836c3242d7451",
"sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2" "sha256:ffa637a2d5883298449a5434b699b22ef98dd8e2ef8a1d9e60fa9cfe79813411"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==7.1.0" "version": "==7.2.0"
}, },
"dill": { "dill": {
"hashes": [ "hashes": [
@@ -411,35 +403,35 @@
}, },
"mypy": { "mypy": {
"hashes": [ "hashes": [
"sha256:01b1b9e1ed40544ef486fa8ac022232ccc57109f379611633ede8e71630d07d2", "sha256:0af4f0e20706aadf4e6f8f8dc5ab739089146b83fd53cb4a7e0e850ef3de0bb6",
"sha256:0ab090d9240d6b4e99e1fa998c2d0aa5b29fc0fb06bd30e7ad6183c95fa07593", "sha256:15b5a824b58c7c822c51bc66308e759243c32631896743f030daf449fe3677f3",
"sha256:14d776869a3e6c89c17eb943100f7868f677703c8a4e00b3803918f86aafbc52", "sha256:17455cda53eeee0a4adb6371a21dd3dbf465897de82843751cf822605d152c8c",
"sha256:1ace23f6bb4aec4604b86c4843276e8fa548d667dbbd0cb83a3ae14b18b2db6c", "sha256:2013226d17f20468f34feddd6aae4635a55f79626549099354ce641bc7d40262",
"sha256:2efa963bdddb27cb4a0d42545cd137a8d2b883bd181bbc4525b568ef6eca258f", "sha256:24189f23dc66f83b839bd1cce2dfc356020dfc9a8bae03978477b15be61b062e",
"sha256:2f6ac8c87e046dc18c7d1d7f6653a66787a4555085b056fe2d599f1f1a2a2d21", "sha256:27a0f74a298769d9fdc8498fcb4f2beb86f0564bcdb1a37b58cbbe78e55cf8c0",
"sha256:3ae4c7a99e5153496243146a3baf33b9beff714464ca386b5f62daad601d87af", "sha256:28cea5a6392bb43d266782983b5a4216c25544cd7d80be681a155ddcdafd152d",
"sha256:3cfad08f16a9c6611e6143485a93de0e1e13f48cfb90bcad7d5fde1c0cec3d36", "sha256:448de661536d270ce04f2d7dddaa49b2fdba6e3bd8a83212164d4174ff43aa65",
"sha256:4e5175026618c178dfba6188228b845b64131034ab3ba52acaffa8f6c361f805", "sha256:48525aec92b47baed9b3380371ab8ab6e63a5aab317347dfe9e55e02aaad22e8",
"sha256:50979d5efff8d4135d9db293c6cb2c42260e70fb010cbc697b1311a4d7a39ddb", "sha256:5bc8d6bd3b274dd3846597855d96d38d947aedba18776aa998a8d46fabdaed76",
"sha256:5cd187d92b6939617f1168a4fe68f68add749902c010e66fe574c165c742ed88", "sha256:5deb252fd42a77add936b463033a59b8e48eb2eaec2976d76b6878d031933fe4",
"sha256:5cfca124f0ac6707747544c127880893ad72a656e136adc935c8600740b21ff5", "sha256:5f546ac34093c6ce33f6278f7c88f0f147a4849386d3bf3ae193702f4fe31407",
"sha256:5e398652d005a198a7f3c132426b33c6b85d98aa7dc852137a2a3be8890c4072", "sha256:5fdd63e4f50e3538617887e9aee91855368d9fc1dea30da743837b0df7373bc4",
"sha256:67cced7f15654710386e5c10b96608f1ee3d5c94ca1da5a2aad5889793a824c1", "sha256:65b122a993d9c81ea0bfde7689b3365318a88bde952e4dfa1b3a8b4ac05d168b",
"sha256:7306edca1c6f1b5fa0bc9aa645e6ac8393014fa82d0fa180d0ebc990ebe15964", "sha256:71a808334d3f41ef011faa5a5cd8153606df5fc0b56de5b2e89566c8093a0c9a",
"sha256:7cc2c01dfc5a3cbddfa6c13f530ef3b95292f926329929001d45e124342cd6b7", "sha256:920169f0184215eef19294fa86ea49ffd4635dedfdea2b57e45cb4ee85d5ccaf",
"sha256:87edfaf344c9401942883fad030909116aa77b0fa7e6e8e1c5407e14549afe9a", "sha256:93a85495fb13dc484251b4c1fd7a5ac370cd0d812bbfc3b39c1bafefe95275d5",
"sha256:8845125d0b7c57838a10fd8925b0f5f709d0e08568ce587cc862aacce453e3dd", "sha256:a2948c40a7dd46c1c33765718936669dc1f628f134013b02ff5ac6c7ef6942bf",
"sha256:92024447a339400ea00ac228369cd242e988dd775640755fa4ac0c126e49bb74", "sha256:c6c2ccb7af7154673c591189c3687b013122c5a891bb5651eca3db8e6c6c55bd",
"sha256:a86b794e8a56ada65c573183756eac8ac5b8d3d59daf9d5ebd72ecdbb7867a43", "sha256:c96b8a0c019fe29040d520d9257d8c8f122a7343a8307bf8d6d4a43f5c5bfcc8",
"sha256:bb2782a036d9eb6b5a6efcdda0986774bf798beef86a62da86cb73e2a10b423d", "sha256:d42a98e76070a365a1d1c220fcac8aa4ada12ae0db679cb4d910fabefc88b994",
"sha256:be78077064d016bc1b639c2cbcc5be945b47b4261a4f4b7d8923f6c69c5c9457", "sha256:dbeb24514c4acbc78d205f85dd0e800f34062efcc1f4a4857c57e4b4b8712bff",
"sha256:c7cf862aef988b5fbaa17764ad1d21b4831436701c7d2b653156a9497d92c83c", "sha256:e60d0b09f62ae97a94605c3f73fd952395286cf3e3b9e7b97f60b01ddfbbda88",
"sha256:e0626db16705ab9f7fa6c249c017c887baf20738ce7f9129da162bb3075fc1af", "sha256:e64f48c6176e243ad015e995de05af7f22bbe370dbb5b32bd6988438ec873919",
"sha256:f34495079c8d9da05b183f9f7daec2878280c2ad7cc81da686ef0b484cea2ecf", "sha256:e831662208055b006eef68392a768ff83596035ffd6d846786578ba1714ba8f6",
"sha256:fe523fcbd52c05040c7bee370d66fee8373c5972171e4fbc323153433198592d" "sha256:eda5c8b9949ed411ff752b9a01adda31afe7eae1e53e946dbdf9db23865e66c4"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.0.0" "version": "==1.0.1"
}, },
"mypy-extensions": { "mypy-extensions": {
"hashes": [ "hashes": [
@@ -467,11 +459,11 @@
}, },
"platformdirs": { "platformdirs": {
"hashes": [ "hashes": [
"sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490", "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9",
"sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2" "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2.6.2" "version": "==3.0.0"
}, },
"pluggy": { "pluggy": {
"hashes": [ "hashes": [
@@ -483,23 +475,22 @@
}, },
"protobuf": { "protobuf": {
"hashes": [ "hashes": [
"sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30", "sha256:1669cb7524221a8e2d9008d0842453dbefdd0fcdd64d67672f657244867635fb",
"sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b", "sha256:29288813aacaa302afa2381db1d6e0482165737b0afdf2811df5fa99185c457b",
"sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc", "sha256:47d31bdf58222dd296976aa1646c68c6ee80b96d22e0a3c336c9174e253fd35e",
"sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791", "sha256:652d8dfece122a24d98eebfef30e31e455d300efa41999d1182e015984ac5930",
"sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717", "sha256:7c535d126e7dcc714105ab20b418c4fedbd28f8b8afc42b7350b1e317bbbcc71",
"sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec", "sha256:86c3d20428b007537ba6792b475c0853bba7f66b1f60e610d913b77d94b486e4",
"sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7", "sha256:a33a273d21852f911b8bda47f39f4383fe7c061eb1814db2c76c9875c89c2491",
"sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab", "sha256:ab4d043865dd04e6b09386981fe8f80b39a1e46139fb4a3c206229d6b9f36ff6",
"sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2", "sha256:b2fea9dc8e3c0f32c38124790ef16cba2ee0628fe2022a52e435e1117bfef9b1",
"sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5", "sha256:c27f371f0159feb70e6ea52ed7e768b3f3a4c5676c1900a7e51a24740381650e",
"sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1", "sha256:c3325803095fb4c2a48649c321d2fbde59f8fbfcb9bfc7a86df27d112831c571",
"sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462", "sha256:e474b63bab0a2ea32a7b26a4d8eec59e33e709321e5e16fb66e766b61b82a95e",
"sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97", "sha256:e894e9ae603e963f0842498c4cd5d39c6a60f0d7e4c103df50ee939564298658"
"sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.21.12" "version": "==4.22.0"
}, },
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
@@ -519,11 +510,11 @@
}, },
"pylint": { "pylint": {
"hashes": [ "hashes": [
"sha256:bad9d7c36037f6043a1e848a43004dfd5ea5ceb05815d713ba56ca4503a9fe37", "sha256:13b2c805a404a9bf57d002cd5f054ca4d40b0b87542bdaba5e05321ae8262c84",
"sha256:ffe7fa536bb38ba35006a7c8a6d2efbfdd3d95bbf21199cad31f76b1c50aaf30" "sha256:ff22dde9c2128cd257c145cfd51adeff0be7df4d80d669055f24a962b351bbe4"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.16.1" "version": "==2.16.2"
}, },
"pyproject-hooks": { "pyproject-hooks": {
"hashes": [ "hashes": [
@@ -559,11 +550,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:a7687c12b444eaac951ea87a9627c4f904ac757e7abdc5aac32833234af90378", "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330",
"sha256:e261cdf010c11a41cb5cb5f1bf3338a7433832029f559a6a7614bd42a967c300" "sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==67.1.0" "version": "==67.4.0"
}, },
"setuptools-git-versioning": { "setuptools-git-versioning": {
"hashes": [ "hashes": [
@@ -583,19 +574,19 @@
}, },
"types-protobuf": { "types-protobuf": {
"hashes": [ "hashes": [
"sha256:09d39f2c84d0c9c323f44a4c6f1f88fbb1aac0c855f89a3c2746b50c823360cc", "sha256:5f4c0ba5840d66a9f32bcfd153a5d41c503f409223eae4fda876cdd6b50a638a",
"sha256:7e1f8641b013f1500ee3b56ab4596df26b325d61bc0c69c6eb58ec65f4e41ad8" "sha256:6ecaddcc7aed2c636745a17c1411932cdef7a035304d50ffd4140297b6b882e8"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.21.0.4" "version": "==4.21.0.7"
}, },
"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": [

View File

@@ -14,7 +14,7 @@
[![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) <!-- ![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) [![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)--> ![protobuf version](https://img.shields.io/badge/protobuf-4.22.0-informational)-->
<!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) --> <!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
@@ -36,7 +36,8 @@ 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)
@@ -86,10 +87,11 @@ 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))
:heavy_check_mark: Everything is just packed in one executable. :heavy_check_mark: Everything is just packed in one executable.
:heavy_check_mark: No installation needed, neither Python nor any dependencies have to be installed. :heavy_check_mark: No installation needed, neither Python nor any dependencies have to be installed.
@@ -106,6 +108,29 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
:information_source: The executables are not signed. Thus, the operating system may show a warning about download from unknown source. :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: `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`
#### Apple Silicon (ARM)
Currently, there is no build for M1 and M2 Apple Silicon processors due to lack of hardware. However, the binary executable should be runnable by Rosetta2 emulation.
```
arch -x86_64 extract_otp_secrets_X.Y.Z_macos_x86_64
```
:warning: This command is untested due to lack of hardware.
## Usage ## Usage
### Capture QR codes from camera (🆕 since version 2.0) ### Capture QR codes from camera (🆕 since version 2.0)
@@ -190,7 +215,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)
@@ -212,7 +237,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.
@@ -226,8 +251,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)
@@ -327,7 +353,9 @@ python extract_otp_secrets.py = < example_export.png</pre>
* 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_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 (barely tested, [libzbar](#installation-of-optional-shared-system-libraries-recommended) needs to be installed additionally if needed) * 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
@@ -603,7 +631,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:
@@ -615,7 +643,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:
@@ -660,11 +688,13 @@ 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
-E Do not build exe
-D Do not build docker -D Do not build docker
-V Do not run pipenv
-G Do not start extract_otp_secrets.py in GUI mode -G Do not 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
@@ -676,7 +706,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.0 (https://github.com/protocolbuffers/protobuf/releases/tag/v22.0).
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.

139
build.sh
View File

@@ -78,6 +78,8 @@ ignore_version_check=true
clean=false clean=false
clean_flag="" clean_flag=""
build_docker=true build_docker=true
build_exe=true
run_pipenv=true
run_gui=true run_gui=true
generate_result_files=false generate_result_files=false
PYTHONHASHSEED=31 PYTHONHASHSEED=31
@@ -92,11 +94,13 @@ 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 "-E Do not build exe"
echo "-D Do not build docker" echo "-D Do not build docker"
echo "-V Do not run pipenv"
echo "-G Do not start extract_otp_secrets.py in GUI mode" echo "-G Do not 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) -a)
@@ -107,10 +111,18 @@ while test $# -gt 0; do
ignore_version_check=false ignore_version_check=false
shift shift
;; ;;
-E)
build_exe=false
shift
;;
-D) -D)
build_docker=false build_docker=false
shift shift
;; ;;
-V)
run_pipenv=false
shift
;;
-G) -G)
run_gui=false run_gui=false
shift shift
@@ -320,21 +332,23 @@ eval "$cmd"
# Pipenv # Pipenv
cmd="$PIP install -U pipenv" if $run_pipenv; then
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi cmd="$PIP install -U pipenv"
eval "$cmd" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
$PIPENV --version $PIPENV --version
cmd="$PIPENV update && $PIPENV --rm && $PIPENV install" cmd="$PIPENV update && $PIPENV --rm && $PIPENV install"
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 $PIPENV run python --version
cmd="$PIPENV run pytest --cov=extract_otp_secrets_test tests/" cmd="$PIPENV run pytest --cov=extract_otp_secrets_test tests/"
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
# Build wheel # Build wheel
@@ -344,18 +358,20 @@ eval "$cmd"
# Build executable # Build executable
cmd="LOCAL_GLIBC_VERSION=$(ldd --version | sed '1!d' | sed -E 's/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/')" if $build_exe; then
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi cmd="LOCAL_GLIBC_VERSION=$(ldd --version | sed '1!d' | sed -E 's/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/')"
eval "$cmd" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
echo "local glibc: $LOCAL_GLIBC_VERSION" 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" 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 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="dist/extract_otp_secrets -h" cmd="dist/extract_otp_secrets -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"
fi
# Generate README.md TOC # Generate README.md TOC
@@ -374,7 +390,7 @@ if $build_docker; then
# Build docker # Build docker
# Build Dockerfile_only_txt (Alpine) # Build Dockerfile_only_txt (Alpine)
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" 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"
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"
@@ -391,7 +407,7 @@ if $build_docker; then
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"
@@ -412,7 +428,7 @@ if $build_docker; then
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 --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"
@@ -437,48 +453,55 @@ if $build_docker; then
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"
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 pip && 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 extract_otp_secrets_linux_x86_64_bullseye --distpath /files/dist/ /files/src/extract_otp_secrets.py'" if $build_exe; then
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";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 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'"
eval "$cmd" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets_linux_x86_64_bullseye -h" 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"
# Build executable from Docker buster # 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/"') 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" echo "Bullseye glibc: $BUSTER_GLIBC_VERSION"
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 pip && 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 extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'" 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 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'"
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" cmd="dist/extract_otp_secrets_linux_x86_64 -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"
# create Windows file_version_info.txt # 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))) YEARS='2020-2023' envsubst < file_version_info_template.txt > build/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 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
# build linux/arm64 # create macOS extract_otp_secrets_macos.spec from extract_otp_secrets_macos_template.spec
# Build extract_otp_secrets (Debian Buster) cmd="VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2023' envsubst < installer/extract_otp_secrets_macos_template.spec > build/extract_otp_secrets_macos.spec"
cmd="docker buildx build --platform=linux/arm64 . -t extract_otp_secrets:buster --pull --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 --pull always --rm --privileged multiarch/qemu-user-static --reset -p yes" # build linux/arm64
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi # Build extract_otp_secrets (Debian Buster)
eval "$cmd" cmd="docker buildx build --platform=linux/arm64 . -t extract_otp_secrets:buster --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
eval "$cmd"
cmd="docker run --platform linux/arm64 --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 --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'" 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 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" 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 -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\"" cmd="docker run --platform linux/arm64 --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 --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 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" 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 -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
# Run GUI from Docker # Run GUI from Docker
if $run_gui; then if $run_gui; then

View File

@@ -16,7 +16,7 @@ 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 \

View File

@@ -3,7 +3,8 @@ 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)

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

@@ -37,8 +37,8 @@ classifiers = [
] ]
dependencies = [ dependencies = [
"colorama>=0.4.6", "colorama>=0.4.6",
"opencv-contrib-python; sys_platform != 'darwin'", "opencv-contrib-python",
"opencv-contrib-python<=4.7.0; sys_platform == 'darwin'", # "opencv-contrib-python<=4.7.0; sys_platform == 'darwin'",
"Pillow", "Pillow",
"protobuf", "protobuf",
"pyzbar", "pyzbar",

View File

@@ -1,7 +1,7 @@
colorama>=0.4.6 colorama>=0.4.6
importlib_metadata; python_version<='3.7' importlib_metadata; python_version<='3.7'
opencv-contrib-python; sys_platform != 'darwin' opencv-contrib-python
opencv-contrib-python<=4.7.0; sys_platform == 'darwin' # opencv-contrib-python<=4.7.0; sys_platform == 'darwin'
Pillow Pillow
protobuf protobuf
pyzbar pyzbar

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
@@ -200,6 +200,7 @@ def main(sys_args: list[str]) -> None:
write_csv(args.csv, otps) write_csv(args.csv, otps)
write_keepass_csv(args.keepass, otps) write_keepass_csv(args.keepass, otps)
write_json(args.json, 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
@@ -262,9 +263,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:
@@ -297,8 +298,9 @@ b) image file containing a QR code or = for stdin for an image containing a QR c
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')
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to directory', metavar=('DIR'))
if cv2_available: 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'))
if not zbar_available: if not zbar_available:
@@ -314,7 +316,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
@@ -391,7 +393,7 @@ def extract_otps_from_camera(args: Args) -> Otps:
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, "Press 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 to save as csv/json/keepass file", 2, TextPosition.LEFT, FONT_COLOR, None) 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)
@@ -486,6 +488,18 @@ def cv2_handle_pressed_keys(qr_mode: QRMode, otps: Otps) -> Tuple[bool, QRMode]:
tk_root.update() tk_root.update()
if len(file_name) > 0: if len(file_name) > 0:
write_keepass_csv(file_name, otps) 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_valid_qr_mode(qr_mode, zbar_available) 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}")
@@ -655,28 +669,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')
@@ -684,10 +697,20 @@ 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_txt(file: str, otps: Otps, write_qr: bool = False) -> None:
if file and len(file) > 0 and len(otps) > 0:
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: def write_csv(file: str, otps: Otps) -> None:

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

@@ -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'])
@@ -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),