pax_global_header00006660000000000000000000000064147616610510014521gustar00rootroot0000000000000052 comment=5292e37c791c8302f61dbffd43f62d13607fe324 miaucl-cookidoo-api-5292e37/000077500000000000000000000000001476166105100156305ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/.github/000077500000000000000000000000001476166105100171705ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/.github/FUNDING.yml000066400000000000000000000001361476166105100210050ustar00rootroot00000000000000github: miaucl patreon: miaucl buy_me_a_coffee: miaucl custom: https://paypal.me/sponsormiauclmiaucl-cookidoo-api-5292e37/.github/dependabot.yml000066400000000000000000000003151476166105100220170ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" miaucl-cookidoo-api-5292e37/.github/labels.yml000066400000000000000000000050451476166105100211610ustar00rootroot00000000000000--- # Labels names are important as they are used by Release Drafter to decide # regarding where to record them in changelog or if to skip them. # # The repository labels will be automatically configured using this file and # the GitHub Action https://github.com/marketplace/actions/github-labeler. - name: ':boom: breaking change' from_name: breaking description: Breaking Changes color: bfd4f2 - name: ':ghost: bug' from_name: bug description: Something isn't working color: d73a4a - name: ':building_construction: build' from_name: build description: Build System and Dependencies color: bfdadc - name: ':construction_worker_woman: ci' from_name: ci description: Continuous Integration color: 4a97d6 - name: ':recycle: dependencies' from_name: dependencies description: Pull requests that update a dependency file color: 0366d6 - name: ':book: documentation' from_name: documentation description: Improvements or additions to documentation color: 0075ca - name: ':roll_eyes: duplicate' from_name: duplicate description: This issue or pull request already exists color: cfd3d7 - name: ':rocket: feature' from_name: enhancement description: New feature or request color: a2eeef - name: ':clapper: github_actions' from_name: github_actions description: Pull requests that update Github_actions code color: '000000' - name: ':hatching_chick: good first issue' from_name: good first issue description: Good for newcomers color: 7057ff - name: ':pray: help wanted' from_name: help wanted description: Extra attention is needed color: '008672' - name: ':no_entry_sign: invalid' from_name: invalid description: This doesn't seem right color: e4e669 - name: ':racing_car: performance' from_name: performance description: Performance color: '016175' - name: ':snake: python' from_name: python description: Pull requests that update Python code color: 2b67c6 - name: ':question: question' from_name: question description: Further information is requested color: d876e3 - name: ':sparkles: code quality' from_name: code quality description: Code quality improvements color: ef67c4 - name: ':file_cabinet: deprecation' from_name: deprecation description: Removals and Deprecations color: 9ae7ea - name: ':nail_care: style' from_name: style description: Style color: c120e5 - name: ':test_tube: testing' from_name: testing description: Pull request that adds tests color: b1fc6f - name: ':woman_shrugging: wontfix' from_name: wontfix description: This will not be worked on color: ffffff miaucl-cookidoo-api-5292e37/.github/release-drafter.yml000066400000000000000000000035261476166105100227660ustar00rootroot00000000000000name-template: '$RESOLVED_VERSION' tag-template: '$RESOLVED_VERSION' categories: - title: '💥 Breaking changes' labels: - ':boom: breaking change' - title: '🚀 New Features' labels: - ':rocket: feature' - title: '👻 Bug Fixes' labels: - ':ghost: bug' - title: '🗄️ Deprecations' labels: - ':file_cabinet: deprecation' - title: '📃 Documentation' labels: - ':book: documentation' - title: '🧰 Maintenance' labels: - ':building_construction: build' - ':construction_worker_woman: ci' - ':clapper: github_actions' - title: '🔬 Other updates' labels: - ':nail_care: style' - ':test_tube: testing' - ':racing_car: performance' - ':sparkles: code quality' - title: '🧩 Dependency Updates' labels: - ':recycle: dependencies' exclude-labels: - ':arrow_up: bump' autolabeler: - label: ':rocket: feature' title: - '/adds/i' - '/add method/i' - label: ':ghost: bug' title: - '/fix/i' - label: ':sparkles: code quality' title: - '/Refactor/i' - label: ':test_tube: testing' files: - 'test_*' - 'conftest.py' - label: ':book: documentation' title: - '/docs:/i' files: - '*.md' - 'mkdocs.yml' - label: ':construction_worker_woman: ci' files: - '.github/*' - label: ':recycle: dependencies' title: - '/bump/i' - label: ':file_cabinet: deprecation' title: - '/Deprecate/i' change-template: '- $TITLE @$AUTHOR (#$NUMBER)' change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. version-resolver: major: labels: - 'breaking' minor: labels: - 'feature' default: patch template: | ## What's Changed $CHANGES Contributors: $CONTRIBUTORS miaucl-cookidoo-api-5292e37/.github/workflows/000077500000000000000000000000001476166105100212255ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/.github/workflows/documentation.yml000066400000000000000000000020741476166105100246240ustar00rootroot00000000000000name: Build documentation on: push: branches: - master # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow one concurrent deployment concurrency: group: "pages" cancel-in-progress: true # Default to bash defaults: run: shell: bash jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements_dev.txt - name: Build run: mkdocs build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: ./site deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 miaucl-cookidoo-api-5292e37/.github/workflows/draft.yml000066400000000000000000000005621476166105100230530ustar00rootroot00000000000000name: Release Drafter on: push: branches: - main - master jobs: update-draft: runs-on: ubuntu-latest permissions: contents: write steps: # Drafts your next Release notes as Pull Requests are merged into "main" - uses: release-drafter/release-drafter@v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} miaucl-cookidoo-api-5292e37/.github/workflows/labeler.yml000066400000000000000000000006411476166105100233570ustar00rootroot00000000000000name: Labeler on: push: branches: - main - master permissions: actions: read contents: read security-events: write pull-requests: write jobs: labeler: runs-on: ubuntu-latest steps: - name: Check out the repository uses: actions/checkout@v4 - name: Run Labeler uses: crazy-max/ghaction-github-labeler@v5.2.0 with: skip-delete: true miaucl-cookidoo-api-5292e37/.github/workflows/markdownlint.yml000066400000000000000000000005661476166105100244700ustar00rootroot00000000000000name: Markdownlint on: [ push, pull_request ] jobs: markdownlint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: DavidAnson/markdownlint-cli2-action@v19 with: config: '.markdownlint.yaml' globs: | **/*.md !docs/changelog.md !docs/index.md !docs/license.mdmiaucl-cookidoo-api-5292e37/.github/workflows/mypy.yaml000066400000000000000000000006031476166105100231060ustar00rootroot00000000000000name: Mypy on: [ push, pull_request ] env: DEFAULT_PYTHON: "3.12" jobs: mypy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - run: pip install -r requirements_dev.txt - run: mypy cookidoo_api tests smoke_test miaucl-cookidoo-api-5292e37/.github/workflows/publish-to-pypi.yml000066400000000000000000000122751476166105100250240ustar00rootroot00000000000000name: Publish Python 🐍 distribution 📦 to PyPI on: 'push' env: DEFAULT_PYTHON: "3.12" jobs: build: name: Build distribution 📦 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.DEFAULT_PYTHON }} check-latest: true - name: Install pypa/build run: >- python3 -m pip install -r requirements.txt build --user - name: Build a binary wheel and a source tarball run: python3 -m build - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ check_version: name: Check the version if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes needs: - build runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Extract tag version id: extract_tag run: | TAG_VERSION=${GITHUB_REF#refs/tags/} echo "TAG_VERSION=$TAG_VERSION" >> $GITHUB_ENV - name: Check tag in package run: | if ! grep -q "__version__ = \"$TAG_VERSION\"" ./cookidoo_api/__init__.py; then echo "Error: Tag version $TAG_VERSION not found in the package." exit 1 else echo "Success: Tag version $TAG_VERSION found in package." fi - name: Check if version type matches branch type run: | branch=$(git branch -r --contains ${{ github.ref }} --format "%(refname:lstrip=3)" | grep -v '^HEAD$') echo "Branch: $branch" version=${GITHUB_REF#refs/tags/} echo "Version from tag: $version" # Check if the version is a prerelease if [[ "$version" =~ [a-zA-Z] ]]; then echo "Prerelease version detected: $version" prerelease=true else echo "Final release version detected: $version" prerelease=false fi # Fail if it's a prerelease and on the master branch if [[ "$branch" == "master" && "$prerelease" == "true" ]]; then echo "Error: Prerelease versions are not allowed on the master branch." exit 1 fi # Fail if it's a final release on a non-master branch if [[ "$branch" != "master" && "$prerelease" == "false" ]]; then echo "Error: Final releases are not allowed on non-master branches." exit 1 fi publish-to-pypi: name: >- Publish Python 🐍 distribution 📦 to PyPI needs: check_version runs-on: ubuntu-latest environment: name: pypi url: https://pypi.org/p/cookidoo-api # Replace with your PyPI project name permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 github-release: name: >- Sign the Python 🐍 distribution 📦 with Sigstore and upload them to GitHub Release needs: - publish-to-pypi runs-on: ubuntu-latest permissions: contents: write # IMPORTANT: mandatory for making GitHub Releases id-token: write # IMPORTANT: mandatory for sigstore steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - name: Get version id: version run: | TAG_VERSION=${GITHUB_REF#refs/tags/} echo "version=$TAG_VERSION" >> $GITHUB_OUTPUT - name: Check if version is prerelease id: check_prerelease run: | TAG_VERSION="${{ steps.version.outputs.version }}" if [[ "$TAG_VERSION" =~ [a-zA-Z] ]]; then echo "prerelease=true" >> $GITHUB_OUTPUT else echo "prerelease=false" >> $GITHUB_OUTPUT fi - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Sign the dists with Sigstore uses: sigstore/gh-action-sigstore-python@v3.0.0 with: inputs: >- ./dist/*.tar.gz ./dist/*.whl - name: Release uses: softprops/action-gh-release@v2 if: ${{ steps.check_prerelease.outputs.prerelease }} with: tag_name: ${{ github.ref }} name: ${{ steps.version.outputs.version }} generate_release_notes: true prerelease: ${{ steps.check_prerelease.outputs.prerelease }} # Mark as prerelease if necessary env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload artifact signatures to GitHub Release env: GITHUB_TOKEN: ${{ github.token }} # Upload to GitHub Release using the `gh` CLI. # `dist/` contains the built packages, and the # sigstore-produced signatures and certificates. run: >- gh release upload '${{ github.ref_name }}' dist/** --repo '${{ github.repository }}'miaucl-cookidoo-api-5292e37/.github/workflows/ruff.yml000066400000000000000000000002451476166105100227130ustar00rootroot00000000000000name: Ruff on: [ push, pull_request ] jobs: ruff: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: chartboost/ruff-action@v1miaucl-cookidoo-api-5292e37/.github/workflows/smoke-test-pr.yml000066400000000000000000000056031476166105100244660ustar00rootroot00000000000000name: Smoke test (PR) on: pull_request_target: types: - labeled env: python-version: "3.12" concurrency: group: "smoke-test" # Make sure, to not run multiple smoke tests at the same time cancel-in-progress: false jobs: smoke_test_pr: runs-on: ubuntu-latest if: ${{ github.event.label.name == 'run smoke test' }} strategy: matrix: country: - ch - de - pl - ma - ie steps: - name: Get User Permission id: checkAccess uses: actions-cool/check-user-permission@v2 with: require: read username: ${{ github.triggering_actor }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Check User Permission if: steps.checkAccess.outputs.require-result == 'false' run: | echo "${{ github.triggering_actor }} does not have permissions on this repo." echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}" echo "Job originally triggered by ${{ github.actor }}" exit 1 - name: Checkout current code uses: actions/checkout@v4 - name: Set up Python ${{ env.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ env.python-version }} - name: Check secret access run: | if [[ "x${{ secrets.PASSWORD }}" != "x" ]]; then echo "Access to secrets" else echo "No access to secrets" exit 1 fi - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install -r requirements_test.txt - name: Prepare empty token run: touch .token - name: Prepare cookies with pytest run: pytest smoke_test/test_1_setup_token.py -v -x env: COUNTRY: ${{ matrix.country }} EMAIL_CH: ${{ vars.EMAIL_CH }} EMAIL_DE: ${{ vars.EMAIL_DE }} EMAIL_PL: ${{ vars.EMAIL_PL }} EMAIL_MA: ${{ vars.EMAIL_MA }} EMAIL_IE: ${{ vars.EMAIL_IE }} PASSWORD: ${{ secrets.PASSWORD }} # !!!!!!!!!!!!!!!!!!!! # From here on, we run new code which does not have access to the password, only the token # !!!!!!!!!!!!!!!!!!!! - name: Checkout new code uses: actions/checkout@v4 with: ref: "${{ github.event.pull_request.merge_commit_sha }}" clean: false - name: Test with pytest run: pytest smoke_test/test_2_methods.py -v -x env: COUNTRY: ${{ matrix.country }} EMAIL_CH: ${{ vars.EMAIL_CH }} EMAIL_DE: ${{ vars.EMAIL_DE }} EMAIL_PL: ${{ vars.EMAIL_PL }} EMAIL_MA: ${{ vars.EMAIL_MA }} EMAIL_IE: ${{ vars.EMAIL_IE }} PASSWORD: miaucl-cookidoo-api-5292e37/.github/workflows/smoke-test.yaml000066400000000000000000000042671476166105100242150ustar00rootroot00000000000000name: Smoke test on: push: branches: - master schedule: - cron: '0 23 * * 0' # Run every Sunday at 23:00 PM workflow_dispatch: # Allows manual triggering env: python-version: "3.12" concurrency: group: "smoke-test" # Make sure, to not run multiple smoke tests at the same time cancel-in-progress: false jobs: smoke_test: runs-on: ubuntu-latest strategy: matrix: country: - ch - de - pl - ma - ie steps: - name: Get User Permission id: checkAccess uses: actions-cool/check-user-permission@v2 with: require: read username: ${{ github.triggering_actor }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Check User Permission if: steps.checkAccess.outputs.require-result == 'false' run: | echo "${{ github.triggering_actor }} does not have permissions on this repo." echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}" echo "Job originally triggered by ${{ github.actor }}" exit 1 - name: Checkout code uses: actions/checkout@v4 - name: Set up Python ${{ env.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ env.python-version }} - name: Check secret access run: | if [[ "x${{ secrets.PASSWORD }}" != "x" ]]; then echo "Access to secrets" else echo "No access to secrets" exit 1 fi - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install -r requirements_test.txt - name: Prepare empty token run: touch .token - name: Test with pytest run: pytest smoke_test -v -x env: COUNTRY: ${{ matrix.country }} EMAIL_CH: ${{ vars.EMAIL_CH }} EMAIL_DE: ${{ vars.EMAIL_DE }} EMAIL_PL: ${{ vars.EMAIL_PL }} EMAIL_MA: ${{ vars.EMAIL_MA }} EMAIL_IE: ${{ vars.EMAIL_IE }} PASSWORD: ${{ secrets.PASSWORD }} miaucl-cookidoo-api-5292e37/.github/workflows/unit-tests.yaml000066400000000000000000000016421476166105100242330ustar00rootroot00000000000000name: Unit tests on: [push, pull_request] jobs: unit_tests: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install -r requirements_test.txt - name: Test with pytest run: pytest -v --cov-config=pyproject.toml --cov=cookidoo_api --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} verbose: truemiaucl-cookidoo-api-5292e37/.gitignore000066400000000000000000000002441476166105100176200ustar00rootroot00000000000000dist/ cookidoo_api.egg-info/ catalog/ test/ .venv __pycache__ .ruff_cache .mypy_cache .pytest_cache .env* .coverage build/ site out scratch* .token miaucl-cookidoo-api-5292e37/.markdownlint.yaml000066400000000000000000000001331476166105100213000ustar00rootroot00000000000000# Default state for all rules default: true # MD013/line-length - Line length MD013: falsemiaucl-cookidoo-api-5292e37/.markdownlintignore000066400000000000000000000000571476166105100215500ustar00rootroot00000000000000docs/changelog.md docs/index.md docs/license.mdmiaucl-cookidoo-api-5292e37/.pre-commit-config.yaml000066400000000000000000000016371476166105100221200ustar00rootroot00000000000000repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. rev: v0.2.1 hooks: # Run the linter. - id: ruff args: [ --fix ] # Run the formatter. - id: ruff-format - repo: local hooks: # Run mypy through our wrapper script in order to get the possible # pyenv and/or virtualenv activated; it may not have been e.g. if # committing from a GUI tool that was not launched from an activated # shell. - id: mypy name: mypy entry: scripts/run-in-env.sh mypy --strict language: script types: [python] require_serial: true files: ^(cookidoo_api|tests|smoke_test)/.+\.py$ - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.41.0 hooks: - id: markdownlint args: ["--disable=MD013"] - id: markdownlint-fix args: ["--disable=MD013", "--fix"] miaucl-cookidoo-api-5292e37/.vscode/000077500000000000000000000000001476166105100171715ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/.vscode/settings.json000066400000000000000000000013021476166105100217200ustar00rootroot00000000000000{ "cSpell.words": [ "aiohttp", "aioresponses", "asyncio", "capsolver", "Cookidoo", "CUSTOMLIST", "customlists", "dotenv", "errmsg", "gaierror", "headful", "jlesage", "kasmvnc", "levelname", "MANAGEDLIST", "managedlists", "miaucl", "mypy", "networkidle", "novnc", "recaptcha", "swiftshader", "venv", "Vorwerk" ], "[python]": { "editor.formatOnSave": true, "editor.defaultFormatter": "charliermarsh.ruff", "editor.codeActionsOnSave": { "source.fixAll": "explicit" } }, "python.testing.pytestArgs": [ "-v", "--color=yes" ], "python.analysis.typeCheckingMode": "basic" }miaucl-cookidoo-api-5292e37/.vscode/tasks.json000066400000000000000000000045741476166105100212230ustar00rootroot00000000000000{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "Python: Build cookidoo-api package", "type": "shell", "command": "scripts/run-in-env.sh pip install -e .", "problemMatcher": [], "group": { "kind": "build" }, "presentation": { "focus": true, "reveal": "always" }, "options": { "cwd": "${workspaceFolder}" } }, { "label": "mkdocs: serve", "type": "shell", "command": "mkdocs serve", "problemMatcher": [], "presentation": { "focus": true, "reveal": "always" }, "options": { "cwd": "${workspaceFolder}" } }, { "label": "pre-commit: run all files", "type": "shell", "command": "pre-commit run --all-files", "problemMatcher": [], "group": { "kind": "build" }, "presentation": { "focus": true, "reveal": "always" }, "options": { "cwd": "${workspaceFolder}" } }, { "label": "Pytest: Unit tests", "type": "shell", "command": "${command:python.interpreterPath} -m pytest --cov-config=pyproject.toml --cov=cookidoo_api -v --color=yes", "problemMatcher": [], "group": { "kind": "test", "isDefault": true }, "presentation": { "focus": true, "reveal": "always" }, "options": { "cwd": "${workspaceFolder}" } }, { "label": "Pytest: Smoke test", "type": "shell", "command": "${command:python.interpreterPath} -m pytest smoke_test -v --color=yes -x", "problemMatcher": [], "group": { "kind": "test", "isDefault": true }, "presentation": { "focus": true, "reveal": "always" }, "options": { "cwd": "${workspaceFolder}" } }, { "label": "Python: Process cookidoo localization extract", "type": "shell", "command": "scripts/run-in-env.sh python scripts/process-localization-extract.py", "problemMatcher": [], "group": { "kind": "build" }, "presentation": { "focus": true, "reveal": "always" }, "options": { "cwd": "${workspaceFolder}" } } ] }miaucl-cookidoo-api-5292e37/CHANGELOG.md000066400000000000000000000032011476166105100174350ustar00rootroot00000000000000# CHANGELOG ## DEPRECATED This changelog is no longer maintained and will not be updated in future releases. Please refer to the [release notes](https://github.com/miaucl/cookidoo-api/releases/latest) on GitHub for the latest changes. ## 0.12.2 - Fix .co.uk countries which do share a common domain ## 0.12.1 - Add support for recipes which feature ranges for quantity in addition to fixed values ## 0.12.0 - Use sub from jwt as username no longer available at login stage ## 0.11.2 - Fix exotic countries which do share a common domain ## 0.11.1 - Extend smoke tests to multiple accounts with different countries ## 0.11.0 - Align token endpoint with new way of auth for cookidoo app ## 0.10.0 - Use async lib aiofiles to read files from file system ## 0.9.1 - Remove unnecessary python-dotenv from prod requirements ## 0.9.0 - Migrate from TypedDict to dataclass ## 0.8.0 - add collections for managed and custom lists - add and remove recipes to custom lists - add calendar/my week with planned recipes ## 0.7.0 - add a method to get the recipe details - rename the ingredients to ingredient_items to have a logical group of items for the shopping list - pure ingredients are only a part of the recipe now ## 0.6.0 - add a method to get the recipes on a shopping list ## 0.5.0 - add localization getter for the Cookidoo instance ## 0.4.0 - switch from web automation to proper API (based on Android App) ## 0.3.0 - add check method to quickly verify browser functionality ## 0.2.0 - add method to add items when in free account - auto-close feedback modal when in free account ## 0.1.1 - fix build ## 0.1.0 Initial commit. miaucl-cookidoo-api-5292e37/LICENSE000066400000000000000000000021401476166105100166320ustar00rootroot00000000000000MIT License Copyright (c) 2024 Cyrill Raccaud cyrill.raccaud+pypi@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.miaucl-cookidoo-api-5292e37/README.md000066400000000000000000000125041476166105100171110ustar00rootroot00000000000000# Cookidoo API [![PyPI version](https://badge.fury.io/py/cookidoo-api.svg)](https://pypi.org/p/cookidoo-api) An unofficial python package to access Cookidoo. [![Unit tests](https://github.com/miaucl/cookidoo-api/actions/workflows/unit-tests.yaml/badge.svg)](https://github.com/miaucl/cookidoo-api/actions/workflows/unit-tests.yaml) [![Smoke test](https://github.com/miaucl/cookidoo-api/actions/workflows/smoke-test.yaml/badge.svg)](https://github.com/miaucl/cookidoo-api/actions/workflows/smoke-test.yaml) [![codecov](https://codecov.io/gh/miaucl/cookidoo-api/graph/badge.svg?token=743ZRO8FRT)](https://codecov.io/gh/miaucl/cookidoo-api) [![Ruff](https://github.com/miaucl/cookidoo-api/actions/workflows/ruff.yml/badge.svg)](https://github.com/miaucl/cookidoo-api/actions/workflows/ruff.yml) [![Mypy](https://github.com/miaucl/cookidoo-api/actions/workflows/mypy.yaml/badge.svg)](https://github.com/miaucl/cookidoo-api/actions/workflows/mypy.yaml) [![Markdownlint](https://github.com/miaucl/cookidoo-api/actions/workflows/markdownlint.yml/badge.svg)](https://github.com/miaucl/cookidoo-api/actions/workflows/markdownlint.yml) [![GitHub](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/miaucl) [![Patreon](https://img.shields.io/badge/Patreon-F96854?style=for-the-badge&logo=patreon&logoColor=white)](https://patreon.com/miaucl) [![BuyMeACoffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/miaucl) [![PayPal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://paypal.me/sponsormiaucl) ## Disclaimer The developers of this module are in no way endorsed by or affiliated with Cookidoo or Vorwerk, or any associated subsidiaries, logos or trademarks. ## Installation `pip install cookidoo-api` ## Documentation See below for usage examples. ## Usage Example The API is based on the `aiohttp` library. Make sure to have stored your credentials in the top-level file `.env` as such, to loaded by `dotenv`. Alternatively, provide the environment variables by any other `dotenv` compatible means. ```text EMAIL=your@mail.com PASSWORD=password ``` Run the [example script](https://github.com/miaucl/cookidoo-api/blob/master/example.py) and have a look at the inline comments for more explanation. ## Exceptions In case something goes wrong during a request, several [exceptions](https://github.com/miaucl/cookidoo/blob/master/cookidoo_api/exceptions.py) can be thrown, all inheriting from `CookidooException`. ### Another asyncio event loop is With the async calls, you might encounter an error that another asyncio event loop is already running on the same thread. This is expected behavior according to the asyncio.run() [documentation](https://docs.python.org/3/library/asyncio-runner.html#asyncio.run). You cannot use more than one aiohttp session per thread, reuse the existing one! ### Exception ignored: RuntimeError: Event loop is closed Due to a known issue in some versions of aiohttp when using Windows, you might encounter a similar error to this: ```python Exception ignored in: Traceback (most recent call last): File "C:\...\py38\lib\asyncio\proactor_events.py", line 116, in __del__ self.close() File "C:\...\py38\lib\asyncio\proactor_events.py", line 108, in close self._loop.call_soon(self._call_connection_lost, None) File "C:\...\py38\lib\asyncio\base_events.py", line 719, in call_soon self._check_closed() File "C:\...\py38\lib\asyncio\base_events.py", line 508, in _check_closed raise RuntimeError('Event loop is closed') RuntimeError: Event loop is closed ``` You can fix this according to [this](https://stackoverflow.com/questions/68123296/asyncio-throws-runtime-error-with-exception-ignored) StackOverflow answer by adding the following line of code before executing the library: ```python asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) ``` ## Dev Setup the dev environment using VSCode, it is highly recommended. ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements_dev.txt ``` Install [pre-commit](https://pre-commit.com) ```bash pre-commit install # Run the commit hooks manually pre-commit run --all-files ``` Following VSCode integrations may be helpful: - [ruff](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff) - [mypy](https://marketplace.visualstudio.com/items?itemName=matangover.mypy) - [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) ### Raw API Requests The raw requests intercepted between the Cookidoo Android App and the backend can be found here `./docs/raw-api-requests`. They have been used to reconstruct the API which is implemented in this library. ### Releasing A _final version_ can only be released from the `master` branch. To pass the gates of the `publish` workflow, the version must match in both the `tag` and `cookidoo_api/__init__.py`. release a prerelease version, it must be done from a feature branch (**not** `master`). Prerelease versions are explicitly marked as such on the GitHub release page. miaucl-cookidoo-api-5292e37/cookidoo_api/000077500000000000000000000000001476166105100202675ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/cookidoo_api/__init__.py000066400000000000000000000031161476166105100224010ustar00rootroot00000000000000"""Cookidoo API package.""" __version__ = "0.13.0" from .cookidoo import Cookidoo from .exceptions import ( CookidooAuthException, CookidooConfigException, CookidooException, CookidooParseException, CookidooRequestException, CookidooResponseException, CookidooUnavailableException, ) from .helpers import get_country_options, get_language_options, get_localization_options from .types import ( CookidooAdditionalItem, CookidooAuthResponse, CookidooCategory, CookidooChapter, CookidooChapterRecipe, CookidooCollection, CookidooConfig, CookidooIngredient, CookidooIngredientItem, CookidooItem, CookidooLocalizationConfig, CookidooRecipeCollection, CookidooShoppingRecipe, CookidooShoppingRecipeDetails, CookidooSubscription, CookidooUserInfo, ) __all__ = [ "Cookidoo", "get_country_options", "get_language_options", "get_localization_options", "CookidooLocalizationConfig", "CookidooConfig", "CookidooAuthResponse", "CookidooUserInfo", "CookidooSubscription", "CookidooItem", "CookidooAdditionalItem", "CookidooIngredientItem", "CookidooShoppingRecipe", "CookidooShoppingRecipeDetails", "CookidooCategory", "CookidooChapter", "CookidooChapterRecipe", "CookidooRecipeCollection", "CookidooIngredient", "CookidooCollection", "CookidooException", "CookidooConfigException", "CookidooAuthException", "CookidooParseException", "CookidooRequestException", "CookidooResponseException", "CookidooUnavailableException", ] miaucl-cookidoo-api-5292e37/cookidoo_api/const.py000066400000000000000000000055311476166105100217730ustar00rootroot00000000000000"""Constants for Cookidoo API.""" from typing import Final COOKIDOO_CLIENT_ID: Final = "kupferwerk-client-nwot" COOKIDOO_CLIENT_SECRET: Final = "Ls50ON1woySqs1dCdJge" COOKIDOO_AUTHORIZATION_HEADER: Final = ( "Basic a3VwZmVyd2Vyay1jbGllbnQtbndvdDpMczUwT04xd295U3FzMWRDZEpnZQ==" ) # "$COOKIDOO_CLIENT_ID:$COOKIDOO_CLIENT_SECRET" DEFAULT_TOKEN_HEADERS: Final = { "ACCEPT": "application/json", "CONTENT-TYPE": "application/x-www-form-urlencoded", "AUTHORIZATION": COOKIDOO_AUTHORIZATION_HEADER, } DEFAULT_API_HEADERS: Final = { "ACCEPT": "application/json", } AUTHORIZATION_HEADER: Final = "{type} {access_token}" COOKIE_HEADER: Final = "v-token={access_token}" INTERNATIONAL_COUNTRY_CODE: Final = "xp" CO_UK_COUNTRY_CODE: Final = "gb" API_ENDPOINT: Final = "https://{country_code}.tmmobile.vorwerk-digital.com" TOKEN_PATH: Final = "ciam/auth/token" RECIPE_PATH: Final = "recipes/recipe/{language}/{id}" SHOPPING_LIST_RECIPES_PATH: Final = "shopping/{language}" INGREDIENT_ITEMS_PATH: Final = "shopping/{language}" EDIT_OWNERSHIP_INGREDIENT_ITEMS_PATH: Final = ( "shopping/{language}/owned-ingredients/ownership/edit" ) ADD_INGREDIENT_ITEMS_FOR_RECIPES_PATH: Final = "shopping/{language}/recipes/add" REMOVE_INGREDIENT_ITEMS_FOR_RECIPES_PATH: Final = "shopping/{language}/recipes/remove" ADDITIONAL_ITEMS_PATH: Final = "shopping/{language}" ADD_ADDITIONAL_ITEMS_PATH: Final = "shopping/{language}/additional-items/add" EDIT_ADDITIONAL_ITEMS_PATH: Final = "shopping/{language}/additional-items/edit" EDIT_OWNERSHIP_ADDITIONAL_ITEMS_PATH: Final = ( "shopping/{language}/additional-items/ownership/edit" ) REMOVE_ADDITIONAL_ITEMS_PATH: Final = "shopping/{language}/additional-items/remove" COMMUNITY_PROFILE_PATH: Final = "community/profile" MOBILE_NOTIFICATIONS_PATH: Final = ( "https://ch.tmmobile.vorwerk-digital.com/ownership/{language}/mobile-notifications" ) SUBSCRIPTIONS_PATH: Final = "ownership/subscriptions" CUSTOM_COLLECTIONS_PATH: Final = "organize/{language}/api/custom-list" ADD_CUSTOM_COLLECTION_PATH: Final = "organize/{language}/api/custom-list" REMOVE_CUSTOM_COLLECTION_PATH: Final = "organize/{language}/api/custom-list/{id}" ADD_RECIPES_TO_CUSTOM_COLLECTION_PATH: Final = ( "organize/{language}/api/custom-list/{id}" ) REMOVE_RECIPE_FROM_CUSTOM_COLLECTION_PATH: Final = ( "organize/{language}/api/custom-list/{id}/recipes/{recipe}" ) MANAGED_COLLECTIONS_PATH: Final = "organize/{language}/api/managed-list" ADD_MANAGED_COLLECTION_PATH: Final = "organize/{language}/api/managed-list" REMOVE_MANAGED_COLLECTION_PATH: Final = "organize/{language}/api/managed-list/{id}" RECIPES_IN_CALENDAR_WEEK_PATH: Final = "planning/{language}/api/my-week/{day}" ADD_RECIPES_TO_CALENDER_PATH: Final = "planning/{language}/api/my-day" REMOVE_RECIPE_FROM_CALENDER_PATH: Final = ( "planning/{language}/api/my-day/{day}/recipes/{recipe}" ) DEFAULT_SITE = "eu" miaucl-cookidoo-api-5292e37/cookidoo_api/cookidoo.py000066400000000000000000002755171476166105100224700ustar00rootroot00000000000000"""Cookidoo api implementation.""" from datetime import date from http import HTTPStatus from json import JSONDecodeError import logging import time import traceback from typing import cast from aiohttp import ClientError, ClientSession, FormData from yarl import URL from cookidoo_api.const import ( ADD_ADDITIONAL_ITEMS_PATH, ADD_CUSTOM_COLLECTION_PATH, ADD_INGREDIENT_ITEMS_FOR_RECIPES_PATH, ADD_MANAGED_COLLECTION_PATH, ADD_RECIPES_TO_CALENDER_PATH, ADD_RECIPES_TO_CUSTOM_COLLECTION_PATH, ADDITIONAL_ITEMS_PATH, API_ENDPOINT, AUTHORIZATION_HEADER, CO_UK_COUNTRY_CODE, COMMUNITY_PROFILE_PATH, COOKIDOO_CLIENT_ID, CUSTOM_COLLECTIONS_PATH, DEFAULT_API_HEADERS, DEFAULT_TOKEN_HEADERS, EDIT_ADDITIONAL_ITEMS_PATH, EDIT_OWNERSHIP_ADDITIONAL_ITEMS_PATH, EDIT_OWNERSHIP_INGREDIENT_ITEMS_PATH, INGREDIENT_ITEMS_PATH, INTERNATIONAL_COUNTRY_CODE, MANAGED_COLLECTIONS_PATH, RECIPE_PATH, RECIPES_IN_CALENDAR_WEEK_PATH, REMOVE_ADDITIONAL_ITEMS_PATH, REMOVE_CUSTOM_COLLECTION_PATH, REMOVE_INGREDIENT_ITEMS_FOR_RECIPES_PATH, REMOVE_MANAGED_COLLECTION_PATH, REMOVE_RECIPE_FROM_CALENDER_PATH, REMOVE_RECIPE_FROM_CUSTOM_COLLECTION_PATH, SHOPPING_LIST_RECIPES_PATH, SUBSCRIPTIONS_PATH, TOKEN_PATH, ) from cookidoo_api.exceptions import ( CookidooAuthException, CookidooConfigException, CookidooParseException, CookidooRequestException, ) from cookidoo_api.helpers import ( cookidoo_additional_item_from_json, cookidoo_auth_data_from_json, cookidoo_calendar_day_from_json, cookidoo_collection_from_json, cookidoo_ingredient_item_from_json, cookidoo_recipe_details_from_json, cookidoo_recipe_from_json, cookidoo_subscription_from_json, cookidoo_user_info_from_json, ) from cookidoo_api.raw_types import ( AdditionalItemJSON, CalendarDayJSON, CustomCollectionJSON, ItemJSON, ManagedCollectionJSON, RecipeDetailsJSON, RecipeJSON, ) from cookidoo_api.types import ( CookidooAdditionalItem, CookidooAuthResponse, CookidooCalendarDay, CookidooCollection, CookidooConfig, CookidooIngredientItem, CookidooLocalizationConfig, CookidooShoppingRecipe, CookidooShoppingRecipeDetails, CookidooSubscription, CookidooUserInfo, ) _LOGGER = logging.getLogger(__name__) class Cookidoo: """Unofficial Cookidoo API interface.""" _session: ClientSession _cfg: CookidooConfig _token_headers: dict[str, str] _api_headers: dict[str, str] _auth_data: CookidooAuthResponse | None def __init__( self, session: ClientSession, cfg: CookidooConfig = CookidooConfig(), ) -> None: """Init function for Bring API. Parameters ---------- session The client session for aiohttp requests cfg Cookidoo config """ self._session = session self._cfg = cfg self._token_headers = DEFAULT_TOKEN_HEADERS.copy() self._api_headers = DEFAULT_API_HEADERS.copy() self.__expires_in: int self._auth_data = None @property def localization(self) -> CookidooLocalizationConfig: """Localization.""" return self._cfg.localization @property def expires_in(self) -> int: """Refresh token expiration.""" return max(0, self.__expires_in - int(time.time())) @expires_in.setter def expires_in(self, expires_in: int | str) -> None: self.__expires_in = int(time.time()) + int(expires_in) @property def auth_data(self) -> CookidooAuthResponse | None: """Auth data.""" return self._auth_data if self._auth_data else None @auth_data.setter def auth_data(self, auth_data: CookidooAuthResponse) -> None: self._api_headers["AUTHORIZATION"] = AUTHORIZATION_HEADER.format( type=auth_data.token_type.lower().capitalize(), access_token=auth_data.access_token, ) self._auth_data = auth_data self.expires_in = auth_data.expires_in @property def api_endpoint(self) -> URL: """Get the api endpoint.""" if "international" in self._cfg.localization.url: return URL(API_ENDPOINT.format(country_code=INTERNATIONAL_COUNTRY_CODE)) if "co.uk" in self._cfg.localization.url: return URL(API_ENDPOINT.format(country_code=CO_UK_COUNTRY_CODE)) return URL(API_ENDPOINT.format(**self._cfg.localization.__dict__)) async def refresh_token(self) -> CookidooAuthResponse: """Try to refresh the token. Returns ------- CookidooAuthResponse The auth response object. Raises ------ CookidooConfigException If no login has happened yet CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. CookidooAuthException If the login fails due invalid credentials. You should check your email and password. """ if not self._auth_data: raise CookidooConfigException("No auth data available, please log in first") refresh_data = FormData() refresh_data.add_field("grant_type", "refresh_token") refresh_data.add_field("refresh_token", self._auth_data.refresh_token) refresh_data.add_field("client_id", COOKIDOO_CLIENT_ID) return await self._request_access_token(refresh_data) async def login(self) -> CookidooAuthResponse: """Try to login. Returns ------- CookidooAuthResponse The auth response object. Raises ------ CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. CookidooAuthException If the login fails due invalid credentials. You should check your email and password. """ user_data = FormData() user_data.add_field("grant_type", "password") user_data.add_field("username", self._cfg.email) user_data.add_field("password", self._cfg.password) return await self._request_access_token(user_data) async def _request_access_token(self, form_data: FormData) -> CookidooAuthResponse: """Request a new access token. Parameters ---------- form_data The data to be passed to the request with user credentials or refresh token Returns ------- CookidooAuthResponse The auth response object. Raises ------ CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. CookidooAuthException If the access token request fails due invalid credentials. You should check your email and password or refresh token. """ try: url = self.api_endpoint / TOKEN_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, data=form_data, headers=self._token_headers ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() if r.status != 200 else "", # do not log response on success, as it contains sensible data ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse access token request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot request access token: %s", errmsg["error_description"], ) raise CookidooAuthException( "Access token request failed due to authorization failure, " "please check your email and password or refresh token." ) if r.status == HTTPStatus.BAD_REQUEST: _LOGGER.debug( "Exception: Cannot request access token: %s", await r.text() ) raise CookidooAuthException( "Access token request failed due to bad request, please check your email or refresh token." ) r.raise_for_status() try: data = cookidoo_auth_data_from_json(await r.json()) except JSONDecodeError as e: _LOGGER.debug( "Exception: Cannot request access token:\n %s", traceback.format_exc(), ) raise CookidooParseException( "Cannot parse access token request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot request access token:\n %s", traceback.format_exc() ) raise CookidooRequestException( "Authentication failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot request access token:\n %s", traceback.format_exc() ) raise CookidooRequestException( "Authentication failed due to request exception." ) from e self.auth_data = data return data async def get_user_info( self, ) -> CookidooUserInfo: """Get user info. Returns ------- CookidooUserInfo The user info Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / COMMUNITY_PROFILE_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.get(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot get user info: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading user info failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return cookidoo_user_info_from_json((await r.json())["userInfo"]) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get user info:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading user info failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot get user info:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading user info failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot user info:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading user info failed due to request exception." ) from e async def get_active_subscription( self, ) -> CookidooSubscription | None: """Get active subscription if any. Returns ------- CookidooSubscription The active subscription Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / SUBSCRIPTIONS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.get(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot get active subscription: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading active subscription failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: if subscription := next( ( subscription for subscription in (await r.json()) if subscription["active"] ), None, ): return cookidoo_subscription_from_json(subscription) else: return None except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get active subscription:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading active subscription failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot get active subscription:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading user info failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot active subscription:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading active subscription failed due to request exception." ) from e async def get_recipe_details(self, id: str) -> CookidooShoppingRecipeDetails: """Get recipe details. Parameters ---------- id The id of the recipe Returns ------- CookidooShoppingRecipeDetails The recipe details Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / RECIPE_PATH.format( **self._cfg.localization.__dict__, id=id ) async with self._session.get(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot get recipe details: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading recipe details failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return cookidoo_recipe_details_from_json( cast(RecipeDetailsJSON, await r.json()) ) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get recipe details:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading recipe details failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot get recipe details:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading recipe details failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot recipe details:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading recipe details failed due to request exception." ) from e async def get_shopping_list_recipes( self, ) -> list[CookidooShoppingRecipe]: """Get recipes. Returns ------- list[CookidooShoppingRecipe] The list of the recipes Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / SHOPPING_LIST_RECIPES_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.get(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot get recipes: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading recipes failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_recipe_from_json(cast(RecipeJSON, recipe)) for recipe in (await r.json())["recipes"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get recipes:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading recipes failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot get recipes:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading recipes failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot recipes:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading recipes failed due to request exception." ) from e async def get_ingredient_items( self, ) -> list[CookidooIngredientItem]: """Get ingredient items. Returns ------- list[CookidooIngredientItem] The list of the ingredient items Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / INGREDIENT_ITEMS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.get(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot get ingredient items: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading ingredient items failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_ingredient_item_from_json(cast(ItemJSON, ingredient)) for recipe in (await r.json())["recipes"] for ingredient in recipe["recipeIngredientGroups"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get ingredient items:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading ingredient items failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot get ingredient items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading ingredient items failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot ingredient items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading ingredient items failed due to request exception." ) from e async def add_ingredient_items_for_recipes( self, recipe_ids: list[str], ) -> list[CookidooIngredientItem]: """Add ingredient items for recipes. Parameters ---------- recipe_ids The recipe ids for the ingredient items to add to the shopping list Returns ------- list[CookidooIngredientItem] The list of the added ingredient items Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = {"recipeIDs": recipe_ids} try: url = self.api_endpoint / ADD_INGREDIENT_ITEMS_FOR_RECIPES_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot add ingredient items for recipes: %s", errmsg["error_description"], ) raise CookidooAuthException( "Add ingredient items for recipes failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_ingredient_item_from_json(cast(ItemJSON, ingredient)) for recipe in (await r.json())["data"] for ingredient in recipe["recipeIngredientGroups"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get added ingredient items:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading added ingredient items failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute add ingredient items for recipes:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add ingredient items for recipes failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute add ingredient items for recipes:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add ingredient items for recipes failed due to request exception." ) from e async def remove_ingredient_items_for_recipes( self, recipe_ids: list[str], ) -> None: """Remove ingredient items for recipes. Parameters ---------- recipe_ids The recipe ids for the ingredient items to remove to the shopping list Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = {"recipeIDs": recipe_ids} try: url = self.api_endpoint / REMOVE_INGREDIENT_ITEMS_FOR_RECIPES_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot remove ingredient items for recipes: %s", errmsg["error_description"], ) raise CookidooAuthException( "Remove ingredient items for recipes failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute remove ingredient items for recipes:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove ingredient items for recipes failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute remove ingredient items for recipes:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove ingredient items for recipes failed due to request exception." ) from e async def edit_ingredient_items_ownership( self, ingredient_items: list[CookidooIngredientItem], ) -> list[CookidooIngredientItem]: """Edit ownership ingredient items. Parameters ---------- ingredient_items The ingredient items to change the the `is_owned` value for Returns ------- list[CookidooIngredientItem] The list of the edited ingredient items Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = { "ingredients": [ { "id": ingredient_item.id, "isOwned": ingredient_item.is_owned, "ownedTimestamp": int(time.time()), } for ingredient_item in ingredient_items ] } try: url = self.api_endpoint / EDIT_OWNERSHIP_INGREDIENT_ITEMS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot edit recipe ingredient items ownership: %s", errmsg["error_description"], ) raise CookidooAuthException( "Edit ingredient items ownership failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_ingredient_item_from_json(cast(ItemJSON, ingredient)) for ingredient in (await r.json())["data"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get edited ingredient items:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading edited ingredient items failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute edit ingredient items ownership:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Edit ingredient items ownership failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute edit ingredient items ownership:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Edit ingredient items ownership failed due to request exception." ) from e async def get_additional_items( self, ) -> list[CookidooAdditionalItem]: """Get additional items. Returns ------- list[CookidooAdditionalItem] The list of the additional items Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / ADDITIONAL_ITEMS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.get(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot get additional items: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading additional items failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_additional_item_from_json( cast(AdditionalItemJSON, additional_item) ) for additional_item in (await r.json())["additionalItems"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get additional items:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading additional items failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot get additional items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading additional items failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot additional items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading additional items failed due to request exception." ) from e async def add_additional_items( self, additional_item_names: list[str], ) -> list[CookidooAdditionalItem]: """Create additional items. Parameters ---------- additional_item_names The additional item names to create, only the label can be set, as the default state `is_owned=false` is forced (chain with immediate update call for work-around) Returns ------- list[CookidooAdditionalItem] The list of the added additional items Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = {"itemsValue": additional_item_names} try: url = self.api_endpoint / ADD_ADDITIONAL_ITEMS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot add additional items: %s", errmsg["error_description"], ) raise CookidooAuthException( "Add additional items failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_additional_item_from_json( cast(AdditionalItemJSON, additional_item) ) for additional_item in (await r.json())["data"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get added additional items:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading added additional items failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute add additional items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add additional items failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute add additional items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add additional items failed due to request exception." ) from e async def edit_additional_items( self, additional_items: list[CookidooAdditionalItem], ) -> list[CookidooAdditionalItem]: """Edit additional items. Parameters ---------- additional_items The additional items to change the the `name` value for Returns ------- list[CookidooAdditionalItem] The list of the edited additional items Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = { "additionalItems": [ { "id": additional_item.id, "name": additional_item.name, } for additional_item in additional_items ] } try: url = self.api_endpoint / EDIT_ADDITIONAL_ITEMS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot edit additional items: %s", errmsg["error_description"], ) raise CookidooAuthException( "Edit additional items failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_additional_item_from_json( cast(AdditionalItemJSON, additional_item) ) for additional_item in (await r.json())["data"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get edited additional items:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading edited additional items failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute edit additional items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Edit additional items failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute edit additional items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Edit additional items failed due to request exception." ) from e async def edit_additional_items_ownership( self, additional_items: list[CookidooAdditionalItem], ) -> list[CookidooAdditionalItem]: """Edit ownership additional items. Parameters ---------- additional_items The additional items to change the the `is_owned` value for Returns ------- list[CookidooAdditionalItem] The list of the edited additional items Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = { "additionalItems": [ { "id": additional_item.id, "isOwned": additional_item.is_owned, "ownedTimestamp": int(time.time()), } for additional_item in additional_items ] } try: url = self.api_endpoint / EDIT_OWNERSHIP_ADDITIONAL_ITEMS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot edit additional items ownership: %s", errmsg["error_description"], ) raise CookidooAuthException( "Edit additional items ownership failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_additional_item_from_json( cast(AdditionalItemJSON, additional_item) ) for additional_item in (await r.json())["data"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get edited additional items:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading edited additional items failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute edit additional items ownership:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Edit additional items ownership failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute edit additional items ownership:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Edit additional items ownership failed due to request exception." ) from e async def remove_additional_items( self, additional_item_ids: list[str], ) -> None: """Remove additional items. Parameters ---------- additional_item_ids The additional item ids to remove Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = {"additionalItemIDs": additional_item_ids} try: url = self.api_endpoint / REMOVE_ADDITIONAL_ITEMS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot remove additional items: %s", errmsg["error_description"], ) raise CookidooAuthException( "Remove additional items failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute remove additional items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove additional items failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute remove additional items:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove additional items failed due to request exception." ) from e async def clear_shopping_list( self, ) -> None: """Remove all additional items, ingredients and recipes. Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / INGREDIENT_ITEMS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.delete(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot clear shopping list: %s", errmsg["error_description"], ) raise CookidooAuthException( "Clear shopping list failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute clear shopping list:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Clear shopping list failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute clear shopping list:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Clear shopping list failed due to request exception." ) from e async def count_managed_collections(self) -> tuple[int, int]: """Get managed collections. Returns ------- tuple[int, int] The number of managed collections and the number of pages Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / MANAGED_COLLECTIONS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.get(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot count managed collections: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading managed collections failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: json = await r.json() return ( int(json["page"]["totalElements"]), int(json["page"]["totalPages"]), ) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot count managed collections%s", traceback.format_exc(), ) raise CookidooParseException( "Loading managed collections during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot count managed collections:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading managed collections due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot count managed collections%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading managed collections due to request exception." ) from e async def get_managed_collections(self, page: int = 0) -> list[CookidooCollection]: """Get managed collections. Parameters ---------- page The page of the managed collections Returns ------- list[CookidooCollection] The list of the managed collections Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / MANAGED_COLLECTIONS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.get( url, headers=self._api_headers, params={"page": page} ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot get managed collections: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading managed collections failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_collection_from_json(cast(ManagedCollectionJSON, list)) for list in (await r.json())["managedlists"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get managed collections%s", traceback.format_exc(), ) raise CookidooParseException( "Loading managed collections during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot get managed collections:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading managed collections due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot get managed collections%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading managed collections due to request exception." ) from e async def add_managed_collection( self, managed_collection_id: str, ) -> CookidooCollection: """Add managed collections. Parameters ---------- managed_collection_id The managed collection id to add Returns ------- CookidooCollection The added managed collection Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = {"collectionId": managed_collection_id} try: url = self.api_endpoint / ADD_MANAGED_COLLECTION_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot add managed collection: %s", errmsg["error_description"], ) raise CookidooAuthException( "Add managed collection failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return cookidoo_collection_from_json( cast(ManagedCollectionJSON, (await r.json())["content"]) ) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get added managed collection:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading added managed collection failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute add managed collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add managed collection failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute add managed collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add managed collection failed due to request exception." ) from e async def remove_managed_collection( self, managed_collection_id: str, ) -> None: """Remove managed collection. Parameters ---------- managed_collection_id The managed collection id to remove Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / REMOVE_MANAGED_COLLECTION_PATH.format( **self._cfg.localization.__dict__, id=managed_collection_id ) async with self._session.delete(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot remove managed collection: %s", errmsg["error_description"], ) raise CookidooAuthException( "Remove managed collection failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute remove managed collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove managed collection failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute remove managed collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove managed collection failed due to request exception." ) from e async def count_custom_collections(self) -> tuple[int, int]: """Get custom collections. Returns ------- tuple[int, int] The number of custom collections and the number of pages Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / CUSTOM_COLLECTIONS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.get(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot count custom collections: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading custom collections failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: json = await r.json() return ( int(json["page"]["totalElements"]), int(json["page"]["totalPages"]), ) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot count custom collections%s", traceback.format_exc(), ) raise CookidooParseException( "Loading custom collections during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot count custom collections:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading custom collections due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot count custom collections%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading custom collections due to request exception." ) from e async def get_custom_collections(self, page: int = 0) -> list[CookidooCollection]: """Get custom collections. Parameters ---------- page The page of the custom collections Returns ------- list[CookidooCollection] The list of the custom collections Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / CUSTOM_COLLECTIONS_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.get( url, headers=self._api_headers, params={"page": page} ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot get custom collections: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading custom collections failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_collection_from_json(cast(CustomCollectionJSON, list)) for list in (await r.json())["customlists"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get custom collections%s", traceback.format_exc(), ) raise CookidooParseException( "Loading custom collections during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot get custom collections:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading custom collections due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot get custom collections%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading custom collections due to request exception." ) from e async def add_custom_collection( self, custom_collection_name: str, ) -> CookidooCollection: """Add custom collections. Parameters ---------- custom_collection_name The custom collection name to add Returns ------- CookidooCollection The added custom collection Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = {"title": custom_collection_name} try: url = self.api_endpoint / ADD_CUSTOM_COLLECTION_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.post( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot add custom collection: %s", errmsg["error_description"], ) raise CookidooAuthException( "Add custom collection failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return cookidoo_collection_from_json( cast(CustomCollectionJSON, (await r.json())["content"]) ) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get added custom collection:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading added custom collection failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute add custom collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add custom collection failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute add custom collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add custom collection failed due to request exception." ) from e async def remove_custom_collection( self, custom_collection_id: str, ) -> None: """Remove custom collection. Parameters ---------- custom_collection_id The custom collection id to remove Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / REMOVE_CUSTOM_COLLECTION_PATH.format( **self._cfg.localization.__dict__, id=custom_collection_id ) async with self._session.delete(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot remove custom collection: %s", errmsg["error_description"], ) raise CookidooAuthException( "Remove custom collection failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute remove custom collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove custom collection failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute remove custom collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove custom collection failed due to request exception." ) from e async def add_recipes_to_custom_collection( self, custom_collection_id: str, recipe_ids: list[str], ) -> CookidooCollection: """Add recipes to a custom collections. Parameters ---------- custom_collection_id The custom collection to add the recipes to recipe_ids The recipe ids to add to a custom collection Returns ------- CookidooCollection The changed custom collection Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = {"recipeIds": recipe_ids} try: url = self.api_endpoint / ADD_RECIPES_TO_CUSTOM_COLLECTION_PATH.format( **self._cfg.localization.__dict__, id=custom_collection_id ) async with self._session.put( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot add recipes to custom collection: %s", errmsg["error_description"], ) raise CookidooAuthException( "Add recipes to custom collection failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return cookidoo_collection_from_json( cast(CustomCollectionJSON, (await r.json())["content"]) ) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get added recipes:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading added recipes failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute add recipes to custom collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add recipes to custom collection failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute add recipes to custom collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add recipes to custom collection failed due to request exception." ) from e async def remove_recipe_from_custom_collection( self, custom_collection_id: str, recipe_id: str, ) -> CookidooCollection: """Remove recipe from a custom collections. Parameters ---------- custom_collection_id The custom collection to remove the recipe from recipe_id The recipe id to remove from a custom collection Returns ------- CookidooCollection The changed custom collection Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / REMOVE_RECIPE_FROM_CUSTOM_COLLECTION_PATH.format( **self._cfg.localization.__dict__, id=custom_collection_id, recipe=recipe_id, ) async with self._session.delete(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot remove recipe from custom collection: %s", errmsg["error_description"], ) raise CookidooAuthException( "Remove recipe from custom collection failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return cookidoo_collection_from_json( cast(CustomCollectionJSON, (await r.json())["content"]) ) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get removed recipe:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading removed recipe failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute add recipe from custom collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove recipe from custom collection failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute remove recipe from custom collection:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove recipe from custom collection failed due to request exception." ) from e async def get_recipes_in_calendar_week( self, day: date ) -> list[CookidooCalendarDay]: """Get recipes in a calendar week. Parameters ---------- day The date specifying the calendar week Returns ------- list[CookidooCalendarDay] The list of the calendar days with recipes Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / RECIPES_IN_CALENDAR_WEEK_PATH.format( **self._cfg.localization.__dict__, day=day.isoformat() ) async with self._session.get(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot get recipes in calendar week: %s", errmsg["error_description"], ) raise CookidooAuthException( "Loading recipes in calendar week failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return [ cookidoo_calendar_day_from_json(cast(CalendarDayJSON, day)) for day in (await r.json())["myDays"] ] except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get recipes in calendar day%s", traceback.format_exc(), ) raise CookidooParseException( "Loading recipes in calendar day during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot get recipes in calendar day:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading recipes in calendar day due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot get recipes in calendar day%s", traceback.format_exc(), ) raise CookidooRequestException( "Loading recipes in calendar day due to request exception." ) from e async def add_recipes_to_calendar( self, day: date, recipe_ids: list[str], ) -> CookidooCalendarDay: """Add recipes to a calendar. Parameters ---------- day The date to add the recipes to in the calendar recipe_ids The recipe ids to add to a custom collection Returns ------- CookidooCalendarDay The changed calendar day Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ json_data = {"recipeIds": recipe_ids, "dayKey": day.isoformat()} try: url = self.api_endpoint / ADD_RECIPES_TO_CALENDER_PATH.format( **self._cfg.localization.__dict__ ) async with self._session.put( url, headers=self._api_headers, json=json_data ) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot add recipes to calendar: %s", errmsg["error_description"], ) raise CookidooAuthException( "Add recipes to calendar failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return cookidoo_calendar_day_from_json( cast(CalendarDayJSON, (await r.json())["content"]) ) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get added recipes:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading added recipes failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute add recipes to calendar:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add recipes to calendar failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute add recipes to calendar:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Add recipes to calendar failed due to request exception." ) from e async def remove_recipe_from_calendar( self, day: date, recipe_id: str, ) -> CookidooCalendarDay: """Remove recipe from calendar. Parameters ---------- day The date to remove the recipe from in the calendar recipe_id The recipe id to remove from the calendar Returns ------- CookidooCalendarDay The changed calendar day Raises ------ CookidooAuthException When the access token is not valid anymore CookidooRequestException If the request fails. CookidooParseException If the parsing of the request response fails. """ try: url = self.api_endpoint / REMOVE_RECIPE_FROM_CALENDER_PATH.format( **self._cfg.localization.__dict__, day=day.isoformat(), recipe=recipe_id, ) async with self._session.delete(url, headers=self._api_headers) as r: _LOGGER.debug( "Response from %s [%s]: %s", url, r.status, await r.text() ) if r.status == HTTPStatus.UNAUTHORIZED: try: errmsg = await r.json() except (JSONDecodeError, ClientError): _LOGGER.debug( "Exception: Cannot parse request response:\n %s", traceback.format_exc(), ) else: _LOGGER.debug( "Exception: Cannot remove recipe from calendar: %s", errmsg["error_description"], ) raise CookidooAuthException( "Remove recipe from calendar failed due to authorization failure, " "the authorization token is invalid or expired." ) r.raise_for_status() try: return cookidoo_calendar_day_from_json( cast(CalendarDayJSON, (await r.json())["content"]) ) except (JSONDecodeError, KeyError) as e: _LOGGER.debug( "Exception: Cannot get removed recipe:\n%s", traceback.format_exc(), ) raise CookidooParseException( "Loading removed recipe failed during parsing of request response." ) from e except TimeoutError as e: _LOGGER.debug( "Exception: Cannot execute add recipe from calendar:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove recipe from calendar failed due to connection timeout." ) from e except ClientError as e: _LOGGER.debug( "Exception: Cannot execute remove recipe from calendar:\n%s", traceback.format_exc(), ) raise CookidooRequestException( "Remove recipe from calendar failed due to request exception." ) from e miaucl-cookidoo-api-5292e37/cookidoo_api/exceptions.py000066400000000000000000000012771476166105100230310ustar00rootroot00000000000000"""Cookidoo API exceptions.""" class CookidooException(Exception): """General exception occurred.""" class CookidooConfigException(CookidooException): """When the config is invalid.""" class CookidooAuthException(CookidooException): """When an authentication error is encountered.""" class CookidooParseException(CookidooException): """When data could not be parsed.""" class CookidooRequestException(CookidooException): """When a request returns an error.""" class CookidooResponseException(CookidooException): """When a response could not be parsed.""" class CookidooUnavailableException(CookidooException): """When the network or server is not available.""" miaucl-cookidoo-api-5292e37/cookidoo_api/helpers.py000066400000000000000000000216441476166105100223120ustar00rootroot00000000000000"""Cookidoo API helpers.""" import json import logging import os from typing import cast import aiofiles from cookidoo_api.raw_types import ( AdditionalItemJSON, AuthResponseJSON, CalendarDayJSON, CustomCollectionJSON, IngredientJSON, ItemJSON, ManagedCollectionJSON, QuantityJSON, RecipeDetailsJSON, RecipeJSON, SubscriptionJSON, UserInfoJSON, ) from cookidoo_api.types import ( CookidooAdditionalItem, CookidooAuthResponse, CookidooCalendarDay, CookidooCalendarDayRecipe, CookidooCategory, CookidooChapter, CookidooChapterRecipe, CookidooCollection, CookidooIngredient, CookidooIngredientItem, CookidooLocalizationConfig, CookidooRecipeCollection, CookidooShoppingRecipe, CookidooShoppingRecipeDetails, CookidooSubscription, CookidooUserInfo, ) _LOGGER = logging.getLogger(__name__) localization_file_path = os.path.join(os.path.dirname(__file__), "localization.json") def cookidoo_auth_data_from_json( auth_data: AuthResponseJSON, ) -> CookidooAuthResponse: """Convert a auth data received from the API to a cookidoo auth data.""" return CookidooAuthResponse( sub=auth_data["sub"], access_token=auth_data["access_token"], refresh_token=auth_data["refresh_token"], token_type=auth_data["token_type"], expires_in=auth_data["expires_in"], ) def cookidoo_user_info_from_json( user_info: UserInfoJSON, ) -> CookidooUserInfo: """Convert a user info received from the API to a cookidoo user info.""" return CookidooUserInfo( username=user_info["username"], description=user_info.get("description"), picture=user_info["picture"], ) def cookidoo_subscription_from_json( subscription: SubscriptionJSON, ) -> CookidooSubscription: """Convert a subscription received from the API to a cookidoo subscription.""" return CookidooSubscription( active=subscription["active"], expires=subscription["expires"], start_date=subscription["startDate"], status=subscription["status"], subscription_level=subscription["subscriptionLevel"], subscription_source=subscription["subscriptionSource"], type=subscription["type"], extended_type=subscription["extendedType"], ) def cookidoo_collection_from_json( collection: CustomCollectionJSON | ManagedCollectionJSON, ) -> CookidooCollection: """Convert a collection received from the API to a cookidoo collection.""" return CookidooCollection( id=collection["id"], name=collection["title"], description=cast(str, collection.get("description", None)), chapters=[ CookidooChapter( name=chapter["title"], recipes=[ CookidooChapterRecipe( id=recipe["id"], name=recipe["title"], total_time=int(float(recipe["totalTime"])), ) for recipe in chapter["recipes"] ], ) for chapter in collection["chapters"] ], ) def cookidoo_recipe_from_json( recipe: RecipeJSON, ) -> CookidooShoppingRecipe: """Convert a shopping recipe received from the API to a cookidoo shopping recipe.""" return CookidooShoppingRecipe( id=recipe["id"], name=recipe["title"], ingredients=[ cookidoo_ingredient_from_json(ingredient) for ingredient in recipe["recipeIngredientGroups"] ], ) def cookidoo_quantity_from_json( quantity: QuantityJSON, ) -> str: """Convert an quantity received from the API to a str.""" if "value" in quantity and quantity["value"]: return str(quantity["value"]) elif ( "from" in quantity and "to" in quantity and quantity["from"] and quantity["to"] ): return f"{quantity['from']} - {quantity['to']}" else: return "" def cookidoo_recipe_details_from_json( recipe: RecipeDetailsJSON, ) -> CookidooShoppingRecipeDetails: """Convert an recipe details received from the API to a cookidoo recipe details.""" return CookidooShoppingRecipeDetails( id=recipe["id"], name=recipe["title"], ingredients=[ cookidoo_ingredient_from_json(ingredient) for ingredientGroup in recipe["recipeIngredientGroups"] for ingredient in ingredientGroup["recipeIngredients"] ], difficulty=recipe["difficulty"], notes=[ additional_notes["content"] for additional_notes in recipe["additionalInformation"] ], categories=[ CookidooCategory( id=category["id"], name=category["title"], notes=category["subtitle"] ) for category in recipe["categories"] ], collections=[ CookidooRecipeCollection( id=collection["id"], name=collection["title"], total_recipes=collection["recipesCount"]["value"], ) for collection in recipe["inCollections"] ], utensils=[utensil["utensilNotation"] for utensil in recipe["recipeUtensils"]], serving_size=f"{recipe["servingSize"]['quantity']['value']} {recipe["servingSize"]["unitNotation"]}", active_time=next( time_["quantity"]["value"] for time_ in recipe["times"] if time_["type"] == "activeTime" and time_["quantity"]["value"] ), total_time=next( time_["quantity"]["value"] for time_ in recipe["times"] if time_["type"] == "totalTime" and time_["quantity"]["value"] ), ) def cookidoo_ingredient_from_json( ingredient: IngredientJSON | ItemJSON, ) -> CookidooIngredient: """Convert an ingredient received from the API to a cookidoo ingredient.""" return CookidooIngredient( id=ingredient["localId"] if "localId" in ingredient else ingredient["id"], # type: ignore[typeddict-item] name=ingredient["ingredientNotation"], description=f"{cookidoo_quantity_from_json(ingredient['quantity'])} {ingredient['unitNotation']}" if ingredient["unitNotation"] and ingredient["quantity"] else cookidoo_quantity_from_json(ingredient["quantity"]) if ingredient["quantity"] else "", ) def cookidoo_ingredient_item_from_json( item: ItemJSON, ) -> CookidooIngredientItem: """Convert an ingredient item received from the API to a cookidoo item.""" return CookidooIngredientItem( id=item["id"], name=item["ingredientNotation"], is_owned=item["isOwned"], description=f"{cookidoo_quantity_from_json(item['quantity'])} {item['unitNotation']}" if item["unitNotation"] and item["quantity"] else str(cookidoo_quantity_from_json(item["quantity"])) if item["quantity"] else "", ) def cookidoo_additional_item_from_json( item: AdditionalItemJSON, ) -> CookidooAdditionalItem: """Convert an additional item received from the API to a cookidoo item.""" return CookidooAdditionalItem( id=item["id"], name=item["name"], is_owned=item["isOwned"], ) def cookidoo_calendar_day_from_json( calendar_day: CalendarDayJSON, ) -> CookidooCalendarDay: """Convert a calendar day received from the API to a cookidoo item.""" return CookidooCalendarDay( id=calendar_day["id"], title=calendar_day["title"], recipes=[ CookidooCalendarDayRecipe( id=recipe["id"], name=recipe["title"], total_time=recipe["totalTime"] ) for recipe in calendar_day["recipes"] ], ) async def __get_localization_options( country: str | None = None, language: str | None = None, ) -> list[CookidooLocalizationConfig]: async with aiofiles.open(localization_file_path, encoding="utf-8") as file: options_ = cast(list[dict[str, str]], json.loads(await file.read())) options = (CookidooLocalizationConfig(**x) for x in options_) filtered_options = filter( lambda option: (not country or option.country_code == country) and (not language or option.language == language), options, ) return list(cast(list[CookidooLocalizationConfig], filtered_options)) async def get_localization_options( country: str | None = None, language: str | None = None, ) -> list[CookidooLocalizationConfig]: """Get a list of possible localization options.""" return await __get_localization_options(country, language) async def get_country_options() -> list[str]: """Get a list of possible country options.""" return list({option.country_code for option in await get_localization_options()}) async def get_language_options() -> list[str]: """Get a list of possible language options.""" return list({option.language for option in await get_localization_options()}) miaucl-cookidoo-api-5292e37/cookidoo_api/localization.json000066400000000000000000001401251476166105100236550ustar00rootroot00000000000000[ { "country_code": "ar", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "ar", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "ar", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "ar", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "ar", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "ar", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "ar", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "ar", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "ar", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "ar", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "au", "language": "en-AU", "url": "https://cookidoo.com.au/foundation/en-AU" }, { "country_code": "at", "language": "de-AT", "url": "https://cookidoo.at/foundation/de-AT" }, { "country_code": "be", "language": "nl-BE", "url": "https://cookidoo.be/foundation/nl-BE" }, { "country_code": "be", "language": "en", "url": "https://cookidoo.be/foundation/en" }, { "country_code": "be", "language": "fr-BE", "url": "https://cookidoo.be/foundation/fr-BE" }, { "country_code": "be", "language": "de-BE", "url": "https://cookidoo.be/foundation/de-BE" }, { "country_code": "br", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "br", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "br", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "br", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "br", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "br", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "br", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "br", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "br", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "br", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "bn", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "bn", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "bn", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "bn", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "bn", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "bn", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "bn", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "bn", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "bn", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "bn", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "ca", "language": "en-CA", "url": "https://cookidoo.ca/foundation/en-CA" }, { "country_code": "ca", "language": "fr-CA", "url": "https://cookidoo.ca/foundation/fr-CA" }, { "country_code": "cl", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "cl", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "cl", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "cl", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "cl", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "cl", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "cl", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "cl", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "cl", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "cl", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "co", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "co", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "co", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "co", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "co", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "co", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "co", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "co", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "co", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "co", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "cy", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "cy", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "cy", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "cy", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "cy", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "cy", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "cy", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "cy", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "cy", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "cy", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "cz", "language": "cs", "url": "https://cookidoo.cz/foundation/cs" }, { "country_code": "dk", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "dk", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "dk", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "dk", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "dk", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "dk", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "dk", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "dk", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "dk", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "dk", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "ee", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "ee", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "ee", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "ee", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "ee", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "ee", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "ee", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "ee", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "ee", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "ee", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "fr", "language": "fr-FR", "url": "https://cookidoo.fr/foundation/fr-FR" }, { "country_code": "de", "language": "de-DE", "url": "https://cookidoo.de/foundation/de-DE" }, { "country_code": "gr", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "gr", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "gr", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "gr", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "gr", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "gr", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "gr", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "gr", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "gr", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "gr", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "gt", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "gt", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "gt", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "gt", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "gt", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "gt", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "gt", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "gt", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "gt", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "gt", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "hu", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "hu", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "hu", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "hu", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "hu", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "hu", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "hu", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "hu", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "hu", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "hu", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "is", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "is", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "is", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "is", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "is", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "is", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "is", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "is", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "is", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "is", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "id", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "id", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "id", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "id", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "id", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "id", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "id", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "id", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "id", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "id", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "ie", "language": "en-GB", "url": "https://cookidoo.co.uk/foundation/en-GB" }, { "country_code": "il", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "il", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "il", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "il", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "il", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "il", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "il", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "il", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "il", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "il", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "it", "language": "it-IT", "url": "https://cookidoo.it/foundation/it-IT" }, { "country_code": "kw", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "kw", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "kw", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "kw", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "kw", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "kw", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "kw", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "kw", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "kw", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "kw", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "lt", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "lt", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "lt", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "lt", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "lt", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "lt", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "lt", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "lt", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "lt", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "lt", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "lu", "language": "nl-BE", "url": "https://cookidoo.be/foundation/nl-BE" }, { "country_code": "lu", "language": "en", "url": "https://cookidoo.be/foundation/en" }, { "country_code": "lu", "language": "fr-BE", "url": "https://cookidoo.be/foundation/fr-BE" }, { "country_code": "lu", "language": "de-BE", "url": "https://cookidoo.be/foundation/de-BE" }, { "country_code": "my", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "my", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "my", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "my", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "my", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "my", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "my", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "my", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "my", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "my", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "mt", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "mt", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "mt", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "mt", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "mt", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "mt", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "mt", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "mt", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "mt", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "mt", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "mx", "language": "es-MX", "url": "https://cookidoo.mx/foundation/es-MX" }, { "country_code": "ma", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "ma", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "ma", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "ma", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "ma", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "ma", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "ma", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "ma", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "ma", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "ma", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "nl", "language": "nl-BE", "url": "https://cookidoo.be/foundation/nl-BE" }, { "country_code": "nl", "language": "en", "url": "https://cookidoo.be/foundation/en" }, { "country_code": "nl", "language": "fr-BE", "url": "https://cookidoo.be/foundation/fr-BE" }, { "country_code": "nl", "language": "de-BE", "url": "https://cookidoo.be/foundation/de-BE" }, { "country_code": "nz", "language": "en-AU", "url": "https://cookidoo.com.au/foundation/en-AU" }, { "country_code": "no", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "no", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "no", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "no", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "no", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "no", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "no", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "no", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "no", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "no", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "pa", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "pa", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "pa", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "pa", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "pa", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "pa", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "pa", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "pa", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "pa", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "pa", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "py", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "py", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "py", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "py", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "py", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "py", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "py", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "py", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "py", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "py", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "pe", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "pe", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "pe", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "pe", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "pe", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "pe", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "pe", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "pe", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "pe", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "pe", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "ph", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "ph", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "ph", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "ph", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "ph", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "ph", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "ph", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "ph", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "ph", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "ph", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "pl", "language": "pl", "url": "https://cookidoo.pl/foundation/pl" }, { "country_code": "pt", "language": "pt-PT", "url": "https://cookidoo.pt/foundation/pt-PT" }, { "country_code": "ro", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "ro", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "ro", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "ro", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "ro", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "ro", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "ro", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "ro", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "ro", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "ro", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "sa", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "sa", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "sa", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "sa", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "sa", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "sa", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "sa", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "sa", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "sa", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "sa", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "sg", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "sg", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "sg", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "sg", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "sg", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "sg", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "sg", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "sg", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "sg", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "sg", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "za", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "za", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "za", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "za", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "za", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "za", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "za", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "za", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "za", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "za", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "es", "language": "es-ES", "url": "https://cookidoo.es/foundation/es-ES" }, { "country_code": "se", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "se", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "se", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "se", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "se", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "se", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "se", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "se", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "se", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "se", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "ch", "language": "en", "url": "https://cookidoo.ch/foundation/en" }, { "country_code": "ch", "language": "fr-CH", "url": "https://cookidoo.ch/foundation/fr-CH" }, { "country_code": "ch", "language": "de-CH", "url": "https://cookidoo.ch/foundation/de-CH" }, { "country_code": "ch", "language": "it-CH", "url": "https://cookidoo.ch/foundation/it-CH" }, { "country_code": "th", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "th", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "th", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "th", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "th", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "th", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "th", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "th", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "th", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "th", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "tr", "language": "tr-TR", "url": "https://cookidoo.com.tr/foundation/tr-TR" }, { "country_code": "ua", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "ua", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "ua", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "ua", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "ua", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "ua", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "ua", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "ua", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "ua", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "ua", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "ae", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "ae", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "ae", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "ae", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "ae", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "ae", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "ae", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "ae", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "ae", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "ae", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "gb", "language": "en-GB", "url": "https://cookidoo.co.uk/foundation/en-GB" }, { "country_code": "us", "language": "en-US", "url": "https://cookidoo.thermomix.com/foundation/en-US" }, { "country_code": "uy", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "uy", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "uy", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "uy", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "uy", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "uy", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "uy", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "uy", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "uy", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "uy", "language": "vi", "url": "https://cookidoo.international/foundation/vi" }, { "country_code": "vn", "language": "en", "url": "https://cookidoo.international/foundation/en" }, { "country_code": "vn", "language": "fr", "url": "https://cookidoo.international/foundation/fr" }, { "country_code": "vn", "language": "el", "url": "https://cookidoo.international/foundation/el" }, { "country_code": "vn", "language": "hu", "url": "https://cookidoo.international/foundation/hu" }, { "country_code": "vn", "language": "id", "url": "https://cookidoo.international/foundation/id" }, { "country_code": "vn", "language": "pt-BR", "url": "https://cookidoo.international/foundation/pt-BR" }, { "country_code": "vn", "language": "ro", "url": "https://cookidoo.international/foundation/ro" }, { "country_code": "vn", "language": "zh-Hans", "url": "https://cookidoo.international/foundation/zh-Hans" }, { "country_code": "vn", "language": "es", "url": "https://cookidoo.international/foundation/es" }, { "country_code": "vn", "language": "vi", "url": "https://cookidoo.international/foundation/vi" } ]miaucl-cookidoo-api-5292e37/cookidoo_api/py.typed000066400000000000000000000000001476166105100217540ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/cookidoo_api/raw_types.py000066400000000000000000000113661476166105100226650ustar00rootroot00000000000000"""Cookidoo API raw json types.""" from typing import Literal, TypedDict class AuthResponseJSON(TypedDict): """An auth response class.""" sub: str access_token: str refresh_token: str token_type: str expires_in: int class UserInfoJSON(TypedDict): """The json for a user info in the API.""" username: str description: str | None picture: str | None class SubscriptionJSON(TypedDict): """The json for a subscription in the API.""" active: bool expires: str startDate: str status: str subscriptionLevel: str subscriptionSource: str type: str extendedType: str # class QuantityJSON(TypedDict): # """The json for an quantity in the API.""" # value: int | None # from: int | None # to: int | None # Need to use alternative syntax as "from" is a protected keyword QuantityJSON = TypedDict( "QuantityJSON", {"value": int | None, "from": int | None, "to": int | None} ) class AdditionalItemJSON(TypedDict): """The json for an additional item in the API.""" id: str name: str isOwned: bool class ItemJSON(TypedDict): """The json for an item in the API.""" id: str ingredientNotation: str isOwned: bool quantity: QuantityJSON | None unitNotation: str | None class IngredientJSON(TypedDict): """The json for an ingredient in the API.""" localId: str ingredientNotation: str quantity: QuantityJSON | None unitNotation: str | None class RecipeJSON(TypedDict): """The json for a recipe in the API.""" id: str title: str recipeIngredientGroups: list[ItemJSON] class RecipeDetailsAdditionalInformationJSON(TypedDict): """The json for a recipe details additional information in the API.""" content: str class RecipeDetailsCategoryJSON(TypedDict): """The json for a recipe details category in the API.""" id: str title: str subtitle: str class RecipeDetailsCollectionCountJSON(TypedDict): """The json for a recipe details collection count in the API.""" value: int class RecipeDetailsCollectionJSON(TypedDict): """The json for a recipe details collection in the API.""" id: str title: str recipesCount: RecipeDetailsCollectionCountJSON class RecipeDetailsIngredientGroupJSON(TypedDict): """The json for a recipe details ingredient group in the API.""" recipeIngredients: list[IngredientJSON] class RecipeDetailsStepJSON(TypedDict): """The json for a recipe step in the API.""" formattedText: str title: str class RecipeDetailsStepGroupJSON(TypedDict): """The json for a recipe step group in the API.""" title: str recipeSteps: list[RecipeDetailsStepJSON] class RecipeDetailsUtensilsJSON(TypedDict): """The json for a recipe utensils in the API.""" utensilNotation: str class RecipeDetailsServingSizeJSON(TypedDict): """The json for a recipe serving size in the API.""" quantity: QuantityJSON unitNotation: str class RecipeDetailsTimeJSON(TypedDict): """The json for a recipe time in the API.""" quantity: QuantityJSON type: str comment: str class RecipeDetailsJSON(TypedDict): """The json for a recipe details in the API.""" id: str title: str difficulty: str additionalInformation: list[RecipeDetailsAdditionalInformationJSON] categories: list[RecipeDetailsCategoryJSON] inCollections: list[RecipeDetailsCollectionJSON] recipeIngredientGroups: list[RecipeDetailsIngredientGroupJSON] recipeStepGroups: list[RecipeDetailsStepGroupJSON] recipeUtensils: list[RecipeDetailsUtensilsJSON] servingSize: RecipeDetailsServingSizeJSON times: list[RecipeDetailsTimeJSON] class ChapterRecipeJSON(TypedDict): """The json for a chapter recipe in the API.""" id: str title: str type: Literal["VORWERK"] | Literal["CUSTOM"] totalTime: int class ChapterJSON(TypedDict): """The json for a chapter in the API.""" title: str recipes: list[ChapterRecipeJSON] class CustomCollectionJSON(TypedDict): """The json for a custom collection in the API.""" id: str title: str chapters: list[ChapterJSON] listType: Literal["CUSTOMLIST"] author: str class ManagedCollectionJSON(TypedDict): """The json for a managed collection in the API.""" id: str title: str description: str chapters: list[ChapterJSON] listType: Literal["MANAGEDLIST"] author: Literal["Vorwerk"] class CalenderDayRecipeJSON(TypedDict): """The json for a calender day recipe in the API.""" id: str title: str totalTime: int class CalendarDayJSON(TypedDict): """The json for a calendar day in the API.""" id: str title: str dayKey: str recipes: list[CalenderDayRecipeJSON] __all__ = ["QuantityJSON"] miaucl-cookidoo-api-5292e37/cookidoo_api/types.py000066400000000000000000000127321476166105100220120ustar00rootroot00000000000000"""Cookidoo API types.""" from dataclasses import dataclass, field @dataclass class CookidooLocalizationConfig: """A localization config class.""" country_code: str = "ch" language: str = "de-CH" url: str = "https://cookidoo.ch/foundation/de-CH" @dataclass class CookidooConfig: """Cookidoo config type. Attributes ---------- localization The localization for the api including country, language and url email The email to login password The password to login """ localization: CookidooLocalizationConfig = field( default_factory=lambda: CookidooLocalizationConfig() ) email: str = "your@email" password: str = "1234password!" @dataclass class CookidooAuthResponse: """An auth response class.""" access_token: str refresh_token: str token_type: str expires_in: int sub: str @dataclass class CookidooUserInfo: """A user info class.""" username: str description: str | None picture: str | None @dataclass class CookidooSubscription: """A subscription class.""" active: bool expires: str start_date: str status: str subscription_level: str subscription_source: str type: str extended_type: str @dataclass class CookidooIngredient: """Cookidoo ingredient type. Attributes ---------- id The id of the ingredient name The label of the ingredient description The description of the item, including the quantity or other helpful information """ id: str name: str description: str @dataclass class CookidooItem: """Cookidoo item type. Attributes ---------- id The id of the item name The label of the item """ id: str name: str is_owned: bool @dataclass class CookidooIngredientItem(CookidooItem): """Cookidoo ingredient item type. Attributes ---------- description The description of the item, including the quantity or other helpful information """ description: str @dataclass class CookidooAdditionalItem(CookidooItem): """Cookidoo additional item type.""" pass @dataclass class CookidooShoppingRecipe: """Cookidoo shopping recipe type. Attributes ---------- id The id of the recipe name The label of the recipe ingredients The ingredients of the recipe """ id: str name: str ingredients: list[CookidooIngredient] @dataclass class CookidooCategory: """Cookidoo category type. Attributes ---------- id The id of the category name The label of the category notes The additional information of the category """ id: str name: str notes: str @dataclass class CookidooRecipeCollection: """Cookidoo recipe collection type. Attributes ---------- id The id of the collection name The label of the collection additional_information The additional information of the collection """ id: str name: str total_recipes: int @dataclass class CookidooShoppingRecipeDetails(CookidooShoppingRecipe): """Cookidoo recipe details type. Attributes ---------- difficulty The difficulty of the recipe notes Hints and additional information about the recipe categories The categories of the recipe collections The collections of the recipe utensils The utensils needed for the recipe serving_size The service size of the recipe active_time The time needed preparing the recipe [in seconds] total_time The time needed until the recipe is ready [in seconds] """ difficulty: str notes: list[str] categories: list[CookidooCategory] collections: list[CookidooRecipeCollection] utensils: list[str] serving_size: str active_time: int total_time: int @dataclass class CookidooChapterRecipe: """Cookidoo chapter recipe type. Attributes ---------- id The id of the recipe name The label of the recipe total_time The time for the recipe """ id: str name: str total_time: int @dataclass class CookidooChapter: """Cookidoo chapter type. Attributes ---------- title The title of the chapter recipes The recipes in the chapter """ name: str recipes: list[CookidooChapterRecipe] @dataclass class CookidooCollection: """Cookidoo collection type. Attributes ---------- id The id of the collection title The title of the collection description The description of the collection chapters The recipes in the collection """ id: str name: str description: str | None chapters: list[CookidooChapter] @dataclass class CookidooCalendarDayRecipe: """Cookidoo calendar day recipe type. Attributes ---------- id The id of the recipe name The label of the recipe total_time The time for the recipe """ id: str name: str total_time: int @dataclass class CookidooCalendarDay: """Cookidoo calendar day type. Attributes ---------- id The id of the calendar day title The title of the calendar day recipes The recipes in the calendar day """ id: str title: str recipes: list[CookidooCalendarDayRecipe] miaucl-cookidoo-api-5292e37/docs/000077500000000000000000000000001476166105100165605ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/docs/authors.md000066400000000000000000000002121476166105100205620ustar00rootroot00000000000000# Credits ## Development Lead * Cyrill Raccaud [@miaucl](https://github.com/miaucl) ## Contributors None yet. Why not be the first? miaucl-cookidoo-api-5292e37/docs/changelog.md000066400000000000000000000000261476166105100210270ustar00rootroot00000000000000--8<-- "CHANGELOG.md" miaucl-cookidoo-api-5292e37/docs/index.md000066400000000000000000000000231476166105100202040ustar00rootroot00000000000000--8<-- "README.md" miaucl-cookidoo-api-5292e37/docs/license.md000066400000000000000000000000421476166105100205200ustar00rootroot00000000000000```plaintext --8<-- "LICENSE" ``` miaucl-cookidoo-api-5292e37/docs/localization.md000066400000000000000000000021331476166105100215710ustar00rootroot00000000000000# Available localizations To extract the available localizations, we use the web application and extract the data from the html. Open [](https://cookidoo.ch/foundation/en) and enter following command `navigator.clipboard.writeText(document.querySelector(".core-footer__language-select ul.core-dropdown-list").outerHTML)` into the console to extract the desired html, which will be copied to the clipboard. Should you run into permission issues, just use `console.log(document.querySelector(".core-footer__language-select ul.core-dropdown-list").outerHTML)` and copy it from the console output manually. Paste it into the [`./raw/localization-extract.html`](https://github.com/miaucl/cookidoo/blob/master/raw/localization-extract.html) file. Run following snippet [`../scripts/process-localization-extract.py`](https://github.com/miaucl/cookidoo/blob/master/scripts/process-localization-extract.py), or use the VSCode task, to extract the data and update the [`../cookidoo_api/localization.json`](https://github.com/miaucl/cookidoo/blob/master/cookidoo_api/localization.json). miaucl-cookidoo-api-5292e37/docs/raw-api-requests/000077500000000000000000000000001476166105100217715ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/docs/raw-api-requests/active-subscription.txt000066400000000000000000000026561476166105100265400ustar00rootroot00000000000000GET https://ch.tmmobile.vorwerk-digital.com/ownership/subscriptions HTTP/2.0 content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ accept-encoding: gzip if-modified-since: Sun, 03 Nov 2024 13:26:28 GMT content-length: 0 HTTP/2.0 200 content-type: application/json date: Sun, 03 Nov 2024 13:26:28 GMT x-xss-protection: 0 set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:26:28 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:26:28 GMT; httponly strict-transport-security: max-age=31536000 x-frame-options: DENY x-content-type-options: nosniff x-cache: Miss from cloudfront via: 1.1 72901e1a1a6af8228b948e1ec3586ace.cloudfront.net (CloudFront) x-amz-cf-pop: MXP63-P4 x-amz-cf-id: _1ka6Bl3Lf8vl51posPiNnAqQOJzT2BUrZVCBFq_7fWB3n3SC34DrQ== content-length: 385 [{"active":false,"startDate":"2024-09-15T00:00:00Z","expires":"2024-10-15T23:59:00Z","type":"TRIAL","extendedType":"TRIAL","autoRenewalProduct":"Cookidoo 1 Month Free","autoRenewalProductCode":null,"countryOfResidence":"ch","subscriptionLevel":"NONE","status":"ENDED","marketMismatch":false,"subscriptionSource":"COMMERCE","_created":"2024-11-02T22:11:58.524122898Z","_modified":null}]miaucl-cookidoo-api-5292e37/docs/raw-api-requests/add-additional-item.txt000066400000000000000000000021311476166105100263210ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/add HTTP/2.0 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ content-type: application/json; charset=UTF-8 content-length: 26 accept-encoding: gzip {"itemsValue":["Fleisch"]} HTTP/2.0 200 content-type: application/json; charset=utf-8 date: Sun, 03 Nov 2024 13:36:39 GMT set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:36:39 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:36:39 GMT; httponly strict-transport-security: max-age=31536000 cache-control: no-cache, no-store, must-revalidate x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: tY9vNlxDrSysFk14W6q07pOqPLbBgb5Ufz8PLgfCq4z0lVJxCs6FbQ== content-length: 79 {"data":[{"id":"01JBS3VWVG6CRM8NG4TADPMHZG","name":"Fleisch","isOwned":false}]}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/add-custom-collection.txt000066400000000000000000000111041476166105100267200ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list HTTP/2.0 accept: application/vnd.vorwerk.organize.custom-list.mobile+json accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg content-type: application/json; charset=UTF-8 content-length: 21 accept-encoding: gzip {"title":"Testliste"} HTTP/2.0 201 content-type: application/vnd.vorwerk.organize.custom-list.mobile+json date: Wed, 06 Nov 2024 22:33:18 GMT x-request-id: fe49725f5c8ba2993f91932874bc7045;auth-proxy-organize-authproxy:9e4a7baa-fc2c-462e-b613-4c763e6dcb10;auth-proxy-organize-authproxy:692c8555-a80a-49e8-88c7-eb075e9530d1;organize-ssi-nginx:b1bf88c6f5f08208d33c4c5bc2e9e1dd;organize:cf73a3d1-ed56-4d73-b7ed-4c4c6d5824b3 etag: "0a792bcc873f7a2fe915a6951172e9aa0" x-frame-options: DENY x-content-type-options: nosniff set-cookie: current-locale=de_CH; Path=/organize; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083; path=/; secure; expires=Fri, 06 Dec 2024 22:33:18 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg; path=/; secure; expires=Fri, 06 Dec 2024 22:33:18 GMT; httponly strict-transport-security: max-age=31536000 x-xss-protection: 0 x-cache: Miss from cloudfront via: 1.1 15a25f000172c4183886f5e8d467c1d8.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: 9ltAMmqOwtJgpjWC_wOH10smyFNCPoSkq5kjt-jawQb1w4zDg0pNxg== content-length: 437 {"message":"Rezeptliste wurde erfolgreich erstellt!","content":{"id":"01JC1SRPRSW0SHE0AK8GCASABX","title":"Testliste","version":1,"created":"2024-11-06T22:33:18.873+00:00","modified":"2024-11-06T22:33:18.873+00:00","chapters":[{"title":"","recipes":[]}],"assets":{"images":{"square":"","portrait":"","landscape":""}},"sharedListId":null,"author":"4a74c102-3ee6-4bcd-9227-2c403900b8de","listType":"CUSTOMLIST","shared":false},"code":null}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/add-ingredients-for-recipe.txt000066400000000000000000000065431476166105100276340ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/add HTTP/2.0 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ content-type: application/json; charset=UTF-8 content-length: 25 accept-encoding: gzip {"recipeIDs":["r907015"]} HTTP/2.0 200 content-type: application/json; charset=utf-8 date: Sun, 03 Nov 2024 13:33:34 GMT set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:33:34 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:33:34 GMT; httponly strict-transport-security: max-age=31536000 cache-control: no-cache, no-store, must-revalidate vary: accept-encoding x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: Mw2sgsgkrB1CdD2-xMTasacDWzrDn42aN0sXDJ7jnLDAmrZmUI4EhQ== content-length: 2379 {"data":[{"id":"r907015","title":"Kokos Pralinen","locale":"ch","language":"de","status":"ok","ulid":"01JBS3P8KHNEE21AWD9SYF4Z5M","hasVariants":false,"descriptiveAssets":[{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e"},{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4c59187077a8767c919bd168a066c224/Derivates/dd52ed01ce418450cb05f7ef7eb9caf32d934b6f","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4c59187077a8767c919bd168a066c224/Derivates/dd52ed01ce418450cb05f7ef7eb9caf32d934b6f","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4c59187077a8767c919bd168a066c224/Derivates/dd52ed01ce418450cb05f7ef7eb9caf32d934b6f"}],"isCustomerRecipe":false,"recipeIngredientGroups":[{"localId":"com.vorwerk.ingredients.Ingredient-rpf-322","optional":false,"quantity":{"value":260},"unit_ref":"11-unit-rdpf3","preparation":"","unitNotation":"g","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-322","ingredientNotation":"Kokosraspeln","isOwned":false,"shoppingCategory_ref":"ShoppingCategory-rpf-6","id":"01JBS3P8KH8CJ8E30FRQKV93G6"},{"localId":"com.vorwerk.ingredients.Ingredient-rpf-319","optional":false,"quantity":{"value":400},"unit_ref":"11-unit-rdpf3","preparation":"","unitNotation":"g","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-319","ingredientNotation":"gezuckerte Kondensmilch","isOwned":false,"shoppingCategory_ref":"ShoppingCategory-rpf-2","id":"01JBS3P8KH6QXRE9MT0R07VNMK"},{"localId":"com.vorwerk.ingredients.Ingredient-rpf-17","optional":false,"quantity":{"value":40},"unit_ref":"11-unit-rdpf3","preparation":", in Stücken","unitNotation":"g","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-17","ingredientNotation":"Butter","isOwned":false,"shoppingCategory_ref":"ShoppingCategory-rpf-2","id":"01JBS3P8KHPHEPXYNN0A72G9GJ"}]}]}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/add-managed-collection.txt000066400000000000000000000461541476166105100270170ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list HTTP/2.0 content-length: 28 sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Android WebView";v="120" content-type: application/json x-requested-with: xmlhttprequest sec-ch-ua-mobile: ?1 user-agent: nwot-mobile-android/1.7.7 (Android/12) sec-ch-ua-platform: "Android" accept: */* origin: https://ch.tmmobile.vorwerk-digital.com sec-fetch-site: same-origin sec-fetch-mode: cors sec-fetch-dest: empty referer: https://ch.tmmobile.vorwerk-digital.com/collection/de-CH/p/col500561 accept-encoding: gzip, deflate, br accept-language: de-CH,de;q=0.9,en-US;q=0.8,en;q=0.7 cookie: current-locale=de_CH cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg cookie: cookidooMobileAnalyticsEnabled=false cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083 {"collectionId":"col500561"} HTTP/2.0 201 content-type: application/json date: Wed, 06 Nov 2024 22:28:13 GMT x-frame-options: DENY etag: "068de67240a5c47f1da31a6e5ae13d15e" x-content-type-options: nosniff set-cookie: current-locale=de_CH; Path=/organize; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083; path=/; secure; expires=Fri, 06 Dec 2024 22:28:13 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg; path=/; secure; expires=Fri, 06 Dec 2024 22:28:13 GMT; httponly x-request-id: 84db914613ea4b90c02f68e6b29f63b9;auth-proxy-organize-authproxy:a3e20173-237c-49c8-9104-25bce34b5d2d;auth-proxy-organize-authproxy:589ef45b-551a-444a-a25b-2956f1cab2c3;organize-ssi-nginx:d6f9938d4202d4c37a274cda21a40dad;organize:5107dc97-fe8f-4dc2-8248-115d66d87787 x-xss-protection: 0 strict-transport-security: max-age=31536000 x-cache: Miss from cloudfront via: 1.1 7b1453554724e38e8ddaa890cda58f10.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: RXS9iuZfrF6duVlYYyEVMXGUHiaoR-T2XtqiBWRtBd771hLB5MZn1Q== content-length: 14890 {"message":"Kollektion wurde in Meine Rezepte gespeichert!","content":{"id":"col500561","userId":"4a74c102-3ee6-4bcd-9227-2c403900b8de","collectionId":"col500561","created":"2024-11-06T22:28:13.202+00:00","modified":"2024-11-06T22:28:13.202+00:00","title":"Schneeweiss und Zuckersüss","asciiTitle":"Schneeweiss und Zuckersuss","description":"Schneeweisse Delikatessen wie Pralinen und cremige Desserts sind ein wahres Fest für den Gaumen. In dieser Kollektion entdeckst du einige zuckersüsse Ideen, die mühelos mit deinem Thermomix® zubereitet werden können.","chapters":[{"title":"Schneeweiss und Zuckersüss","recipeIds":["r907016","r907015","r907014","r907013","r437886","r907056"],"recipes":[{"id":"r907016","title":"Mini-Pavlova mit Orangen","type":"VORWERK","asciiTitle":"Mini-Pavlova mit Orangen","favoriteCount":null,"portion":null,"prepTime":"1200.0","totalTime":"6600.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006"}},"squareRetinaImage":"https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg","squareImageSrcSet":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 2x","responsiveImageSizes":"(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px","landscapeImage":"https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg","squareImage":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg","responsiveImageSrcset":"https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 276w"},{"id":"r907015","title":"Kokos Pralinen","type":"VORWERK","asciiTitle":"Kokos Pralinen","favoriteCount":null,"portion":null,"prepTime":"2700.0","totalTime":"32400.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e"}},"squareRetinaImage":"https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg","squareImageSrcSet":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 2x","responsiveImageSizes":"(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px","landscapeImage":"https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg","squareImage":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg","responsiveImageSrcset":"https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 276w"},{"id":"r907014","title":"Quarkcreme mit weisser Schoggi","type":"VORWERK","asciiTitle":"Quarkcreme mit weisser Schoggi","favoriteCount":null,"portion":null,"prepTime":"600.0","totalTime":"4200.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485"}},"squareRetinaImage":"https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg","squareImageSrcSet":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 2x","responsiveImageSizes":"(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px","landscapeImage":"https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg","squareImage":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg","responsiveImageSrcset":"https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 276w"},{"id":"r907013","title":"Pannacotta mit Ananaskompott","type":"VORWERK","asciiTitle":"Pannacotta mit Ananaskompott","favoriteCount":null,"portion":null,"prepTime":"2400.0","totalTime":"16800.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb"}},"squareRetinaImage":"https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg","squareImageSrcSet":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 2x","responsiveImageSizes":"(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px","landscapeImage":"https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg","squareImage":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg","responsiveImageSrcset":"https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 276w"},{"id":"r437886","title":"Zimtsterne","type":"VORWERK","asciiTitle":"Zimtsterne","favoriteCount":null,"portion":null,"prepTime":"2700.0","totalTime":"5400.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d"}},"squareRetinaImage":"https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg","squareImageSrcSet":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 2x","responsiveImageSizes":"(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px","landscapeImage":"https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg","squareImage":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg","responsiveImageSrcset":"https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 276w"},{"id":"r907056","title":"Weisse Schoggimilch","type":"VORWERK","asciiTitle":"Weisse Schoggimilch","favoriteCount":null,"portion":null,"prepTime":"480.0","totalTime":"480.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7"}},"squareRetinaImage":"https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg","squareImageSrcSet":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 2x","responsiveImageSizes":"(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px","landscapeImage":"https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg","squareImage":"https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg","responsiveImageSrcset":"https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 276w"}]}],"recipeCount":6,"version":0,"author":"Vorwerk","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/collection/ras/Assets/38cf56dff995c01c9a4ce2ce3b9f0bf8/Derivates/d8d563384bbd2652791aedfcb3200dde0e6221cb","portrait":"","landscape":""}},"listType":"MANAGEDLIST","uid":"col500561","listTypeName":"MANAGEDLIST"},"code":null}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/add-recipe-to-calendar.txt000066400000000000000000000111061476166105100267150ustar00rootroot00000000000000PUT https://gb.tmmobile.vorwerk-digital.com/planning/en-GB/api/my-day HTTP/2.0 accept: application/vnd.vorwerk.planning.my-day.mobile+json accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFhOTc2OWQxLTUxMGUtNDM0NS04YzQxLTc4MTRhYTA3NzMxMiJ9.eyJhdWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiYXV0aF90aW1lIjoxNzQxMTE0MzY4LCJlbWFpbCI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20iLCJleHAiOjE3NDExNTc1NjgsImlhdCI6MTc0MTExNDM2OCwiaXNzIjoiaHR0cHM6Ly9jaWFtLnByb2QuY29va2lkb28udm9yd2Vyay1kaWdpdGFsLmNvbSIsImlzdWIiOiJiYjZlMTU1Mi00ODM1LTQxM2UtYTc0Yi01N2EwMDUzNjQ1NjgiLCJqdGkiOiIwNWQzMWM5ZC1kMjAxLTQ2NzgtOGVjYy00NmE5MjlhM2E2YmYiLCJyb2xlcyI6WyJST0xFX1ZPUldFUktDVVNUT01FUiIsIlVTRVIiXSwic2NvcGVzIjpbImVtYWlsIiwiZ3JvdXBzIiwib2ZmbGluZSIsIm9mZmxpbmVfYWNjZXNzIiwib3BlbmlkIiwicHJvZmlsZSIsInJvbGVzIl0sInNpZCI6IjA4NmQzOGQ3LTFhN2ItNGY2ZS05ZmNlLWMzOTQxYzRmODYwNSIsInN1YiI6IjRhNzRjMTAyLTNlZTYtNGJjZC05MjI3LTJjNDAzOTAwYjhkZSIsInVhX2hhc2giOiIxMWQwZTM0ZjZhZDgyNTgzNGE0YTFhMDVjMTQ4NzA1MCJ9.XnAEoF3CznAmVFHiH7rWu89zatKoL50jp09faO1JORdMwmreX0BvMAgyHQPIgZkK8zSqyHCHitMUjG4VpSrVfvJ1zcUkpxryeThJku1Fu69GLVpIDpCeCqsXEh8kKbe47w9lBkehwzkU7campXGZA1xin92OsQ-M45pwkowYGq0ufq0WvOimBXvkCZZdWc4ssWAmSlxTByqIjSq-J6ta2piOOhXjX_X_J5Z_y31-2pP8isDllNTzdHOzlng4yKd2bELKImr8TrZ9coAkpSfMweq1AoLx9k0V0iD0Jv0d2Fx0LiLUWURhLJqSHsXSCPWGe73Z9rUfZxqEpkkrLBQTxQ content-type: application/json; charset=UTF-8 content-length: 47 accept-encoding: gzip {"dayKey":"2025-03-04","recipeIds":["r214846"]} HTTP/2.0 200 content-type: application/vnd.vorwerk.planning.my-day.mobile+json date: Tue, 04 Mar 2025 18:56:05 GMT x-request-id: b9b51b57894b329cf973d889c2f033e1;auth-proxy-planning-authproxy:ed5498e1-04c2-4c9e-a59e-e2566d4e4ae2;auth-proxy-planning-authproxy:8109239c-51ee-4a2b-ad6b-e83231c597a4;planning-ssi-nginx:791cdd0920641a1e0a11d1c0d640a646;planning:23c3807b-ceb2-4cc9-bd4e-1e483d4563fa set-cookie: current-locale=en_GB; Path=/planning; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=971cb8b4515cd3a725aa7d58adf32c4d6285eb9766c280ede4134128ca74c3df; path=/; secure; expires=Thu, 03 Apr 2025 18:56:05 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFhOTc2OWQxLTUxMGUtNDM0NS04YzQxLTc4MTRhYTA3NzMxMiJ9.eyJhdWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiYXV0aF90aW1lIjoxNzQxMTE0MzY4LCJlbWFpbCI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20iLCJleHAiOjE3NDExNTc1NjgsImlhdCI6MTc0MTExNDM2OCwiaXNzIjoiaHR0cHM6Ly9jaWFtLnByb2QuY29va2lkb28udm9yd2Vyay1kaWdpdGFsLmNvbSIsImlzdWIiOiJiYjZlMTU1Mi00ODM1LTQxM2UtYTc0Yi01N2EwMDUzNjQ1NjgiLCJqdGkiOiIwNWQzMWM5ZC1kMjAxLTQ2NzgtOGVjYy00NmE5MjlhM2E2YmYiLCJyb2xlcyI6WyJST0xFX1ZPUldFUktDVVNUT01FUiIsIlVTRVIiXSwic2NvcGVzIjpbImVtYWlsIiwiZ3JvdXBzIiwib2ZmbGluZSIsIm9mZmxpbmVfYWNjZXNzIiwib3BlbmlkIiwicHJvZmlsZSIsInJvbGVzIl0sInNpZCI6IjA4NmQzOGQ3LTFhN2ItNGY2ZS05ZmNlLWMzOTQxYzRmODYwNSIsInN1YiI6IjRhNzRjMTAyLTNlZTYtNGJjZC05MjI3LTJjNDAzOTAwYjhkZSIsInVhX2hhc2giOiIxMWQwZTM0ZjZhZDgyNTgzNGE0YTFhMDVjMTQ4NzA1MCJ9.XnAEoF3CznAmVFHiH7rWu89zatKoL50jp09faO1JORdMwmreX0BvMAgyHQPIgZkK8zSqyHCHitMUjG4VpSrVfvJ1zcUkpxryeThJku1Fu69GLVpIDpCeCqsXEh8kKbe47w9lBkehwzkU7campXGZA1xin92OsQ-M45pwkowYGq0ufq0WvOimBXvkCZZdWc4ssWAmSlxTByqIjSq-J6ta2piOOhXjX_X_J5Z_y31-2pP8isDllNTzdHOzlng4yKd2bELKImr8TrZ9coAkpSfMweq1AoLx9k0V0iD0Jv0d2Fx0LiLUWURhLJqSHsXSCPWGe73Z9rUfZxqEpkkrLBQTxQ; path=/; secure; expires=Thu, 03 Apr 2025 18:56:05 GMT; httponly vary: Accept-Encoding strict-transport-security: max-age=31536000 x-xss-protection: 0 x-frame-options: DENY x-content-type-options: nosniff x-cache: Miss from cloudfront via: 1.1 3b9bc30854f4e71bb0e665c24e7125ba.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: -H026UZ6fMrnVTmm9U53AVNYfbb6j8ecJ5nFS8Z9pEtOx_QTUACUPw== content-length: 927 {"message":"Recipe Waffles planned for 4 Mar 2025!","content":{"id":"2025-03-04","title":"2025-03-04","dayKey":"2025-03-04","created":"2025-03-04T18:56:05.424+00:00","modified":"2025-03-04T18:56:05.424+00:00","recipes":[{"id":"r214846","title":"Waffles","totalTime":"1500.0","locale":"gb","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3"}}}],"customerRecipeIds":[],"author":"4a74c102-3ee6-4bcd-9227-2c403900b8de"},"code":null}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/add-recipe-to-custom-collection.txt000066400000000000000000000133551476166105100306170ustar00rootroot00000000000000PUT https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX HTTP/2.0 accept: application/vnd.vorwerk.organize.custom-list.mobile+json accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg content-type: application/json; charset=UTF-8 content-length: 25 accept-encoding: gzip {"recipeIds":["r907015"]} HTTP/2.0 200 content-type: application/vnd.vorwerk.organize.custom-list.mobile+json date: Wed, 06 Nov 2024 22:35:24 GMT etag: W/"0423151d52416f4775121dbb213274b08" set-cookie: current-locale=de_CH; Path=/organize; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083; path=/; secure; expires=Fri, 06 Dec 2024 22:35:24 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg; path=/; secure; expires=Fri, 06 Dec 2024 22:35:24 GMT; httponly vary: Accept-Encoding x-content-type-options: nosniff x-request-id: d7ef7cfbe2214cabb985a34a5c8e16f3;auth-proxy-organize-authproxy:9d5d7492-0814-4c3b-8a8e-a4d3f7d08dd1;auth-proxy-organize-authproxy:7a1a50cc-fbd0-411f-be71-0cd802d1a690;organize-ssi-nginx:563d8fd9f72b88e7cf3c1a9cbf45030c;organize:1a50bdbe-bcd5-4b64-9841-a499e7955254 strict-transport-security: max-age=31536000 x-xss-protection: 0 x-frame-options: DENY x-cache: Miss from cloudfront via: 1.1 15a25f000172c4183886f5e8d467c1d8.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: WgTHkHcVLY7OEj3Wu_eEP5OebqvImKFOWN7X9c9eZs8jEM-SZzaBxA== content-length: 1575 {"message":"Rezeptliste wurde erfolgreich aktualisiert!","content":{"id":"01JC1SRPRSW0SHE0AK8GCASABX","title":"Testliste1","version":3,"created":"2024-11-06T22:33:18.873+00:00","modified":"2024-11-06T22:35:24.467+00:00","chapters":[{"title":"","recipes":[{"id":"r907015","title":"Kokos Pralinen","type":"VORWERK","totalTime":"32400.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e"}}}]}],"assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e"}},"sharedListId":null,"shared":false,"listType":"CUSTOMLIST","author":"4a74c102-3ee6-4bcd-9227-2c403900b8de"},"code":null}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/clear-shopping-list.txt000066400000000000000000000021041476166105100264130ustar00rootroot00000000000000DELETE https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH HTTP/2.0 content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ accept-encoding: gzip content-length: 0 HTTP/2.0 200 content-type: application/json; charset=utf-8 date: Sun, 03 Nov 2024 13:35:55 GMT set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:35:55 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:35:55 GMT; httponly strict-transport-security: max-age=31536000 cache-control: no-cache, no-store, must-revalidate x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: E_xQNhgBrgJ63GHGXcjPj33NMIcU3MNa0cJa2n4wNT1MjiWaFitvJA== content-length: 87 {"message":"Von der Einkaufsliste entfernt","data":{"recipes":[],"additionalItems":[]}}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/edit-additional-item-ownership.txt000066400000000000000000000023211476166105100305330ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/ownership/edit HTTP/2.0 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ content-type: application/json; charset=UTF-8 content-length: 103 accept-encoding: gzip {"additionalItems":[{"id":"01JBS3VWVG6CRM8NG4TADPMHZG","isOwned":true,"ownedTimestamp":1730641035146}]} HTTP/2.0 200 content-type: application/json; charset=utf-8 date: Sun, 03 Nov 2024 13:37:15 GMT set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:37:15 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:37:15 GMT; httponly strict-transport-security: max-age=31536000 cache-control: no-cache, no-store, must-revalidate x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: NayAXAt89oNyO3Eb8mixx4a34vDdR6_4cahRgt_z2C2f1XRUyAU6Gw== content-length: 109 {"data":[{"id":"01JBS3VWVG6CRM8NG4TADPMHZG","name":"Fleisch","isOwned":true,"ownedTimestamp":1730641035146}]}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/edit-additional-item.txt000066400000000000000000000022451476166105100265240ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/edit HTTP/2.0 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ content-type: application/json; charset=UTF-8 content-length: 72 accept-encoding: gzip {"additionalItems":[{"id":"01JBS3VWVG6CRM8NG4TADPMHZG","name":"Fisch"}]} HTTP/2.0 200 content-type: application/json; charset=utf-8 date: Sun, 03 Nov 2024 13:38:01 GMT set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:38:01 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:38:01 GMT; httponly strict-transport-security: max-age=31536000 cache-control: no-cache, no-store, must-revalidate x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: aE0jA_dr_AyMXMANqbsEWEHjXiIno-ecp0UeQL_0fMJX2wSyOwWQVA== content-length: 107 {"data":[{"id":"01JBS3VWVG6CRM8NG4TADPMHZG","name":"Fisch","isOwned":true,"ownedTimestamp":1730641035146}]}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/edit-custom-collection.txt000066400000000000000000000111741476166105100271240ustar00rootroot00000000000000PUT https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX HTTP/2.0 accept: application/vnd.vorwerk.organize.custom-list.mobile+json accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg content-type: application/json; charset=UTF-8 content-length: 22 accept-encoding: gzip {"title":"Testliste1"} HTTP/2.0 200 content-type: application/vnd.vorwerk.organize.custom-list.mobile+json date: Wed, 06 Nov 2024 22:34:05 GMT x-content-type-options: nosniff vary: Accept-Encoding set-cookie: current-locale=de_CH; Path=/organize; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083; path=/; secure; expires=Fri, 06 Dec 2024 22:34:05 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg; path=/; secure; expires=Fri, 06 Dec 2024 22:34:05 GMT; httponly x-xss-protection: 0 x-request-id: 35ef161110c6c4d3d58ec3cfb94be8f6;auth-proxy-organize-authproxy:070df21a-a834-4f61-b6df-74e42f2d4f35;auth-proxy-organize-authproxy:7963e687-6140-4322-bcf1-328b6c7cbb6e;organize-ssi-nginx:954fe59f7b66effe80349b1ccb8ab4bd;organize:a3f2204d-0075-406c-a143-7c7a187af5ef x-frame-options: DENY strict-transport-security: max-age=31536000 etag: W/"0f0622f2438a40eec36c6bf830a3c4836" x-cache: Miss from cloudfront via: 1.1 15a25f000172c4183886f5e8d467c1d8.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: rFSHBVg3vC5EbiHckBBBRvngEizhOC5fN2YU2OxkEkYFTc-keeoAlg== content-length: 442 {"message":"Rezeptliste wurde erfolgreich aktualisiert!","content":{"id":"01JC1SRPRSW0SHE0AK8GCASABX","title":"Testliste1","version":2,"created":"2024-11-06T22:33:18.873+00:00","modified":"2024-11-06T22:34:05.817+00:00","chapters":[{"title":"","recipes":[]}],"assets":{"images":{"square":"","portrait":"","landscape":""}},"sharedListId":null,"shared":false,"listType":"CUSTOMLIST","author":"4a74c102-3ee6-4bcd-9227-2c403900b8de"},"code":null}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/edit-ingredient-ownership.txt000066400000000000000000000027451476166105100276310ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/owned-ingredients/ownership/edit HTTP/2.0 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJy content-type: application/json; charset=UTF-8 content-length: 99 accept-encoding: gzip {"ingredients":[{"id":"01JBS24P4DVD34T4HS2KM292VT","isOwned":true,"ownedTimestamp":1730640907930}]} HTTP/2.0 200 content-type: application/json; charset=utf-8 date: Sun, 03 Nov 2024 13:35:08 GMT set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:35:08 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:35:08 GMT; httponly strict-transport-security: max-age=31536000 cache-control: no-cache, no-store, must-revalidate x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: KKfrN-dy1h1SVj4ffetiaEdn7x6XSyReblY9GQzamJfJ1iuww4w9Bg== content-length: 388 {"data":[{"id":"01JBS24P4DVD34T4HS2KM292VT","isOwned":true,"localId":"com.vorwerk.ingredients.Ingredient-rpf-9","optional":false,"quantity":{"value":200},"unit_ref":"11-unit-rdpf3","preparation":"","unitNotation":"g","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-9","ingredientNotation":"Zucker","shoppingCategory_ref":"ShoppingCategory-rpf-6","ownedTimestamp":1730640907930}]}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/get-custom-collections.txt000066400000000000000000000112771476166105100271450ustar00rootroot00000000000000GET https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list HTTP/2.0 accept: application/vnd.vorwerk.organize.custom-list.mobile+json content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg accept-encoding: gzip if-none-match: W/"07883f35dc581d1b5fd207fedb85cf12f" content-length: 0 HTTP/2.0 200 content-type: application/vnd.vorwerk.organize.custom-list.mobile+json date: Wed, 06 Nov 2024 22:38:32 GMT x-content-type-options: nosniff vary: Accept-Encoding x-xss-protection: 0 set-cookie: current-locale=de_CH; Path=/organize; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083; path=/; secure; expires=Fri, 06 Dec 2024 22:38:32 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg; path=/; secure; expires=Fri, 06 Dec 2024 22:38:32 GMT; httponly x-request-id: 873104f0cd84669ca16ef489c99d5ae5;auth-proxy-organize-authproxy:9ef1a115-5fd1-442c-9f03-4491c3af5f5c;auth-proxy-organize-authproxy:016d219d-1081-40d2-8719-40d82c9ca362;organize-ssi-nginx:c6d9dce04b2961bcb7e6bb5f6dd25380;organize:4d52ab6b-4549-4448-ab69-2a7aebac135e strict-transport-security: max-age=31536000 x-frame-options: DENY etag: W/"013137b8c173fe4d3901fb2e1f64c3b28" x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: mSiHvRtAsTjGynJ3dqUDzVkwUvmhge1VjeIq7PVRaVyatDH6GQfq5g== content-length: 489 {"customlists":[{"id":"01JC1SRPRSW0SHE0AK8GCASABX","title":"Testliste1","version":4,"created":"2024-11-06T22:33:18.873+00:00","modified":"2024-11-06T22:36:25.914+00:00","chapters":[{"title":"","recipes":[]}],"assets":{"images":{"square":"","portrait":"","landscape":""}},"sharedListId":null,"shared":false,"listType":"CUSTOMLIST","author":"4a74c102-3ee6-4bcd-9227-2c403900b8de"}],"page":{"page":0,"totalPages":1,"totalElements":1},"links":{"self":"/organize/de-CH/api/custom-list?page=0"}}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/get-managed-collections.txt000066400000000000000000000216011476166105100272170ustar00rootroot00000000000000GET https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list HTTP/2.0 accept: application/vnd.vorwerk.organize.managed-list.mobile+json content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg accept-encoding: gzip if-none-match: "0d3d0decaac322fdd53577e90812303a7" content-length: 0 HTTP/2.0 200 content-type: application/vnd.vorwerk.organize.managed-list.mobile+json date: Wed, 06 Nov 2024 22:28:26 GMT strict-transport-security: max-age=31536000 set-cookie: current-locale=de_CH; Path=/organize; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083; path=/; secure; expires=Fri, 06 Dec 2024 22:28:26 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg; path=/; secure; expires=Fri, 06 Dec 2024 22:28:26 GMT; httponly x-request-id: ad417e4ed92f7f7a99b40edb3cd7d9da;auth-proxy-organize-authproxy:b1ed2ac4-4ad6-4dae-9b26-07caa762df37;auth-proxy-organize-authproxy:ef154551-9a1e-4136-9b68-83ae663955bc;organize-ssi-nginx:0238253888dce559897921999bb97ea8;organize:4ddce020-996b-468c-95c6-98832b3085df etag: W/"04f45ec90176310652f3c12555f863d36" x-xss-protection: 0 x-frame-options: DENY vary: Accept-Encoding x-content-type-options: nosniff x-cache: Miss from cloudfront via: 1.1 15a25f000172c4183886f5e8d467c1d8.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: oN2iyv0mAovIXq2_rBMpS53o7WGd6GRKdX4Z3P52Ko2572aWm_2NPA== content-length: 4777 {"managedlists":[{"id":"col500561","created":"2024-11-06T22:28:13.202+00:00","modified":"2024-11-06T22:28:13.202+00:00","title":"Schneeweiss und Zuckersüss","description":"Schneeweisse Delikatessen wie Pralinen und cremige Desserts sind ein wahres Fest für den Gaumen. In dieser Kollektion entdeckst du einige zuckersüsse Ideen, die mühelos mit deinem Thermomix® zubereitet werden können.","chapters":[{"title":"Schneeweiss und Zuckersüss","recipes":[{"id":"r907016","title":"Mini-Pavlova mit Orangen","type":"VORWERK","totalTime":"6600.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006"}}},{"id":"r907015","title":"Kokos Pralinen","type":"VORWERK","totalTime":"32400.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e"}}},{"id":"r907014","title":"Quarkcreme mit weisser Schoggi","type":"VORWERK","totalTime":"4200.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485"}}},{"id":"r907013","title":"Pannacotta mit Ananaskompott","type":"VORWERK","totalTime":"16800.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb"}}},{"id":"r437886","title":"Zimtsterne","type":"VORWERK","totalTime":"5400.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d"}}},{"id":"r907056","title":"Weisse Schoggimilch","type":"VORWERK","totalTime":"480.0","locale":"","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7"}}}]}],"version":0,"author":"Vorwerk","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/collection/ras/Assets/38cf56dff995c01c9a4ce2ce3b9f0bf8/Derivates/d8d563384bbd2652791aedfcb3200dde0e6221cb","portrait":"","landscape":""}},"listType":"MANAGEDLIST"}],"page":{"page":0,"totalPages":1,"totalElements":1},"links":{"self":"/organize/de-CH/api/managed-list?page=0"}}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/get-recipe.txt000066400000000000000000000157001476166105100245610ustar00rootroot00000000000000GET https://ch.tmmobile.vorwerk-digital.com/recipes/recipe/de-CH/r907015 HTTP/2.0 accept: application/vnd.vorwerk.recipe.embedded.hal+json authorization: Basic a3VwZmVyd2Vyay1jbGllbnQtbndvdDpMczUwT04xd295U3FzMWRDZEpnZQ== user-agent: nwot-mobile-android/1.7.7 (Android/12) content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 accept-encoding: gzip content-length: 0 HTTP/2.0 200 content-type: application/vnd.vorwerk.recipe.embedded.hal+json;charset=utf-8 date: Wed, 06 Nov 2024 18:58:16 GMT strict-transport-security: max-age=31536000 cache-control: no-cache, no-store, must-revalidate x-request-id: 5ff26dc8e18f7b2a872641394efca949;auth-proxy-recipe-details:a4a4c5ef-3fe1-4888-93aa-524e2606beda;auth-proxy-recipe-details:73a89ee9-4a5a-4ffa-b7d6-2a0476a9f34b;recipe-details-nginx:5ff26dc8e18f7b2a872641394efca949 correlation-id: 5ff26dc8e18f7b2a872641394efca949 cache-fetch-status: HIT vary: Accept-Encoding x-cache: Miss from cloudfront via: 1.1 38e6d5d4bf1ff4c61fe7860d1fe50b94.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: EKx9Ost6ZZUIsCZncX4LkAM3xATsc3q8XWVgAlSsufj6nVUQAGw6cQ== content-length: 5926 {"id":"r907015","tags":[{"id":"40-recipetag-rdpf3","name":"Fingerfood"},{"id":"143-recipetag-rdpf3","name":"Vegetarisch"},{"id":"166-recipetag-rdpf3","name":"alkoholfrei"},{"id":"167-recipetag-rdpf3","name":"Glutenfrei"},{"id":"212-marketingtag-rdpf3","name":"Weihnachten"},{"id":"233-marketingtag-rdpf3","name":"Winter"},{"id":"247-marketingtag-rdpf3","name":"Geschenk"},{"id":"8-marketingtag-rdpf3","name":"Europäisch"},{"id":"209-marketingtag-rdpf3","name":"Herbst"},{"id":"230-marketingtag-rdpf3","name":"Sommer"},{"id":"228-marketingtag-rdpf3","name":"Frühling"},{"id":"238-marketingtag-rdpf3","name":"Geburtstag"},{"id":"253-marketingtag-rdpf3","name":"Party"}],"times":[{"type":"activeTime","comment":"","quantity":{"value":2700}},{"type":"totalTime","comment":"","quantity":{"value":32400}}],"title":"Kokos Pralinen","locale":"ch","status":"ok","markets":["ch"],"language":"de","categories":[{"id":"VrkNavCategory-RPF-011","title":"Desserts, Pâtisserie und Süssigkeiten","subtitle":"","defaultTitle":"Desserts and sweets"},{"id":"VrkNavCategory-RPF-020","title":"Snacks","subtitle":"","defaultTitle":"Snacks and finger food"}],"difficulty":"easy","exportDate":"2024-10-28T16:08:56Z","ingredients":[{"id":"com.vorwerk.ingredients.Ingredient-rpf-322","title":"Kokosraspeln","defaultTitle":"coconut, dried, grated, unsweetened","primaryNotation":"Kokosnuss, getrocknet, geraspelt, ungesüsst","shoppingCategory_ref":"com.vorwerk.categories.ShoppingCategory-rpf-6"},{"id":"com.vorwerk.ingredients.Ingredient-rpf-319","title":"gezuckerte Kondensmilch","defaultTitle":"condensed milk, sweetened","primaryNotation":"gezuckerte Kondensmilch","shoppingCategory_ref":"com.vorwerk.categories.ShoppingCategory-rpf-2"},{"id":"com.vorwerk.ingredients.Ingredient-rpf-17","title":"Butter","defaultTitle":"butter, unsalted (from cows' milk)","primaryNotation":"Butter","shoppingCategory_ref":"com.vorwerk.categories.ShoppingCategory-rpf-2"}],"servingSize":{"quantity":{"value":50},"unitNotation":"Stück"},"recipeUtensils":[{"utensilRef":"5460-utensil-rdpf3","utensilNotation":"Kühlschrank"}],"categories_refs":["VrkNavCategory-RPF-011","VrkNavCategory-RPF-020"],"nutritionGroups":[{"name":"","recipeNutritions":[{"quantity":1,"nutritions":[{"type":"kJ","number":275,"unittype":"kJ"},{"type":"kcal","number":65.7,"unittype":"kcal"},{"type":"protein","number":1,"unittype":"g"},{"type":"carb2","number":5.6,"unittype":"g"},{"type":"fat","number":4.7,"unittype":"g"},{"type":"dietaryFibre","number":0.8,"unittype":"g"}],"unitNotation":"Stück"}]}],"optionalDevices":[],"publicationDate":"2024-10-29T06:00:00Z","targetCountries":["ch"],"recipeStepGroups":[{"title":"","recipeSteps":[{"title":"1","formattedText":"200 g Kokosraspeln in den Mixtopf geben und 15 Sek./Stufe 8 zerkleinern. In eine Schüssel umfüllen und beiseitestellen."},{"title":"2","formattedText":"Kondensmilch und Butter in den Mixtopf geben und 6 Min./100°C/Stufe 2 kochen."},{"title":"3","formattedText":"Gemahlene Kokosraspeln zugeben und 2 Min./Stufe 4 vermischen. In eine Schüssel umfüllen und mindestens 8 Stunden, am besten über Nacht, in den Kühlschrank stellen."},{"title":"4","formattedText":"60 g Kokosraspeln auf einen Teller geben. Aus der Kokos-Milch-Mischung kleine Kugeln formen (ca. Ø 1 cm), in den Kokosraspeln wälzen und bis zum Servieren kühl stellen."}]}],"additionalDevices":[],"descriptiveAssets":[{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e"},{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4c59187077a8767c919bd168a066c224/Derivates/dd52ed01ce418450cb05f7ef7eb9caf32d934b6f","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4c59187077a8767c919bd168a066c224/Derivates/dd52ed01ce418450cb05f7ef7eb9caf32d934b6f","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4c59187077a8767c919bd168a066c224/Derivates/dd52ed01ce418450cb05f7ef7eb9caf32d934b6f"}],"thermomixVersions":["TM5","TM6"],"additionalInformation":[{"type":"VrkRecipeAdditionalInformationType","content":"Die Pralinen sollen kühl aufbewahrt und gegessen werden. Sie können auch eingefroren werden."}],"recipeIngredientGroups":[{"title":"","recipeIngredients":[{"localId":"com.vorwerk.ingredients.Ingredient-rpf-322","optional":false,"quantity":{"value":260},"unit_ref":"11-unit-rdpf3","preparation":"","unitNotation":"g","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-322","ingredientNotation":"Kokosraspeln"},{"localId":"com.vorwerk.ingredients.Ingredient-rpf-319","optional":false,"quantity":{"value":400},"unit_ref":"11-unit-rdpf3","preparation":"","unitNotation":"g","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-319","ingredientNotation":"gezuckerte Kondensmilch"},{"localId":"com.vorwerk.ingredients.Ingredient-rpf-17","optional":false,"quantity":{"value":40},"unit_ref":"11-unit-rdpf3","preparation":", in Stücken","unitNotation":"g","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-17","ingredientNotation":"Butter"}]}],"inCollections":[{"id":"col500561","title":"Schneeweiss und Zuckersüss","recipesCount":{"value":6,"text":"other"},"market":"ch","descriptiveAssets":[{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/collection/ras/Assets/38cf56dff995c01c9a4ce2ce3b9f0bf8/Derivates/d8d563384bbd2652791aedfcb3200dde0e6221cb"}]}]}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/get-recipes-for-calendar-week.txt000066400000000000000000000126661476166105100302400ustar00rootroot00000000000000GET https://gb.tmmobile.vorwerk-digital.com/planning/en-GB/api/my-week/2025-03-03 HTTP/2.0 accept: application/vnd.vorwerk.planning.my-day.mobile+json content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFhOTc2OWQxLTUxMGUtNDM0NS04YzQxLTc4MTRhYTA3NzMxMiJ9.eyJhdWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiYXV0aF90aW1lIjoxNzQxMTE0MzY4LCJlbWFpbCI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20iLCJleHAiOjE3NDExNTc1NjgsImlhdCI6MTc0MTExNDM2OCwiaXNzIjoiaHR0cHM6Ly9jaWFtLnByb2QuY29va2lkb28udm9yd2Vyay1kaWdpdGFsLmNvbSIsImlzdWIiOiJiYjZlMTU1Mi00ODM1LTQxM2UtYTc0Yi01N2EwMDUzNjQ1NjgiLCJqdGkiOiIwNWQzMWM5ZC1kMjAxLTQ2NzgtOGVjYy00NmE5MjlhM2E2YmYiLCJyb2xlcyI6WyJST0xFX1ZPUldFUktDVVNUT01FUiIsIlVTRVIiXSwic2NvcGVzIjpbImVtYWlsIiwiZ3JvdXBzIiwib2ZmbGluZSIsIm9mZmxpbmVfYWNjZXNzIiwib3BlbmlkIiwicHJvZmlsZSIsInJvbGVzIl0sInNpZCI6IjA4NmQzOGQ3LTFhN2ItNGY2ZS05ZmNlLWMzOTQxYzRmODYwNSIsInN1YiI6IjRhNzRjMTAyLTNlZTYtNGJjZC05MjI3LTJjNDAzOTAwYjhkZSIsInVhX2hhc2giOiIxMWQwZTM0ZjZhZDgyNTgzNGE0YTFhMDVjMTQ4NzA1MCJ9.XnAEoF3CznAmVFHiH7rWu89zatKoL50jp09faO1JORdMwmreX0BvMAgyHQPIgZkK8zSqyHCHitMUjG4VpSrVfvJ1zcUkpxryeThJku1Fu69GLVpIDpCeCqsXEh8kKbe47w9lBkehwzkU7campXGZA1xin92OsQ-M45pwkowYGq0ufq0WvOimBXvkCZZdWc4ssWAmSlxTByqIjSq-J6ta2piOOhXjX_X_J5Z_y31-2pP8isDllNTzdHOzlng4yKd2bELKImr8TrZ9coAkpSfMweq1AoLx9k0V0iD0Jv0d2Fx0LiLUWURhLJqSHsXSCPWGe73Z9rUfZxqEpkkrLBQTxQ accept-encoding: gzip if-none-match: W/"0304a93acd8aa67a2907f7a9da8d2a081" content-length: 0 HTTP/2.0 200 content-type: application/vnd.vorwerk.planning.my-day.mobile+json date: Tue, 04 Mar 2025 19:03:44 GMT etag: W/"0b71bf06b853ea978f0c2e694c512bd60" vary: Accept-Encoding x-request-id: d5da0e257db33760ba3cb183ade6af24;auth-proxy-planning-authproxy:46d220eb-7e9a-461e-b872-74ebda9799dd;auth-proxy-planning-authproxy:9ace2009-46b7-40bd-8eba-821a275a4632;planning-ssi-nginx:1f62e6fbd24f2d430e5afebb9a9e7ba0;planning:e9ed9fd9-4acc-4569-b0f6-ae804be36d87 x-content-type-options: nosniff x-frame-options: DENY strict-transport-security: max-age=31536000 x-xss-protection: 0 set-cookie: current-locale=en_GB; Path=/planning; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=971cb8b4515cd3a725aa7d58adf32c4d6285eb9766c280ede4134128ca74c3df; path=/; secure; expires=Thu, 03 Apr 2025 19:03:44 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFhOTc2OWQxLTUxMGUtNDM0NS04YzQxLTc4MTRhYTA3NzMxMiJ9.eyJhdWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiYXV0aF90aW1lIjoxNzQxMTE0MzY4LCJlbWFpbCI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20iLCJleHAiOjE3NDExNTc1NjgsImlhdCI6MTc0MTExNDM2OCwiaXNzIjoiaHR0cHM6Ly9jaWFtLnByb2QuY29va2lkb28udm9yd2Vyay1kaWdpdGFsLmNvbSIsImlzdWIiOiJiYjZlMTU1Mi00ODM1LTQxM2UtYTc0Yi01N2EwMDUzNjQ1NjgiLCJqdGkiOiIwNWQzMWM5ZC1kMjAxLTQ2NzgtOGVjYy00NmE5MjlhM2E2YmYiLCJyb2xlcyI6WyJST0xFX1ZPUldFUktDVVNUT01FUiIsIlVTRVIiXSwic2NvcGVzIjpbImVtYWlsIiwiZ3JvdXBzIiwib2ZmbGluZSIsIm9mZmxpbmVfYWNjZXNzIiwib3BlbmlkIiwicHJvZmlsZSIsInJvbGVzIl0sInNpZCI6IjA4NmQzOGQ3LTFhN2ItNGY2ZS05ZmNlLWMzOTQxYzRmODYwNSIsInN1YiI6IjRhNzRjMTAyLTNlZTYtNGJjZC05MjI3LTJjNDAzOTAwYjhkZSIsInVhX2hhc2giOiIxMWQwZTM0ZjZhZDgyNTgzNGE0YTFhMDVjMTQ4NzA1MCJ9.XnAEoF3CznAmVFHiH7rWu89zatKoL50jp09faO1JORdMwmreX0BvMAgyHQPIgZkK8zSqyHCHitMUjG4VpSrVfvJ1zcUkpxryeThJku1Fu69GLVpIDpCeCqsXEh8kKbe47w9lBkehwzkU7campXGZA1xin92OsQ-M45pwkowYGq0ufq0WvOimBXvkCZZdWc4ssWAmSlxTByqIjSq-J6ta2piOOhXjX_X_J5Z_y31-2pP8isDllNTzdHOzlng4yKd2bELKImr8TrZ9coAkpSfMweq1AoLx9k0V0iD0Jv0d2Fx0LiLUWURhLJqSHsXSCPWGe73Z9rUfZxqEpkkrLBQTxQ; path=/; secure; expires=Thu, 03 Apr 2025 19:03:44 GMT; httponly x-cache: Miss from cloudfront via: 1.1 4b04b092439272f69394f0b36f3b262a.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: xETZq9j2L3l-3wPZW4jMJRTyQSI6BQ1ViWJpB7E00LIaDJ2Oo52_SA== content-length: 1728 {"myDays":[{"id":"2025-03-04","title":"2025-03-04","dayKey":"2025-03-04","created":"2025-03-04T18:56:05.424+00:00","modified":"2025-03-04T18:56:05.424+00:00","recipes":[{"id":"r214846","title":"Waffles","totalTime":"1500.0","locale":"gb","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3"}}}],"customerRecipeIds":[],"author":"4a74c102-3ee6-4bcd-9227-2c403900b8de"},{"id":"2025-03-05","title":"2025-03-05","dayKey":"2025-03-05","created":"2025-03-04T19:00:02.773+00:00","modified":"2025-03-04T19:00:02.773+00:00","recipes":[{"id":"r338888","title":"Moroccan Mint Tea","totalTime":"1500.0","locale":"gb","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/47d318332476db821ab3d1c5414f772e/Derivates/8d7d71b9c549cb55b11cc965a6b47be108ab44e5","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/47d318332476db821ab3d1c5414f772e/Derivates/8d7d71b9c549cb55b11cc965a6b47be108ab44e5","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/47d318332476db821ab3d1c5414f772e/Derivates/8d7d71b9c549cb55b11cc965a6b47be108ab44e5"}}}],"customerRecipeIds":[],"author":"4a74c102-3ee6-4bcd-9227-2c403900b8de"}]}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/login-old.txt000066400000000000000000000027401476166105100244210ustar00rootroot00000000000000POST https://eu.login.vorwerk.com/oauth2/token HTTP/2.0 authorization: Basic a3VwZmVyd2Vyay1jbGllbnQtbndvdDpMczUwT04xd295U3FzMWRDZEpnZQ== cookie: vrkPreAccessGranted=true accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) content-type: application/x-www-form-urlencoded content-length: 100 accept-encoding: gzip username=%40gmail.com&password=&grant_type=password HTTP/2.0 200 content-type: application/json;charset=UTF-8 date: Sun, 03 Nov 2024 13:26:28 GMT set-cookie: oauth2_authentication_session=MTczMDY0MDM4OHxEdi1CQkFFQ180SUFBUkFCRUFBQUhmLUNBQUVHYzNSeWFXNW5EQVVBQTNOcFpBWnpkSEpwYm1jTUFnQUF8S1hHUg6eze5vY1KjTwqv2abUaNJQjQy-z_6GwJDl-z8=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; HttpOnly; Secure; SameSite=None strict-transport-security: max-age=31536000 cache-control: no-store pragma: no-cache x-cache: Miss from cloudfront via: 1.1 a5b35da3a37f3b4542ea89737243a522.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: ytjjjMDmsz6HdL_6iwLgNSLr1GUdzTbqERTnNw7TR3ahTmuJlT8NQg== content-length: 4232 {"access_token":"eyJhbGci","expires_in":43199,"id_token":"eyJhbGciOiJSUzI1NiIsIm","iss":"https://eu.login.vorwerk.com/","jti":"4b027df7-8578-4cda-831f-d1fcb3ec77ad","refresh_token":"eyJhbGciOiJSUzI1NiI","scope":"marcossapi openid profile email Online offline_access","token_type":"bearer","user_name":"@gmail.com"}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/login.txt000066400000000000000000000033771476166105100236540ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/ciam/auth/token HTTP/2.0 authorization: Basic a3VwZmVyd2Vyay1jbGllbnQtbndvdDpMczUwT04xd295U3FzMWRDZEpnZQ== cookie: vrkPreAccessGranted=true accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) content-type: application/x-www-form-urlencoded content-length: 100 accept-encoding: gzip username=%40gmail.com&password=&grant_type=password HTTP/2.0 200 content-type: application/json content-length: 3138 date: Sat, 04 Jan 2025 11:31:23 GMT set-cookie: __cf_bm=BC9dKTwZV693guzfxXOunvhgsdpKpOydts___3rwQBE-1735990283-1.0.1.1-l8HIPMW_RrNcUT1cNQnvpv08kudOpXqMLfWa_B4Qcj80uxPtRvoyW7TfUl8bjmPkD6DzFDziHXACm137_n.owQ; path=/; expires=Sat, 04-Jan-25 12:01:23 GMT; domain=.ciam.prod.cookidoo.vorwerk-digital.com; HttpOnly; Secure; SameSite=None strict-transport-security: max-age=31536000 vary: Accept-Encoding x-content-type-options: nosniff x-powered-by: cidaas x-ref-number: a47bd5cb-4473-45da-8442-1a3cdf136873 x-xss-protection: 1; mode=block cf-cache-status: DYNAMIC cf-ray: 8fcae6e8589bd247-FRA correlation-id: 5a1a1307feec7b28ada36459f51713cf traceparent: 00-7d79395ceeaf44f23f4f8b9d785fa8de-d320805fd2780a03-01 x-cache: Miss from cloudfront via: 1.1 6efc112ba7faf702bfdea07c3f51a870.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: zcn9rZjMrb8aVSV862y0Zzqrh9c6XFhh9YJIIkQmL4CSN-dsf1rFNg== {"access_token":"eyJhbGci","expires_in":43199,"id_token":"eyJhbGciOiJSUzI1NiIsIm","iss":"https://eu.login.vorwerk.com/","jti":"4b027df7-8578-4cda-831f-d1fcb3ec77ad","refresh_token":"eyJhbGciOiJSUzI1NiI","scope":"marcossapi openid profile email Online offline_access","token_type":"bearer","sub":"sub_uuid"}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/refresh-old.txt000066400000000000000000000023331476166105100247450ustar00rootroot00000000000000POST https://eu.login.vorwerk.com/oauth2/token HTTP/2.0 cookie: vrkPreAccessGranted=true authorization: Basic a3VwZmVyd2Vyay1jbGllbnQtbndvdDpMczUwT04xd295U3FzMWRDZEpnZQ== accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) content-type: application/x-www-form-urlencoded content-length: 1495 accept-encoding: gzip grant_type=refresh_token&refresh_token=eyJhbGciOiJ&client_id=kupferwerk-client-nwot HTTP/2.0 200 content-type: application/json;charset=UTF-8 date: Sun, 03 Nov 2024 13:41:04 GMT pragma: no-cache strict-transport-security: max-age=31536000 cache-control: no-store x-cache: Miss from cloudfront via: 1.1 3591be88662e5675a9dc1cc4e0a9c392.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: MknpCULsh5YDGevBJtX1MMLTQ2CTKdbPBjCcJcRlXHWLVinNCIxgfg== content-length: 4272 {"access_token":"eyJhbGciOiJ","expires_in":43199,"id_token":"eyJhbGciOiJSUzI1NiI","iss":"https://eu.login.vorwerk.com/","jti":"d7b73c06-6693-46cf-af9d-63c9e082208d","refresh_token":"eyJhbGciOiJSUzI1NiIsImtp","scope":"marcossapi openid profile email Online offline_access","token_type":"bearer","user_name":"@gmail.com"}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/refresh.txt000066400000000000000000000023211476166105100241660ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/ciam/auth/token HTTP/2.0 cookie: vrkPreAccessGranted=true authorization: Basic a3VwZmVyd2Vyay1jbGllbnQtbndvdDpMczUwT04xd295U3FzMWRDZEpnZQ== accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) content-type: application/x-www-form-urlencoded content-length: 1495 accept-encoding: gzip grant_type=refresh_token&refresh_token=eyJhbGciOiJ&client_id=kupferwerk-client-nwot HTTP/2.0 200 content-type: application/json;charset=UTF-8 date: Sun, 03 Nov 2024 13:41:04 GMT pragma: no-cache strict-transport-security: max-age=31536000 cache-control: no-store x-cache: Miss from cloudfront via: 1.1 3591be88662e5675a9dc1cc4e0a9c392.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: MknpCULsh5YDGevBJtX1MMLTQ2CTKdbPBjCcJcRlXHWLVinNCIxgfg== content-length: 4272 {"access_token":"eyJhbGciOiJ","expires_in":43199,"id_token":"eyJhbGciOiJSUzI1NiI","iss":"https://eu.login.vorwerk.com/","jti":"d7b73c06-6693-46cf-af9d-63c9e082208d","refresh_token":"eyJhbGciOiJSUzI1NiIsImtp","scope":"marcossapi openid profile email Online offline_access","token_type":"bearer","sub":"sub_uuid"}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/remove-additional-item.txt000066400000000000000000000017701476166105100270760ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/remove HTTP/2.0 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ content-type: application/json; charset=UTF-8 content-length: 52 accept-encoding: gzip {"additionalItemIDs":["01JBS3VWVG6CRM8NG4TADPMHZG"]} HTTP/2.0 204 date: Sun, 03 Nov 2024 13:38:49 GMT strict-transport-security: max-age=31536000 cache-control: no-cache, no-store, must-revalidate set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:38:49 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:38:49 GMT; httponly x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: 9iCAOSk41rwVE7Uj1Jeiq4Zfm-Dd4Q4f_QceQRQD29CKJr-2nvw_DA== content-length: 0 miaucl-cookidoo-api-5292e37/docs/raw-api-requests/remove-custom-collection.txt000066400000000000000000000110751476166105100274740ustar00rootroot00000000000000DELETE https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX HTTP/2.0 accept: application/vnd.vorwerk.organize.custom-list.mobile+json content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg accept-encoding: gzip content-length: 0 HTTP/2.0 200 content-type: application/vnd.vorwerk.organize.custom-list.mobile+json date: Wed, 06 Nov 2024 22:39:30 GMT strict-transport-security: max-age=31536000 vary: Accept-Encoding set-cookie: current-locale=de_CH; Path=/organize; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083; path=/; secure; expires=Fri, 06 Dec 2024 22:39:30 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg; path=/; secure; expires=Fri, 06 Dec 2024 22:39:30 GMT; httponly x-request-id: 1fa161caa1c402ad131bb6eee2af0280;auth-proxy-organize-authproxy:f2ba11e5-a92c-45fa-90a3-d985a6ec2ead;auth-proxy-organize-authproxy:e9757a96-e77f-4392-8287-4bcd4d304ec3;organize-ssi-nginx:3d41b396bcff6c8b0c61bdadf6e67ec0;organize:67d0398c-75da-43f4-9cc5-84a1a596f2f3 x-frame-options: DENY x-xss-protection: 0 x-content-type-options: nosniff x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: LCQDTFpYRD9PXjXwRZo24-blEHL7nO3kmV24mKtwzvd5dYZT5nuUFg== content-length: 426 {"message":"Rezeptliste wurde entfernt!","content":{"id":"01JC1SRPRSW0SHE0AK8GCASABX","title":"Testliste1","version":4,"created":"2024-11-06T22:33:18.873+00:00","modified":"2024-11-06T22:36:25.914+00:00","chapters":[{"title":"","recipes":[]}],"assets":{"images":{"square":"","portrait":"","landscape":""}},"sharedListId":null,"shared":false,"listType":"CUSTOMLIST","author":"4a74c102-3ee6-4bcd-9227-2c403900b8de"},"code":null}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/remove-ingredients-for-recipe.txt000066400000000000000000000017471476166105100304020ustar00rootroot00000000000000POST https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/remove HTTP/2.0 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ content-type: application/json; charset=UTF-8 content-length: 44 accept-encoding: gzip {"recipeIDs":["01JBS3P8KHNEE21AWD9SYF4Z5M"]} HTTP/2.0 204 date: Sun, 03 Nov 2024 13:34:28 GMT strict-transport-security: max-age=31536000 cache-control: no-cache, no-store, must-revalidate set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:34:28 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:34:28 GMT; httponly x-cache: Miss from cloudfront via: 1.1 0796439594da8d89bc262ec25ca7f192.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: LiZxN7K-OInGlTb-mb7PckGLRntktH9YE4LJx24ocMC4Rw8jAiH7Hg== content-length: 0 miaucl-cookidoo-api-5292e37/docs/raw-api-requests/remove-managed-collection.txt000066400000000000000000000106251476166105100275560ustar00rootroot00000000000000DELETE https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list/col500561 HTTP/2.0 accept: application/vnd.vorwerk.organize.managed-list.mobile+json content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg accept-encoding: gzip content-length: 0 HTTP/2.0 200 content-type: application/vnd.vorwerk.organize.managed-list.mobile+json date: Wed, 06 Nov 2024 22:31:05 GMT x-request-id: 872c635d66138217d8e1160a71ddb51e;auth-proxy-organize-authproxy:c1faf8e0-217b-411c-90b1-c6f21dc95243;auth-proxy-organize-authproxy:2303bf2c-67b0-42a0-b07e-93c7e4551368;organize-ssi-nginx:5c90b1ebca5de911fcd13799ea1d2e3f;organize:c61af3c2-0883-4ed3-84bd-00d104f319bb x-content-type-options: nosniff x-xss-protection: 0 vary: Accept-Encoding x-frame-options: DENY set-cookie: current-locale=de_CH; Path=/organize; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083; path=/; secure; expires=Fri, 06 Dec 2024 22:31:05 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg; path=/; secure; expires=Fri, 06 Dec 2024 22:31:05 GMT; httponly strict-transport-security: max-age=31536000 x-cache: Miss from cloudfront via: 1.1 15a25f000172c4183886f5e8d467c1d8.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: hb2G1s_nkleZQFf6O9piMULzteAyQ3Q2r2kH6pawkGa5-ksp5DipIg== content-length: 272 {"message":"Kollektion wurde entfernt!","content":{"id":"col500561","created":null,"modified":null,"title":null,"description":null,"chapters":[],"version":0,"author":null,"assets":{"images":{"square":"","portrait":"","landscape":""}},"listType":"MANAGEDLIST"},"code":null}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/remove-recipe-from-calendar.txt000066400000000000000000000110721476166105100300050ustar00rootroot00000000000000DELETE https://gb.tmmobile.vorwerk-digital.com/planning/en-GB/api/my-day/2025-03-04/recipes/r214846 HTTP/2.0 accept: application/vnd.vorwerk.planning.my-day.mobile+json content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjFhOTc2OWQxLTUxMGUtNDM0NS04YzQxLTc4MTRhYTA3NzMxMiJ9.eyJhdWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiYXV0aF90aW1lIjoxNzQxMTE0MzY4LCJlbWFpbCI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20iLCJleHAiOjE3NDExNTc1NjgsImlhdCI6MTc0MTExNDM2OCwiaXNzIjoiaHR0cHM6Ly9jaWFtLnByb2QuY29va2lkb28udm9yd2Vyay1kaWdpdGFsLmNvbSIsImlzdWIiOiJiYjZlMTU1Mi00ODM1LTQxM2UtYTc0Yi01N2EwMDUzNjQ1NjgiLCJqdGkiOiIwNWQzMWM5ZC1kMjAxLTQ2NzgtOGVjYy00NmE5MjlhM2E2YmYiLCJyb2xlcyI6WyJST0xFX1ZPUldFUktDVVNUT01FUiIsIlVTRVIiXSwic2NvcGVzIjpbImVtYWlsIiwiZ3JvdXBzIiwib2ZmbGluZSIsIm9mZmxpbmVfYWNjZXNzIiwib3BlbmlkIiwicHJvZmlsZSIsInJvbGVzIl0sInNpZCI6IjA4NmQzOGQ3LTFhN2ItNGY2ZS05ZmNlLWMzOTQxYzRmODYwNSIsInN1YiI6IjRhNzRjMTAyLTNlZTYtNGJjZC05MjI3LTJjNDAzOTAwYjhkZSIsInVhX2hhc2giOiIxMWQwZTM0ZjZhZDgyNTgzNGE0YTFhMDVjMTQ4NzA1MCJ9.XnAEoF3CznAmVFHiH7rWu89zatKoL50jp09faO1JORdMwmreX0BvMAgyHQPIgZkK8zSqyHCHitMUjG4VpSrVfvJ1zcUkpxryeThJku1Fu69GLVpIDpCeCqsXEh8kKbe47w9lBkehwzkU7campXGZA1xin92OsQ-M45pwkowYGq0ufq0WvOimBXvkCZZdWc4ssWAmSlxTByqIjSq-J6ta2piOOhXjX_X_J5Z_y31-2pP8isDllNTzdHOzlng4yKd2bELKImr8TrZ9coAkpSfMweq1AoLx9k0V0iD0Jv0d2Fx0LiLUWURhLJqSHsXSCPWGe73Z9rUfZxqEpkkrLBQTxQ accept-encoding: gzip content-length: 0 HTTP/2.0 200 content-type: application/vnd.vorwerk.planning.my-day.mobile+json date: Tue, 04 Mar 2025 19:05:21 GMT x-content-type-options: nosniff x-frame-options: DENY x-request-id: 36cd8196cc3b44e49cc8e098105d71c4;auth-proxy-planning-authproxy:9350488d-a168-4a38-8d70-1732dadb7e4a;auth-proxy-planning-authproxy:11c80879-220c-4f03-9b4a-2847e24800f3;planning-ssi-nginx:57075cf00dc922b6e2555fc490fd5103;planning:6cba82c1-1db6-45b6-a8fc-52735de85b1c vary: Accept-Encoding x-xss-protection: 0 strict-transport-security: max-age=31536000 set-cookie: current-locale=en_GB; Path=/planning; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=971cb8b4515cd3a725aa7d58adf32c4d6285eb9766c280ede4134128ca74c3df; path=/; secure; expires=Thu, 03 Apr 2025 19:05:21 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjFhOTc2OWQxLTUxMGUtNDM0NS04YzQxLTc4MTRhYTA3NzMxMiJ9.eyJhdWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiYXV0aF90aW1lIjoxNzQxMTE0MzY4LCJlbWFpbCI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20iLCJleHAiOjE3NDExNTc1NjgsImlhdCI6MTc0MTExNDM2OCwiaXNzIjoiaHR0cHM6Ly9jaWFtLnByb2QuY29va2lkb28udm9yd2Vyay1kaWdpdGFsLmNvbSIsImlzdWIiOiJiYjZlMTU1Mi00ODM1LTQxM2UtYTc0Yi01N2EwMDUzNjQ1NjgiLCJqdGkiOiIwNWQzMWM5ZC1kMjAxLTQ2NzgtOGVjYy00NmE5MjlhM2E2YmYiLCJyb2xlcyI6WyJST0xFX1ZPUldFUktDVVNUT01FUiIsIlVTRVIiXSwic2NvcGVzIjpbImVtYWlsIiwiZ3JvdXBzIiwib2ZmbGluZSIsIm9mZmxpbmVfYWNjZXNzIiwib3BlbmlkIiwicHJvZmlsZSIsInJvbGVzIl0sInNpZCI6IjA4NmQzOGQ3LTFhN2ItNGY2ZS05ZmNlLWMzOTQxYzRmODYwNSIsInN1YiI6IjRhNzRjMTAyLTNlZTYtNGJjZC05MjI3LTJjNDAzOTAwYjhkZSIsInVhX2hhc2giOiIxMWQwZTM0ZjZhZDgyNTgzNGE0YTFhMDVjMTQ4NzA1MCJ9.XnAEoF3CznAmVFHiH7rWu89zatKoL50jp09faO1JORdMwmreX0BvMAgyHQPIgZkK8zSqyHCHitMUjG4VpSrVfvJ1zcUkpxryeThJku1Fu69GLVpIDpCeCqsXEh8kKbe47w9lBkehwzkU7campXGZA1xin92OsQ-M45pwkowYGq0ufq0WvOimBXvkCZZdWc4ssWAmSlxTByqIjSq-J6ta2piOOhXjX_X_J5Z_y31-2pP8isDllNTzdHOzlng4yKd2bELKImr8TrZ9coAkpSfMweq1AoLx9k0V0iD0Jv0d2Fx0LiLUWURhLJqSHsXSCPWGe73Z9rUfZxqEpkkrLBQTxQ; path=/; secure; expires=Thu, 03 Apr 2025 19:05:21 GMT; httponly x-cache: Miss from cloudfront via: 1.1 4b04b092439272f69394f0b36f3b262a.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: JGOWQ56uRipcMj0SpmlLKu7nkjCNOJN_rXN9b8__lXA56VaSRK5gXw== content-length: 916 {"message":"Recipe Waffles was removed!","content":{"id":"2025-03-04","title":"2025-03-04","dayKey":"2025-03-04","created":"2025-03-04T18:56:05.424+00:00","modified":"2025-03-04T18:56:05.424+00:00","recipes":[{"id":"r214846","title":"Waffles","totalTime":"1500.0","locale":"gb","assets":{"images":{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3"}}}],"customerRecipeIds":[],"author":"4a74c102-3ee6-4bcd-9227-2c403900b8de"},"code":null}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/remove-recipe-from-custom-collection.txt000066400000000000000000000111371476166105100317010ustar00rootroot00000000000000DELETE https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX/recipes/r907015 HTTP/2.0 accept: application/vnd.vorwerk.organize.custom-list.mobile+json content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg accept-encoding: gzip content-length: 0 HTTP/2.0 200 content-type: application/vnd.vorwerk.organize.custom-list.mobile+json date: Wed, 06 Nov 2024 22:36:25 GMT x-content-type-options: nosniff vary: Accept-Encoding x-xss-protection: 0 strict-transport-security: max-age=31536000 x-frame-options: DENY set-cookie: current-locale=de_CH; Path=/organize; Secure; HttpOnly; SameSite=Lax set-cookie: v-authenticated=b172d34352762cb4bb14b8b1cccaaebedb0874495126c871852e94affbebc083; path=/; secure; expires=Fri, 06 Dec 2024 22:36:25 GMT set-cookie: v-token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImEyYjY5NWE4LWEyZGMtM2YyOC1iODM2LTQ4YzhkYWVhMzBlZCIsInR5cCI6IkpXVCJ9.eyJhdWQiOltdLCJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRfaWQiOiJrdXBmZXJ3ZXJrLWNsaWVudC1ud290IiwiY2xpZW50YXBwcm92ZWQiOmZhbHNlLCJleHAiOjE3MzA5NjE0ODYsImV4dCI6eyJhdXRoX3RpbWUiOjEuNzMwNjQwMzg4ZSswOSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9WT1JXRVJLQ1VTVE9NRVJHUk9VUCJdLCJjbGllbnRhcHByb3ZlZCI6ZmFsc2UsInVzZXJfbmFtZSI6ImN5cmlsbC5yYWNjYXVkK2Nvb2tpZG9vX2FwaUBnbWFpbC5jb20ifSwiZ3JhbnRfdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJpYXQiOjE3MzA5MTgyODYsImlzcyI6Imh0dHBzOi8vZXUubG9naW4udm9yd2Vyay5jb20vIiwianRpIjoiNzEyYzM5ZGYtMzQ3ZS00ZDllLWJhNDYtMjk0N2NjOGRhN2QwIiwibmJmIjoxNzMwOTE4Mjg2LCJzY29wZSI6WyJtYXJjb3NzYXBpIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIiwiT25saW5lIiwib2ZmbGluZV9hY2Nlc3MiXSwic2NwIjpbIm1hcmNvc3NhcGkiLCJvcGVuaWQiLCJwcm9maWxlIiwiZW1haWwiLCJPbmxpbmUiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiI0YTc0YzEwMi0zZWU2LTRiY2QtOTIyNy0yYzQwMzkwMGI4ZGUiLCJ1c2VyX25hbWUiOiJjeXJpbGwucmFjY2F1ZCtjb29raWRvb19hcGlAZ21haWwuY29tIn0.gb71KUGJK6QwS7FuuKezde9MWp-Nfc4iBg_DhVDdO-tnNdKZuE_ncPkoR8R1wO0SXnWtU3rYinah2mqOYGmUPNssqt3_aRq6inGVMpRnHH-RSSVal7RcDwwbOOyVyWY3j4G-ZqkM3JFRTEERvNsx03Ck7czRXBI-2RyiYvr557_SarQ_uI6x30PbS_4B6qfqPz36nnvdR2MnwKMHV0RpjarZ8tzTO8HsrPNAsyzp1XywTeC2KmSZgMHVqqpbCH-vPMtj7g1-WA76mNNdENlZoEmstd4BrWK_MBjBHwcOtT6HKKbkM_N95qIBRXB1GiF_vLTn7h5KjwFtqBboafnCzg; path=/; secure; expires=Fri, 06 Dec 2024 22:36:25 GMT; httponly x-request-id: c710d19819c922b9ecb5fbe75d46fdac;auth-proxy-organize-authproxy:2e5a1690-f60c-472e-83da-fdb4458088d4;auth-proxy-organize-authproxy:a7f36ee5-4f58-4865-9e6f-99ef4b3a742b;organize-ssi-nginx:ce6f4ba8ce745070e9e788cae358553f;organize:fd96a5c4-76d3-4868-ba4c-8fb274ce192f x-cache: Miss from cloudfront via: 1.1 15a25f000172c4183886f5e8d467c1d8.cloudfront.net (CloudFront) x-amz-cf-pop: ZRH55-P2 x-amz-cf-id: oZE9LUW6Aqlf7sXI0V8dM14zE-mFuzDp2ZmV-UfhJCH3-rf06d30gg== content-length: 444 {"message":"Rezept wurde aus dieser Rezeptliste entfernt!","content":{"id":"01JC1SRPRSW0SHE0AK8GCASABX","title":"Testliste1","version":4,"created":"2024-11-06T22:33:18.873+00:00","modified":"2024-11-06T22:36:25.914+00:00","chapters":[{"title":"","recipes":[]}],"assets":{"images":{"square":"","portrait":"","landscape":""}},"sharedListId":null,"author":"4a74c102-3ee6-4bcd-9227-2c403900b8de","listType":"CUSTOMLIST","shared":false},"code":null}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/shopping-list.txt000066400000000000000000000113531476166105100253350ustar00rootroot00000000000000GET https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH HTTP/2.0 content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ accept-encoding: gzip content-length: 0 HTTP/2.0 200 content-type: application/json; charset=utf-8 date: Sun, 03 Nov 2024 13:26:29 GMT set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:26:29 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:26:29 GMT; httponly strict-transport-security: max-age=31536000 vary: accept-encoding cache-control: no-cache, no-store, must-revalidate x-cache: Miss from cloudfront via: 1.1 72901e1a1a6af8228b948e1ec3586ace.cloudfront.net (CloudFront) x-amz-cf-pop: MXP63-P4 x-amz-cf-id: Frrq3Al5UN3vWq4eXK_B8UXbgsmtqxeNHYJxOiN1q5xGHSrQBGyvJA== content-length: 3817 {"recipes":[{"id":"r907016","ulid":"01JBS24P4D0T35V0VAKF7XHB60","title":"Mini-Pavlova mit Orangen","locale":"ch","status":"ok","language":"de","hasVariants":false,"isCustomerRecipe":false,"descriptiveAssets":[{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006"},{"square":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596","portrait":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596","landscape":"https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596"}],"recipeIngredientGroups":[{"id":"01JBS24P4DVD34T4HS2KM292VT","isOwned":false,"localId":"com.vorwerk.ingredients.Ingredient-rpf-9","optional":false,"quantity":{"value":200},"unit_ref":"11-unit-rdpf3","preparation":"","unitNotation":"g","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-9","ingredientNotation":"Zucker","shoppingCategory_ref":"ShoppingCategory-rpf-6"},{"id":"01JBS24P4DQR9QT7VTPCPQGGNA","isOwned":false,"localId":"com.vorwerk.ingredients.Ingredient-rpf-116","optional":false,"quantity":{"value":4},"unit_ref":"","preparation":", kalt","unitNotation":"","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-116","ingredientNotation":"Eiweisse","shoppingCategory_ref":"ShoppingCategory-rpf-10"},{"id":"01JBS24P4DA9G35VZAHT9DXM7J","isOwned":false,"localId":"com.vorwerk.ingredients.Ingredient-rpf-269","optional":false,"quantity":{"value":1},"unit_ref":"65-unit-rdpf3","preparation":"","unitNotation":"Prise","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-269","ingredientNotation":"Salz","shoppingCategory_ref":"ShoppingCategory-rpf-5"},{"id":"01JBS24P4DNV65WQ1HT0N2QD6D","isOwned":false,"localId":"com.vorwerk.ingredients.Ingredient-rpf-46","optional":false,"quantity":{"value":1},"unit_ref":"23-unit-rdpf3","preparation":"","unitNotation":"TL","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-46","ingredientNotation":"Zitronensaft","shoppingCategory_ref":"ShoppingCategory-rpf-9"},{"id":"01JBS24P4D3Q79CVDNVM23676P","isOwned":false,"localId":"com.vorwerk.ingredients.Ingredient-rpf-288","optional":false,"quantity":{"value":1},"unit_ref":"25-unit-rdpf3","preparation":"","unitNotation":"EL","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-288","ingredientNotation":"Maizena","shoppingCategory_ref":"ShoppingCategory-rpf-7"},{"id":"01JBS24P4DQNXK2BZSYF3KGPHJ","isOwned":false,"localId":"com.vorwerk.ingredients.Ingredient-rpf-316","optional":false,"quantity":{"value":4},"unit_ref":"","preparation":", geschält, filetiert","unitNotation":"","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-316","ingredientNotation":"Orangen","shoppingCategory_ref":"ShoppingCategory-rpf-8"},{"id":"01JBS24P4DVB33Y4M5SZGJ3R5A","isOwned":false,"localId":"com.vorwerk.ingredients.Ingredient-rpf-620","optional":false,"quantity":{"value":180},"unit_ref":"11-unit-rdpf3","preparation":"","unitNotation":"g","ingredient_ref":"com.vorwerk.ingredients.Ingredient-rpf-620","ingredientNotation":"Crème fraîche","shoppingCategory_ref":"ShoppingCategory-rpf-2"}]}],"customerRecipes":[],"additionalItems":[]}miaucl-cookidoo-api-5292e37/docs/raw-api-requests/user-info.txt000066400000000000000000000034771476166105100244540ustar00rootroot00000000000000GET https://ch.tmmobile.vorwerk-digital.com/community/profile/de-CH HTTP/2.0 content-type: application/x-www-form-urlencoded; charset=utf-8 accept-language: en;q = 1, de-AT;q = 0.9 accept: application/json user-agent: Thermomix/5427 (iPhone; iOS11.2; Scale / 3.00) authorization: Bearer eyJhbGciOiJ accept-encoding: gzip content-length: 0 HTTP/2.0 200 content-type: application/json; charset=utf-8 date: Sun, 03 Nov 2024 13:26:28 GMT x-frame-options: SAMEORIGIN set-cookie: v-authenticated=c1c4e026a4611640e299d6978fcac0e2599b4843e490db69e9d64dc6014ec0c7; path=/; secure; expires=Tue, 03 Dec 2024 13:26:28 GMT set-cookie: v-token=eyJhbGciOiJ; path=/; secure; expires=Tue, 03 Dec 2024 13:26:28 GMT; httponly x-permitted-cross-domain-policies: none x-xss-protection: 0 cache-control: no-cache, no-store, must-revalidate, max-age=600 x-content-type-options: nosniff origin-agent-cluster: ?1 vary: Accept-Encoding strict-transport-security: max-age=31536000 x-download-options: noopen cross-origin-opener-policy: same-origin referrer-policy: no-referrer x-dns-prefetch-control: off cross-origin-resource-policy: same-origin x-cache: Miss from cloudfront via: 1.1 72901e1a1a6af8228b948e1ec3586ace.cloudfront.net (CloudFront) x-amz-cf-pop: MXP63-P4 x-amz-cf-id: b2MRR-Lu_8UhAn3rswS5ZoWxs-_IY3bZvR3CHLPmS9DOSUVDf8ZiCg== content-length: 501 {"id":"4a74c102-3ee6-4bcd-9227-2c403900b8de","isPublic":false,"userInfo":{"username":" API","description":"","picture":"","pictureTemplate":""},"savedSearches":[{"id":"default","search":{"countries":["ch"],"languages":["de"],"accessories":["includingFriend","includingBladeCover","includingBladeCoverWithPeeler","includingCutter","includingSensor"]}}],"foodPreferences":[],"meta":{"cloudinaryPublicId":"61c22d8465a60c86de8ca7ce045665bfa35546d78ba6f971ffbed3780d4fa026"},"thermomixes":[]}miaucl-cookidoo-api-5292e37/docs/reference.md000066400000000000000000000001421476166105100210350ustar00rootroot00000000000000# Reference ::: cookidoo_api options: show_root_heading: false show_source: true miaucl-cookidoo-api-5292e37/example.py000077500000000000000000000132431476166105100176430ustar00rootroot00000000000000#!/usr/bin/env python3 """Example script for cookidoo-api.""" import asyncio from datetime import datetime import logging import os import sys import aiohttp from dotenv import load_dotenv from cookidoo_api import Cookidoo from cookidoo_api.helpers import ( get_country_options, get_language_options, get_localization_options, ) from cookidoo_api.types import ( CookidooAdditionalItem, CookidooConfig, CookidooIngredientItem, ) load_dotenv() # Configure the root logger logging.basicConfig( level=logging.DEBUG, # Set the logging level (DEBUG, INFO, WARNING, etc.) format="%(asctime)s [%(levelname)8s] %(name)s:%(lineno)s %(message)s", # Format of the log messages handlers=[ # Specify the handlers for the logger logging.StreamHandler(sys.stdout) # Output to stdout ], ) async def main(): """Run main example function.""" async with aiohttp.ClientSession() as session: # Show all country_codes, languages and some localizations _country_codes = await get_country_options() _languages = await get_language_options() _localizations_ch = await get_localization_options(country="ch") _localizations_en = await get_localization_options(language="en") # Create Cookidoo instance with email and password cookidoo = Cookidoo( session, cfg=CookidooConfig( email=os.environ["EMAIL"], password=os.environ["PASSWORD"], localization=( await get_localization_options(country="ie", language="en-GB") )[0], ), ) # Login await cookidoo.login() await cookidoo.refresh_token() # Info await cookidoo.get_user_info() await cookidoo.get_active_subscription() # Custom collections added_custom_collection = await cookidoo.add_custom_collection( "TEST_COLLECTION" ) _custom_collections = await cookidoo.get_custom_collections() await cookidoo.add_recipes_to_custom_collection( added_custom_collection.id, ["r907015"] ) _custom_collections = await cookidoo.get_custom_collections() await cookidoo.remove_recipe_from_custom_collection( added_custom_collection.id, "r907015" ) _custom_collections = await cookidoo.get_custom_collections() await cookidoo.remove_custom_collection(added_custom_collection.id) # Managed collections _added_managed_collection = await cookidoo.add_managed_collection("col500401") _managed_collections = await cookidoo.get_managed_collections() await cookidoo.remove_managed_collection("col500401") # Calendar _added_recipes_to_calendar = await cookidoo.add_recipes_to_calendar( datetime.now().date(), ["r907015", "r59322"] ) _recipes_in_calendar = await cookidoo.get_recipes_in_calendar_week( datetime.now().date() ) _removed_recipes_from_calendar = await cookidoo.remove_recipe_from_calendar( datetime.now().date(), "r907015" ) _removed_recipes_from_calendar = await cookidoo.remove_recipe_from_calendar( datetime.now().date(), "r59322" ) _recipes_in_calendar = await cookidoo.get_recipes_in_calendar_week( datetime.now().date() ) # Recipe details _recipe_details = await cookidoo.get_recipe_details("r59322") # Shopping list await cookidoo.clear_shopping_list() # Ingredients added_ingredients = await cookidoo.add_ingredient_items_for_recipes( ["r59322", "r907016"] ) _edited_ingredients = await cookidoo.edit_ingredient_items_ownership( [ CookidooIngredientItem( **{**ingredient.__dict__, "is_owned": not ingredient.is_owned}, ) for ingredient in filter( lambda ingredient: ingredient.name == "Hefe", added_ingredients, ) ] ) _ingredients = await cookidoo.get_ingredient_items() _recipes = await cookidoo.get_shopping_list_recipes() await cookidoo.remove_ingredient_items_for_recipes(["r59322"]) # Additional items added_additional_items = await cookidoo.add_additional_items( ["Fleisch", "Fisch"] ) edited_additional_items = await cookidoo.edit_additional_items_ownership( [ CookidooAdditionalItem( **{ **additional_item.__dict__, "is_owned": not additional_item.is_owned, }, ) for additional_item in filter( lambda additional_item: additional_item.name == "Fisch", added_additional_items, ) ] ) await cookidoo.edit_additional_items( [ CookidooAdditionalItem( **{ **additional_item.__dict__, "is_owned": "Vogel", }, ) for additional_item in filter( lambda additional_item: additional_item.name == "Fisch", edited_additional_items, ) ] ) _additional_items = await cookidoo.get_additional_items() await cookidoo.remove_additional_items( [ added_additional_item.id for added_additional_item in added_additional_items ] ) asyncio.run(main()) miaucl-cookidoo-api-5292e37/mkdocs.yml000066400000000000000000000032761476166105100176430ustar00rootroot00000000000000site_name: Cookidoo API site_description: An unofficial python package to access Cookidoo. repo_url: https://github.com/miaucl/cookidoo-api repo_name: miaucl/cookidoo-api theme: name: material icon: logo: material/library palette: # Palette toggle for automatic mode - media: "(prefers-color-scheme)" toggle: icon: material/brightness-auto name: Switch to light mode # Palette toggle for light mode - media: "(prefers-color-scheme: light)" scheme: default primary: indigo accent: light green toggle: icon: material/brightness-7 name: Switch to dark mode # Palette toggle for dark mode - media: "(prefers-color-scheme: dark)" scheme: slate primary: indigo accent: light green toggle: icon: material/brightness-4 name: Switch to light mode markdown_extensions: - toc: permalink: true - pymdownx.highlight: anchor_linenums: true - pymdownx.tasklist: custom_checkbox: true - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences plugins: - search - mkdocstrings: handlers: python: import: - https://docs.python.org/3.11/objects.inv options: docstring_style: numpy merge_init_into_class: true show_signature: false members_order: source watch: - docs - cookidoo_api nav: - About: index.md - Authors: authors.md - License: license.md - Reference: reference.md - Localization: localization.md miaucl-cookidoo-api-5292e37/pyproject.toml000066400000000000000000000202031476166105100205410ustar00rootroot00000000000000[build-system] requires = [ "setuptools>=46.4.0", "wheel" ] build-backend = "setuptools.build_meta" [project] name = "cookidoo-api" dynamic = ["version", "readme"] description = "Unofficial package to access Cookidoo." authors = [ { name = "Cyrill Raccaud", email = "cyrill.raccaud+pypi@gmail.com" }, ] dependencies = [ "aiohttp", "aiofiles", ] requires-python = ">=3.12" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Intended Audience :: Information Technology", "Topic :: System :: Monitoring", "Topic :: System :: Logging", "Topic :: System :: Systems Administration", ] license = {text = "MIT License"} keywords = ["cookidoo", "todo", "home-assistant", "iot"] [tool.setuptools] [tool.setuptools.packages.find] include = ["cookidoo_api*"] [tool.setuptools.package-data] cookidoo_api = ["py.typed", '*.json'] [tool.setuptools.dynamic] version = {attr = "cookidoo_api.__version__"} readme = {file = ["README.md"], content-type = "text/markdown"} [project.urls] Documentation = "https://miaucl.github.io/cookidoo-api/" Source = "https://github.com/miaucl/cookidoo-api" Issues = "https://github.com/miaucl/cookidoo-api/issues" "Release notes" = "https://github.com/miaucl/cookidoo-api/releases" [tool.ruff] target-version = "py312" [tool.ruff.lint] select = [ "B002", # Python does not support the unary prefix increment "B007", # Loop control variable {name} not used within loop body "B014", # Exception handler with duplicate exception "B023", # Function definition does not bind loop variable {name} "B026", # Star-arg unpacking after a keyword argument is strongly discouraged "C", # complexity "COM818", # Trailing comma on bare tuple prohibited "D", # docstrings "DTZ003", # Use datetime.now(tz=) instead of datetime.utcnow() "DTZ004", # Use datetime.fromtimestamp(ts, tz=) instead of datetime.utcfromtimestamp(ts) "E", # pycodestyle "F", # pyflakes/autoflake "G", # flake8-logging-format "I", # isort "ICN001", # import concentions; {name} should be imported as {asname} "N804", # First argument of a class method should be named cls "N805", # First argument of a method should be named self "N815", # Variable {name} in class scope should not be mixedCase "S307", # No builtin eval() allowed "PGH004", # Use specific rule codes when using noqa "PLC0414", # Useless import alias. Import alias does not rename original package. "PLC", # pylint "PLE", # pylint "PLR", # pylint "PLW", # pylint "Q000", # Double quotes found but single quotes preferred "RUF006", # Store a reference to the return value of asyncio.create_task "S102", # Use of exec detected "S103", # bad-file-permissions "S108", # hardcoded-temp-file "S306", # suspicious-mktemp-usage "S307", # suspicious-eval-usage "S313", # suspicious-xmlc-element-tree-usage "S314", # suspicious-xml-element-tree-usage "S315", # suspicious-xml-expat-reader-usage "S316", # suspicious-xml-expat-builder-usage "S317", # suspicious-xml-sax-usage "S318", # suspicious-xml-mini-dom-usage "S319", # suspicious-xml-pull-dom-usage "S320", # suspicious-xmle-tree-usage "S601", # paramiko-call "S602", # subprocess-popen-with-shell-equals-true "S604", # call-with-shell-equals-true "S608", # hardcoded-sql-expression "S609", # unix-command-wildcard-injection "SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass "SIM117", # Merge with-statements that use the same scope "SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys() "SIM201", # Use {left} != {right} instead of not {left} == {right} "SIM208", # Use {expr} instead of not (not {expr}) "SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a} "SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'. "SIM401", # Use get from dict with default instead of an if block "T100", # Trace found: {name} used "T20", # flake8-print "TID251", # Banned imports "TRY004", # Prefer TypeError exception for invalid type "B904", # Use raise from to specify exception cause "TRY", # tryceratops "UP", # pyupgrade "W", # pycodestyle ] ignore = [ "D202", # No blank lines allowed after function docstring "D203", # 1 blank line required before class docstring "D213", # Multi-line docstring summary should start at the second line "D406", # Section name should end with a newline "D407", # Section name underlining "E501", # line too long "E731", # do not assign a lambda expression, use a def # Ignore ignored, as the rule is now back in preview/nursery, which cannot # be ignored anymore without warnings. # https://github.com/astral-sh/ruff/issues/7491 # "PLC1901", # Lots of false positives # False positives https://github.com/astral-sh/ruff/issues/5386 "PLC0208", # Use a sequence type instead of a `set` when iterating over values "PLR0911", # Too many return statements ({returns} > {max_returns}) "PLR0912", # Too many branches ({branches} > {max_branches}) "PLR0913", # Too many arguments to function call ({c_args} > {max_args}) "PLR0915", # Too many statements ({statements} > {max_statements}) "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target "TRY003", # Avoid specifying long messages outside the exception class # May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules "W191", "E111", "E114", "E117", "D206", "D300", "Q000", "Q001", "Q002", "Q003", "COM812", "COM819", "ISC001", "ISC002", # Disabled because ruff does not understand type of __all__ generated by a function "PLE0605", ] [tool.ruff.lint.flake8-pytest-style] fixture-parentheses = false [tool.ruff.lint.flake8-tidy-imports.banned-api] "async_timeout".msg = "use asyncio.timeout instead" "pytz".msg = "use zoneinfo instead" [tool.ruff.lint.isort] force-sort-within-sections = true known-first-party = [ "cookidoo_api", ] combine-as-imports = true split-on-trailing-comma = false [tool.ruff.lint.per-file-ignores] # Allow for main entry & scripts to write to stdout "cookidoo_api/__main__.py" = ["T201"] "cookidoo_api/scripts/*" = ["T201"] "scripts/*" = ["T20"] "tests/*" = ["T20"] "smoke_test/*" = ["T20"] [tool.ruff.lint.mccabe] max-complexity = 25 [tool.pytest.ini_options] asyncio_mode = "auto" testpaths = [ "tests", ] log_cli = true # log_cli_level = "DEBUG" log_cli_format = "%(asctime)s%(msecs)03d [%(levelname)8s] %(name)s:%(lineno)s %(message)s" log_cli_date_format = "%Y%m%dT%H%M%S." [tool.coverage.run] branch = true source = ["tests"] [tool.coverage.report] format="text" show_missing = true # Regexes for lines to exclude from consideration exclude_also = [ # Don't complain about missing debug-only code: "def __repr__", "if self\\.debug", # Don't complain if tests don't hit defensive assertion code: "raise AssertionError", "raise NotImplementedError", # Don't complain if non-runnable code isn't run: "if 0:", "if __name__ == .__main__.:", # Don't complain about abstract methods, they aren't run: "@(abc\\.)?abstractmethod", ] [tool.coverage.html] directory = "coverage_html_report" [tool.mypy] python_version = "3.12" plugins = "pydantic.mypy" show_error_codes = true follow_imports = "normal" local_partial_types = true strict_equality = true no_implicit_optional = true warn_incomplete_stub = true warn_redundant_casts = true warn_unused_configs = true warn_unused_ignores = true enable_error_code = ["ignore-without-code", "redundant-self", "truthy-iterable"] disable_error_code = ["annotation-unchecked", "import-not-found", "import-untyped"] extra_checks = false check_untyped_defs = true disallow_incomplete_defs = true disallow_subclassing_any = true disallow_untyped_calls = true disallow_untyped_decorators = true disallow_untyped_defs = true warn_return_any = true warn_unreachable = true miaucl-cookidoo-api-5292e37/raw/000077500000000000000000000000001476166105100164215ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/raw/localization-extract.html000066400000000000000000003122461476166105100234570ustar00rootroot00000000000000
miaucl-cookidoo-api-5292e37/requirements.txt000066400000000000000000000000361476166105100211130ustar00rootroot00000000000000aiohttp~=3.11 aiofiles~=24.1.0miaucl-cookidoo-api-5292e37/requirements_dev.txt000066400000000000000000000004021476166105100217460ustar00rootroot00000000000000aiohttp~=3.11 aiofiles~=24.1.0 mypy>=1.8.0 pydantic>=2.9.2 pre-commit>=3.6.1 python-dotenv>=1.0.1 ruff~=0.9 mkdocs-material==9.6.7 mkdocstrings[python]==0.28.2 pytest-asyncio>=0.23.6 pytest-cov>=5.0.0 pytest>=8.1.1 python-dotenv>=1.0.1 beautifulsoup4~=4.13.3miaucl-cookidoo-api-5292e37/requirements_test.txt000066400000000000000000000001371476166105100221540ustar00rootroot00000000000000pytest-asyncio>=0.23.6 pytest-cov>=5.0.0 pytest>=8.1.1 python-dotenv>=1.0.1 aioresponses>=0.7.6miaucl-cookidoo-api-5292e37/scripts/000077500000000000000000000000001476166105100173175ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/scripts/process-localization-extract.py000077500000000000000000000021501476166105100255060ustar00rootroot00000000000000"""Process the extract for a localization table from the cookidoo web application.""" #!/usr/bin/env python3 # Instructions found here: cookidoo-api/docs/localization.md import json import os from bs4 import BeautifulSoup script_dir = os.path.dirname(__file__) input_file_path = os.path.join(script_dir, "../raw/localization-extract.html") output_file_path = os.path.join(script_dir, "../cookidoo_api/localization.json") # Load the HTML content with open(input_file_path, encoding="utf-8") as file: html_content = file.read() # Parse the HTML soup = BeautifulSoup(html_content, "html.parser") # Extract the data localization_data = [ { "country_code": li["data-filter"], "language": li["data-lang"], "url": li.find("input")["value"], } for li in soup.find_all("li", {"class": "core-dropdown-list__item"}) if li.find("input", {"type": "checkbox"}) ] print(f"Successfully extract {len(localization_data)} entries") # Save the extracted data to JSON with open(output_file_path, "w", encoding="utf-8") as file: json.dump(localization_data, file, ensure_ascii=False, indent=4) miaucl-cookidoo-api-5292e37/scripts/run-in-env.sh000077500000000000000000000010411476166105100216500ustar00rootroot00000000000000#!/usr/bin/env sh set -eu # Used in venv activate script. # Would be an error if undefined. OSTYPE="${OSTYPE-}" # Activate pyenv and virtualenv if present, then run the specified command # pyenv, pyenv-virtualenv if [ -s .python-version ]; then PYENV_VERSION=$(head -n 1 .python-version) export PYENV_VERSION fi # other common virtualenvs my_path=$(git rev-parse --show-toplevel) for venv in venv .venv .; do if [ -f "${my_path}/${venv}/bin/activate" ]; then . "${my_path}/${venv}/bin/activate" break fi done exec "$@" miaucl-cookidoo-api-5292e37/smoke_test/000077500000000000000000000000001476166105100200055ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/smoke_test/__init__.py000066400000000000000000000000431476166105100221130ustar00rootroot00000000000000"""Smoke test for cookidoo-api.""" miaucl-cookidoo-api-5292e37/smoke_test/conftest.py000066400000000000000000000046731476166105100222160ustar00rootroot00000000000000"""Smoke test for cookidoo-api.""" from collections.abc import AsyncGenerator import json import os from aiohttp import ClientSession from dotenv import load_dotenv import pytest from cookidoo_api.cookidoo import Cookidoo from cookidoo_api.helpers import get_localization_options from cookidoo_api.types import CookidooAuthResponse, CookidooConfig load_dotenv() def save_token(token: CookidooAuthResponse) -> None: """Save the token locally.""" with open(".token", "w", encoding="utf-8") as file: file.write(json.dumps(token.__dict__)) def load_token() -> CookidooAuthResponse: """Load the token locally.""" # Open and read the file with open(".token", encoding="utf-8") as file: token = file.read() return CookidooAuthResponse(**json.loads(token)) @pytest.fixture(name="auth_data") async def auth_data() -> CookidooAuthResponse: """Load the token.""" return load_token() @pytest.fixture(name="session") async def aiohttp_client_session() -> AsyncGenerator[ClientSession]: """Create a client session.""" async with ClientSession() as session: yield session @pytest.fixture(name="cookidoo_no_auth") async def cookidoo_api_client_no_auth(session: ClientSession) -> Cookidoo: """Create Cookidoo instance.""" country = os.environ["COUNTRY"] localizations = await get_localization_options(country=country) cookidoo = Cookidoo( session, cfg=CookidooConfig( email=os.environ[f"EMAIL_{country.upper()}"], password=os.environ["PASSWORD"], localization=localizations[0], ), ) return cookidoo @pytest.fixture(name="cookidoo") async def cookidoo_authenticated_api_client( session: ClientSession, auth_data: CookidooAuthResponse ) -> Cookidoo: """Create authenticated Cookidoo instance.""" country = os.environ["COUNTRY"] localizations = await get_localization_options(country=country) print( CookidooConfig( email=os.environ[f"EMAIL_{country.upper()}"], password=os.environ["PASSWORD"], localization=localizations[0], ) ) cookidoo = Cookidoo( session, cfg=CookidooConfig( email=os.environ[f"EMAIL_{country.upper()}"], password=os.environ["PASSWORD"], localization=localizations[0], ), ) # Restore auth data from saved token cookidoo.auth_data = auth_data return cookidoo miaucl-cookidoo-api-5292e37/smoke_test/ruff.toml000066400000000000000000000015111476166105100216420ustar00rootroot00000000000000extend = "../pyproject.toml" [lint] extend-select = [ "PT001", # Use @pytest.fixture without parentheses "PT002", # Configuration for fixture specified via positional args, use kwargs "PT003", # The scope='function' is implied in @pytest.fixture() "PT006", # Single parameter in parameterize is a string, multiple a tuple "PT013", # Found incorrect pytest import, use simple import pytest instead "PT015", # Assertion always fails, replace with pytest.fail() "PT021", # use yield instead of request.addfinalizer "PT022", # No teardown in fixture, replace useless yield with return ] extend-ignore = [ "PLC", # pylint "PLE", # pylint "PLR", # pylint "PLW", # pylint "B904", # Use raise from to specify exception cause "N815", # Variable {name} in class scope should not be mixedCase ]miaucl-cookidoo-api-5292e37/smoke_test/test_1_setup_token.py000066400000000000000000000014051476166105100241760ustar00rootroot00000000000000"""Setup token for smoke test for cookidoo-api.""" import asyncio from cookidoo_api.cookidoo import Cookidoo from smoke_test.conftest import save_token class TestLoginAndValidation: """Test login and validation.""" async def test_cookidoo_login(self, cookidoo_no_auth: Cookidoo) -> None: """Test cookidoo validation of the token or login otherwise.""" await cookidoo_no_auth.login() # Should login await asyncio.sleep(3) expires_in_login = cookidoo_no_auth.expires_in await cookidoo_no_auth.refresh_token() # Should refresh expires_in_refesh = cookidoo_no_auth.expires_in assert expires_in_refesh > expires_in_login if auth_data := cookidoo_no_auth.auth_data: save_token(auth_data) miaucl-cookidoo-api-5292e37/smoke_test/test_2_methods.py000066400000000000000000000214461476166105100233110ustar00rootroot00000000000000"""Smoke test for cookidoo-api.""" from datetime import datetime import pytest from cookidoo_api.cookidoo import Cookidoo from cookidoo_api.types import CookidooAdditionalItem, CookidooIngredientItem class TestMethods: """Test methods.""" async def test_cookidoo_clear_shopping_list(self, cookidoo: Cookidoo) -> None: """Test cookidoo clear shopping_ ist before testing of all methods.""" await cookidoo.clear_shopping_list() async def test_cookidoo_get_user_info(self, cookidoo: Cookidoo) -> None: """Test cookidoo get user info.""" user_info = await cookidoo.get_user_info() assert "API" in user_info.username async def test_cookidoo_get_active_subscription(self, cookidoo: Cookidoo) -> None: """Test cookidoo get active subscription.""" sub = await cookidoo.get_active_subscription() if sub is not None: # Test account uses the free plan or a trial assert sub.status == "ACTIVE" assert sub.type == "TRIAL" else: assert sub is None @pytest.mark.parametrize( ( "recipe_id", "name", ), [ ("r59322", "Vollwert-Brötchen/Baguettes"), ("r628448", "Salmorejo de sandía con cubo de Rubik"), ], ) async def test_cookidoo_recipe_details( self, cookidoo: Cookidoo, recipe_id: str, name: str ) -> None: """Test cookidoo recipe details.""" recipe_details = await cookidoo.get_recipe_details(recipe_id) assert isinstance(recipe_details, object) assert recipe_details.id == recipe_id assert recipe_details.name == name async def test_cookidoo_shopping_list_recipe_and_ingredients( self, cookidoo: Cookidoo ) -> None: """Test cookidoo shopping list recipe and ingredients.""" added_ingredients = await cookidoo.add_ingredient_items_for_recipes( ["r59322", "r907016"] ) assert isinstance(added_ingredients, list) assert len(added_ingredients) == 14 assert "Zucker" in ( added_ingredient.name for added_ingredient in added_ingredients ) edited_ingredients = await cookidoo.edit_ingredient_items_ownership( [ CookidooIngredientItem( **{**ingredient.__dict__, "is_owned": not ingredient.is_owned}, ) for ingredient in filter( lambda ingredient: ingredient.name == "Hefe", added_ingredients, ) ] ) assert isinstance(edited_ingredients, list) assert len(edited_ingredients) == 1 assert edited_ingredients[0].is_owned ingredients = await cookidoo.get_ingredient_items() assert isinstance(ingredients, list) assert len(ingredients) == 14 recipes = await cookidoo.get_shopping_list_recipes() assert isinstance(recipes, list) assert len(recipes) == 2 await cookidoo.remove_ingredient_items_for_recipes(["r59322", "r907016"]) ingredients_empty = await cookidoo.get_ingredient_items() assert isinstance(ingredients_empty, list) assert len(ingredients_empty) == 0 async def test_cookidoo_additional_items(self, cookidoo: Cookidoo) -> None: """Test cookidoo additional items.""" added_additional_items = await cookidoo.add_additional_items( ["Fleisch", "Fisch"] ) assert isinstance(added_additional_items, list) assert len(added_additional_items) == 2 assert "Fleisch" in ( added_ingredient.name for added_ingredient in added_additional_items ) assert "Fisch" in ( added_ingredient.name for added_ingredient in added_additional_items ) edited_additional_items = await cookidoo.edit_additional_items_ownership( [ CookidooAdditionalItem( **{ **additional_item.__dict__, "is_owned": not additional_item.is_owned, }, ) for additional_item in filter( lambda additional_item: additional_item.name == "Fisch", added_additional_items, ) ] ) assert isinstance(edited_additional_items, list) assert len(edited_additional_items) == 1 assert edited_additional_items[0].is_owned edited_additional_items = await cookidoo.edit_additional_items( [ CookidooAdditionalItem( **{ **additional_item.__dict__, "name": "Vogel", }, ) for additional_item in filter( lambda additional_item: additional_item.name == "Fisch", edited_additional_items, ) ] ) assert isinstance(edited_additional_items, list) assert len(edited_additional_items) == 1 assert edited_additional_items[0].name == "Vogel" additional_items = await cookidoo.get_additional_items() assert isinstance(additional_items, list) assert len(additional_items) == 2 await cookidoo.remove_additional_items( [ added_additional_item.id for added_additional_item in added_additional_items ] ) additional_items = await cookidoo.get_additional_items() assert isinstance(additional_items, list) assert len(additional_items) == 0 async def test_cookidoo_managed_collections(self, cookidoo: Cookidoo) -> None: """Test cookidoo managed collections.""" added_managed_collection = await cookidoo.add_managed_collection("col500401") assert added_managed_collection.id == "col500401" managed_collections = await cookidoo.get_managed_collections() assert isinstance(managed_collections, list) assert len(managed_collections) == 1 count_collections, count_pages = await cookidoo.count_managed_collections() assert count_collections == 1 assert count_pages == 1 await cookidoo.remove_managed_collection("col500401") managed_collections = await cookidoo.get_managed_collections() assert isinstance(managed_collections, list) assert len(managed_collections) == 0 async def test_cookidoo_custom_collections(self, cookidoo: Cookidoo) -> None: """Test cookidoo custom collections.""" added_custom_collection = await cookidoo.add_custom_collection( "TEST_COLLECTION" ) assert added_custom_collection.name == "TEST_COLLECTION" custom_collections = await cookidoo.get_custom_collections() assert isinstance(custom_collections, list) assert len(custom_collections) == 1 count_collections, count_pages = await cookidoo.count_custom_collections() assert count_collections == 1 assert count_pages == 1 custom_collection_with_recipe = await cookidoo.add_recipes_to_custom_collection( added_custom_collection.id, ["r907015"] ) assert custom_collection_with_recipe.chapters[0].recipes[0].id == "r907015" custom_collection_without_recipe = ( await cookidoo.remove_recipe_from_custom_collection( added_custom_collection.id, "r907015" ) ) assert len(custom_collection_without_recipe.chapters[0].recipes) == 0 await cookidoo.remove_custom_collection(added_custom_collection.id) custom_collections = await cookidoo.get_custom_collections() assert isinstance(custom_collections, list) assert len(custom_collections) == 0 async def test_cookidoo_calendar(self, cookidoo: Cookidoo) -> None: """Test cookidoo calendar.""" added_day_recipes = await cookidoo.add_recipes_to_calendar( datetime.now().date(), ["r907015", "r59322"] ) assert len(added_day_recipes.recipes) == 2 assert [recipe.id for recipe in added_day_recipes.recipes] == [ "r907015", "r59322", ] day_recipes = await cookidoo.get_recipes_in_calendar_week(datetime.now().date()) assert isinstance(day_recipes, list) assert len(day_recipes) == 1 assert [recipe.id for recipe in day_recipes[0].recipes] == [ "r907015", "r59322", ] await cookidoo.remove_recipe_from_calendar(datetime.now().date(), "r907015") await cookidoo.remove_recipe_from_calendar(datetime.now().date(), "r59322") day_recipes = await cookidoo.get_recipes_in_calendar_week(datetime.now().date()) assert isinstance(day_recipes, list) assert len(day_recipes) == 0 miaucl-cookidoo-api-5292e37/tests/000077500000000000000000000000001476166105100167725ustar00rootroot00000000000000miaucl-cookidoo-api-5292e37/tests/__init__.py000066400000000000000000000000431476166105100211000ustar00rootroot00000000000000"""Unit tests for cookidoo-api.""" miaucl-cookidoo-api-5292e37/tests/conftest.py000066400000000000000000000015511476166105100211730ustar00rootroot00000000000000"""Unit tests for cookidoo-api.""" from collections.abc import AsyncGenerator, Generator from aiohttp import ClientSession from aioresponses import aioresponses from dotenv import load_dotenv import pytest from cookidoo_api.cookidoo import Cookidoo load_dotenv() UUID = "00000000-00000000-00000000-00000000" @pytest.fixture(name="session") async def aiohttp_client_session() -> AsyncGenerator[ClientSession]: """Create a client session.""" async with ClientSession() as session: yield session @pytest.fixture(name="cookidoo") async def bring_api_client(session: ClientSession) -> Cookidoo: """Create Cookidoo instance.""" cookidoo = Cookidoo(session) return cookidoo @pytest.fixture(name="mocked") def aioclient_mock() -> Generator[aioresponses]: """Mock Aiohttp client requests.""" with aioresponses() as m: yield m miaucl-cookidoo-api-5292e37/tests/responses.py000066400000000000000000002521321476166105100213720ustar00rootroot00000000000000"""Test response for cookidoo-api.""" from typing import Final from tests.conftest import UUID COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE: Final = { "access_token": "eyJhbGciOiJ", "expires_in": 43199, "id_token": "eyJhbGciOiJSUzI1Ni", "iss": "https://eu.login.vorwerk.com/", "jti": "9f97a234-3f80-4e35-bf48-2e3e9e7e8720", "refresh_token": "eyJhbGciOiJSUzI1Ni", "scope": "marcossapi openid profile email Online offline_access", "token_type": "bearer", "sub": "sub_uuid", } COOKIDOO_TEST_RESPONSE_USER_INFO: Final = { "id": UUID, "isPublic": False, "userInfo": { "username": "Test User", "description": "", "picture": "", "pictureTemplate": "", }, "savedSearches": [ { "id": "default", "search": { "countries": ["ch"], "languages": ["de"], "accessories": [ "includingFriend", "includingBladeCover", "includingBladeCoverWithPeeler", "includingCutter", "includingSensor", ], }, } ], "foodPreferences": [], "meta": { "cloudinaryPublicId": "61c22d8465a60c86de8ca7ce045665bfa35546d78ba6f971ffbed3780d4fa026" }, "thermomixes": [], } COOKIDOO_TEST_RESPONSE_ACTIVE_SUBSCRIPTION: Final = [ { "active": True, "startDate": "2024-09-15T00:00:00Z", "expires": "2024-10-15T23:59:00Z", "type": "TRIAL", "extendedType": "TRIAL", "autoRenewalProduct": "Cookidoo 1 Month Free", "autoRenewalProductCode": None, "countryOfResidence": "ch", "subscriptionLevel": "NONE", "status": "RUNNING", "marketMismatch": False, "subscriptionSource": "COMMERCE", "_created": "2024-10-30T15:38:26.496029003Z", "_modified": None, } ] COOKIDOO_TEST_RESPONSE_INACTIVE_SUBSCRIPTION: Final = [ { "active": False, "startDate": "2024-09-15T00:00:00Z", "expires": "2024-10-15T23:59:00Z", "type": "TRIAL", "extendedType": "TRIAL", "autoRenewalProduct": "Cookidoo 1 Month Free", "autoRenewalProductCode": None, "countryOfResidence": "ch", "subscriptionLevel": "NONE", "status": "ENDED", "marketMismatch": False, "subscriptionSource": "COMMERCE", "_created": "2024-10-30T15:38:26.496029003Z", "_modified": None, } ] COOKIDOO_TEST_RESPONSE_GET_RECIPE_DETAILS = { "id": "r907015", "tags": [ {"id": "40-recipetag-rdpf3", "name": "Fingerfood"}, {"id": "143-recipetag-rdpf3", "name": "Vegetarisch"}, {"id": "166-recipetag-rdpf3", "name": "alkoholfrei"}, {"id": "167-recipetag-rdpf3", "name": "Glutenfrei"}, {"id": "212-marketingtag-rdpf3", "name": "Weihnachten"}, {"id": "233-marketingtag-rdpf3", "name": "Winter"}, {"id": "247-marketingtag-rdpf3", "name": "Geschenk"}, {"id": "8-marketingtag-rdpf3", "name": "Europäisch"}, {"id": "209-marketingtag-rdpf3", "name": "Herbst"}, {"id": "230-marketingtag-rdpf3", "name": "Sommer"}, {"id": "228-marketingtag-rdpf3", "name": "Frühling"}, {"id": "238-marketingtag-rdpf3", "name": "Geburtstag"}, {"id": "253-marketingtag-rdpf3", "name": "Party"}, ], "times": [ {"type": "activeTime", "comment": "", "quantity": {"value": 2700}}, {"type": "totalTime", "comment": "", "quantity": {"value": 32400}}, ], "title": "Kokos Pralinen", "locale": "ch", "status": "ok", "markets": ["ch"], "language": "de", "categories": [ { "id": "VrkNavCategory-RPF-011", "title": "Desserts, Pâtisserie und Süssigkeiten", "subtitle": "", "defaultTitle": "Desserts and sweets", }, { "id": "VrkNavCategory-RPF-020", "title": "Snacks", "subtitle": "", "defaultTitle": "Snacks and finger food", }, ], "difficulty": "easy", "exportDate": "2024-10-28T16:08:56Z", "ingredients": [ { "id": "com.vorwerk.ingredients.Ingredient-rpf-322", "title": "Kokosraspeln", "defaultTitle": "coconut, dried, grated, unsweetened", "primaryNotation": "Kokosnuss, getrocknet, geraspelt, ungesüsst", "shoppingCategory_ref": "com.vorwerk.categories.ShoppingCategory-rpf-6", }, { "id": "com.vorwerk.ingredients.Ingredient-rpf-319", "title": "gezuckerte Kondensmilch", "defaultTitle": "condensed milk, sweetened", "primaryNotation": "gezuckerte Kondensmilch", "shoppingCategory_ref": "com.vorwerk.categories.ShoppingCategory-rpf-2", }, { "id": "com.vorwerk.ingredients.Ingredient-rpf-17", "title": "Butter", "defaultTitle": "butter, unsalted (from cows' milk)", "primaryNotation": "Butter", "shoppingCategory_ref": "com.vorwerk.categories.ShoppingCategory-rpf-2", }, ], "servingSize": {"quantity": {"value": 50}, "unitNotation": "Stück"}, "recipeUtensils": [ {"utensilRef": "5460-utensil-rdpf3", "utensilNotation": "Kühlschrank"} ], "categories_refs": ["VrkNavCategory-RPF-011", "VrkNavCategory-RPF-020"], "nutritionGroups": [ { "name": "", "recipeNutritions": [ { "quantity": 1, "nutritions": [ {"type": "kJ", "number": 275, "unittype": "kJ"}, {"type": "kcal", "number": 65.7, "unittype": "kcal"}, {"type": "protein", "number": 1, "unittype": "g"}, {"type": "carb2", "number": 5.6, "unittype": "g"}, {"type": "fat", "number": 4.7, "unittype": "g"}, {"type": "dietaryFibre", "number": 0.8, "unittype": "g"}, ], "unitNotation": "Stück", } ], } ], "optionalDevices": [], "publicationDate": "2024-10-29T06:00:00Z", "targetCountries": ["ch"], "recipeStepGroups": [ { "title": "", "recipeSteps": [ { "title": "1", "formattedText": "200 g Kokosraspeln in den Mixtopf geben und 15 Sek./Stufe 8 zerkleinern. In eine Schüssel umfüllen und beiseitestellen.", }, { "title": "2", "formattedText": "Kondensmilch und Butter in den Mixtopf geben und 6 Min./100°C/Stufe 2 kochen.", }, { "title": "3", "formattedText": "Gemahlene Kokosraspeln zugeben und 2 Min./Stufe 4 vermischen. In eine Schüssel umfüllen und mindestens 8 Stunden, am besten über Nacht, in den Kühlschrank stellen.", }, { "title": "4", "formattedText": "60 g Kokosraspeln auf einen Teller geben. Aus der Kokos-Milch-Mischung kleine Kugeln formen (ca. Ø 1 cm), in den Kokosraspeln wälzen und bis zum Servieren kühl stellen.", }, ], } ], "additionalDevices": [], "descriptiveAssets": [ { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", }, { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4c59187077a8767c919bd168a066c224/Derivates/dd52ed01ce418450cb05f7ef7eb9caf32d934b6f", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4c59187077a8767c919bd168a066c224/Derivates/dd52ed01ce418450cb05f7ef7eb9caf32d934b6f", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4c59187077a8767c919bd168a066c224/Derivates/dd52ed01ce418450cb05f7ef7eb9caf32d934b6f", }, ], "thermomixVersions": ["TM5", "TM6"], "additionalInformation": [ { "type": "VrkRecipeAdditionalInformationType", "content": "Die Pralinen sollen kühl aufbewahrt und gegessen werden. Sie können auch eingefroren werden.", } ], "recipeIngredientGroups": [ { "title": "", "recipeIngredients": [ { "localId": "com.vorwerk.ingredients.Ingredient-rpf-322", "optional": False, "quantity": {"wrong_value": 260}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-322", "ingredientNotation": "Kokosraspeln", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-319", "optional": False, "quantity": {"value": 400}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-319", "ingredientNotation": "gezuckerte Kondensmilch", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-17", "optional": False, "quantity": {"from": 40, "to": 60}, "unit_ref": "11-unit-rdpf3", "preparation": ", in Stücken", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-17", "ingredientNotation": "Butter", }, ], } ], "inCollections": [ { "id": "col500561", "title": "Schneeweiss und Zuckersüss", "recipesCount": {"value": 6, "text": "other"}, "market": "ch", "descriptiveAssets": [ { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/collection/ras/Assets/38cf56dff995c01c9a4ce2ce3b9f0bf8/Derivates/d8d563384bbd2652791aedfcb3200dde0e6221cb" } ], } ], } COOKIDOO_TEST_RESPONSE_GET_SHOPPING_LIST_RECIPES = { "recipes": [ { "id": "r907016", "ulid": "01JBQFM448MWF37WM0CQ763MQ8", "title": "Mini-Pavlova mit Orangen", "locale": "ch", "status": "ok", "language": "de", "hasVariants": False, "isCustomerRecipe": False, "descriptiveAssets": [ { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", }, { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596", }, ], "recipeIngredientGroups": [ { "id": "01JBQFM44769BTC1P25CNDWJK9", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-9", "optional": False, "quantity": {"value": 200}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-9", "ingredientNotation": "Zucker", "shoppingCategory_ref": "ShoppingCategory-rpf-6", }, { "id": "01JBQFM447GYJYP0QR4EYSEX75", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-116", "optional": False, "quantity": {"value": 4}, "unit_ref": "", "preparation": ", kalt", "unitNotation": "", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-116", "ingredientNotation": "Eiweisse", "shoppingCategory_ref": "ShoppingCategory-rpf-10", }, { "id": "01JBQFM447EDC3NKF6RZAQ9FQV", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-269", "optional": False, "quantity": {"value": 1}, "unit_ref": "65-unit-rdpf3", "preparation": "", "unitNotation": "Prise", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-269", "ingredientNotation": "Salz", "shoppingCategory_ref": "ShoppingCategory-rpf-5", }, { "id": "01JBQFM44772YQA0204VFQZTG9", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-46", "optional": False, "quantity": {"value": 1}, "unit_ref": "23-unit-rdpf3", "preparation": "", "unitNotation": "TL", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-46", "ingredientNotation": "Zitronensaft", "shoppingCategory_ref": "ShoppingCategory-rpf-9", }, { "id": "01JBQFM447TKG7A721C375CF0P", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-288", "optional": False, "quantity": {"value": 1}, "unit_ref": "25-unit-rdpf3", "preparation": "", "unitNotation": "EL", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-288", "ingredientNotation": "Maizena", "shoppingCategory_ref": "ShoppingCategory-rpf-7", }, { "id": "01JBQFM447HKJH45AWGHHG87Q9", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-316", "optional": False, "quantity": {"value": 4}, "unit_ref": "", "preparation": ", geschält, filetiert", "unitNotation": "", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-316", "ingredientNotation": "Orangen", "shoppingCategory_ref": "ShoppingCategory-rpf-8", }, { "id": "01JBQFM4476W1BN6VZBSTZ50KG", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-620", "optional": False, "quantity": {"value": 180}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-620", "ingredientNotation": "Crème fraîche", "shoppingCategory_ref": "ShoppingCategory-rpf-2", }, ], }, { "id": "r59322", "ulid": "01JBQFM447FVYJP9YP0F35PNY9", "title": "Vollwert-Brötchen/Baguettes", "locale": "de", "status": "ok", "language": "de", "clusterId": "043293bf-82be-4548-b2c2-6bb651574c6e", "hasVariants": True, "servingSize": {"quantity": {"value": 12}, "unitNotation": "Stück"}, "isCustomerRecipe": False, "descriptiveAssets": [ { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/665554F6-0147-480B-9CB6-36CC5181140F/Derivates/5D3D75B4-8136-4E6A-8096-861037D5CA12", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/665554F6-0147-480B-9CB6-36CC5181140F/Derivates/5D3D75B4-8136-4E6A-8096-861037D5CA12", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/665554F6-0147-480B-9CB6-36CC5181140F/Derivates/5D3D75B4-8136-4E6A-8096-861037D5CA12", } ], "recipeIngredientGroups": [ { "id": "01JBQFM446FATGVE3EXT8YXSRJ", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-181", "optional": False, "quantity": {"value": 100}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-181", "ingredientNotation": "Weizenkörner", "shoppingCategory_ref": "ShoppingCategory-rpf-7", "recipeAlternativeIngredient": { "localId": "com.vorwerk.ingredients.Ingredient-rpf-185", "optional": False, "quantity": {"value": 100}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-185", "ingredientNotation": "Dinkelkörner", }, }, { "id": "01JBQFM446JK3AW6S781AQ69NE", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-40", "optional": False, "quantity": {"value": 400}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-40", "ingredientNotation": "Weizenmehl", "shoppingCategory_ref": "ShoppingCategory-rpf-7", }, { "id": "01JBQFM447X5SPZX7N1EXJAJKB", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-269", "optional": False, "quantity": {"value": 1.5}, "unit_ref": "23-unit-rdpf3", "preparation": "", "unitNotation": "TL", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-269", "ingredientNotation": "Salz", "shoppingCategory_ref": "ShoppingCategory-rpf-5", }, { "id": "01JBQFM4472Z498TSDDE11C2EG", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-54", "optional": False, "quantity": {"value": 220}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-54", "ingredientNotation": "Wasser", "shoppingCategory_ref": "ShoppingCategory-rpf-9", }, { "id": "01JBQFM447GRGV27DW8JZRWQNX", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-151", "optional": False, "quantity": {"value": 40}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-151", "ingredientNotation": "Öl", "shoppingCategory_ref": "ShoppingCategory-rpf-11", }, { "id": "01JBQFM44709X31KCPYZ1YJ2NS", "isOwned": True, "localId": "com.vorwerk.ingredients.Ingredient-rpf-345", "optional": False, "quantity": {"value": 1}, "unit_ref": "132-unit-rdpf3", "preparation": "(40 g)", "unitNotation": "Würfel", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-345", "ownedTimestamp": 1730586218, "ingredientNotation": "Hefe", "shoppingCategory_ref": "ShoppingCategory-rpf-10", }, { "id": "01JBQFM447DFBB3B109XJ82EGM", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-9", "optional": False, "quantity": {"value": 1}, "unit_ref": "65-unit-rdpf3", "preparation": "", "unitNotation": "Prise", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-9", "ingredientNotation": "Zucker", "shoppingCategory_ref": "ShoppingCategory-rpf-6", }, ], }, ], "customerRecipes": [], "additionalItems": [], } COOKIDOO_TEST_RESPONSE_GET_INGREDIENTS = { "recipes": [ { "id": "r907016", "ulid": "01JBQFM448MWF37WM0CQ763MQ8", "title": "Mini-Pavlova mit Orangen", "locale": "ch", "status": "ok", "language": "de", "hasVariants": False, "isCustomerRecipe": False, "descriptiveAssets": [ { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", }, { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596", }, ], "recipeIngredientGroups": [ { "id": "01JBQFM44769BTC1P25CNDWJK9", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-9", "optional": False, "quantity": {"value": 200}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-9", "ingredientNotation": "Zucker", "shoppingCategory_ref": "ShoppingCategory-rpf-6", }, { "id": "01JBQFM447GYJYP0QR4EYSEX75", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-116", "optional": False, "quantity": {"value": 4}, "unit_ref": "", "preparation": ", kalt", "unitNotation": "", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-116", "ingredientNotation": "Eiweisse", "shoppingCategory_ref": "ShoppingCategory-rpf-10", }, { "id": "01JBQFM447EDC3NKF6RZAQ9FQV", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-269", "optional": False, "quantity": {"value": 1}, "unit_ref": "65-unit-rdpf3", "preparation": "", "unitNotation": "Prise", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-269", "ingredientNotation": "Salz", "shoppingCategory_ref": "ShoppingCategory-rpf-5", }, { "id": "01JBQFM44772YQA0204VFQZTG9", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-46", "optional": False, "quantity": {"value": 1}, "unit_ref": "23-unit-rdpf3", "preparation": "", "unitNotation": "TL", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-46", "ingredientNotation": "Zitronensaft", "shoppingCategory_ref": "ShoppingCategory-rpf-9", }, { "id": "01JBQFM447TKG7A721C375CF0P", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-288", "optional": False, "quantity": {"value": 1}, "unit_ref": "25-unit-rdpf3", "preparation": "", "unitNotation": "EL", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-288", "ingredientNotation": "Maizena", "shoppingCategory_ref": "ShoppingCategory-rpf-7", }, { "id": "01JBQFM447HKJH45AWGHHG87Q9", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-316", "optional": False, "quantity": {"value": 4}, "unit_ref": "", "preparation": ", geschält, filetiert", "unitNotation": "", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-316", "ingredientNotation": "Orangen", "shoppingCategory_ref": "ShoppingCategory-rpf-8", }, { "id": "01JBQFM4476W1BN6VZBSTZ50KG", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-620", "optional": False, "quantity": {"value": 180}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-620", "ingredientNotation": "Crème fraîche", "shoppingCategory_ref": "ShoppingCategory-rpf-2", }, ], }, { "id": "r59322", "ulid": "01JBQFM447FVYJP9YP0F35PNY9", "title": "Vollwert-Brötchen/Baguettes", "locale": "de", "status": "ok", "language": "de", "clusterId": "043293bf-82be-4548-b2c2-6bb651574c6e", "hasVariants": True, "servingSize": {"quantity": {"value": 12}, "unitNotation": "Stück"}, "isCustomerRecipe": False, "descriptiveAssets": [ { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/665554F6-0147-480B-9CB6-36CC5181140F/Derivates/5D3D75B4-8136-4E6A-8096-861037D5CA12", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/665554F6-0147-480B-9CB6-36CC5181140F/Derivates/5D3D75B4-8136-4E6A-8096-861037D5CA12", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/665554F6-0147-480B-9CB6-36CC5181140F/Derivates/5D3D75B4-8136-4E6A-8096-861037D5CA12", } ], "recipeIngredientGroups": [ { "id": "01JBQFM446FATGVE3EXT8YXSRJ", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-181", "optional": False, "quantity": {"value": 100}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-181", "ingredientNotation": "Weizenkörner", "shoppingCategory_ref": "ShoppingCategory-rpf-7", "recipeAlternativeIngredient": { "localId": "com.vorwerk.ingredients.Ingredient-rpf-185", "optional": False, "quantity": {"value": 100}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-185", "ingredientNotation": "Dinkelkörner", }, }, { "id": "01JBQFM446JK3AW6S781AQ69NE", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-40", "optional": False, "quantity": {"value": 400}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-40", "ingredientNotation": "Weizenmehl", "shoppingCategory_ref": "ShoppingCategory-rpf-7", }, { "id": "01JBQFM447X5SPZX7N1EXJAJKB", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-269", "optional": False, "quantity": {"value": 1.5}, "unit_ref": "23-unit-rdpf3", "preparation": "", "unitNotation": "TL", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-269", "ingredientNotation": "Salz", "shoppingCategory_ref": "ShoppingCategory-rpf-5", }, { "id": "01JBQFM4472Z498TSDDE11C2EG", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-54", "optional": False, "quantity": {"value": 220}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-54", "ingredientNotation": "Wasser", "shoppingCategory_ref": "ShoppingCategory-rpf-9", }, { "id": "01JBQFM447GRGV27DW8JZRWQNX", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-151", "optional": False, "quantity": {"value": 40}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-151", "ingredientNotation": "Öl", "shoppingCategory_ref": "ShoppingCategory-rpf-11", }, { "id": "01JBQFM44709X31KCPYZ1YJ2NS", "isOwned": True, "localId": "com.vorwerk.ingredients.Ingredient-rpf-345", "optional": False, "quantity": {"value": 1}, "unit_ref": "132-unit-rdpf3", "preparation": "(40 g)", "unitNotation": "Würfel", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-345", "ownedTimestamp": 1730586218, "ingredientNotation": "Hefe", "shoppingCategory_ref": "ShoppingCategory-rpf-10", }, { "id": "01JBQFM447DFBB3B109XJ82EGM", "isOwned": False, "localId": "com.vorwerk.ingredients.Ingredient-rpf-9", "optional": False, "quantity": {"value": 1}, "unit_ref": "65-unit-rdpf3", "preparation": "", "unitNotation": "Prise", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-9", "ingredientNotation": "Zucker", "shoppingCategory_ref": "ShoppingCategory-rpf-6", }, ], }, ], "customerRecipes": [], "additionalItems": [], } COOKIDOO_TEST_RESPONSE_ADD_INGREDIENTS_FOR_RECIPES: Final = { "data": [ { "id": "r59322", "title": "Vollwert-Brötchen/Baguettes", "locale": "de", "language": "de", "status": "ok", "ulid": "01JBQEYRFQ219DZVSFEG59AWYS", "clusterId": "043293bf-82be-4548-b2c2-6bb651574c6e", "hasVariants": True, "servingSize": {"quantity": {"value": 12}, "unitNotation": "Stück"}, "descriptiveAssets": [ { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/665554F6-0147-480B-9CB6-36CC5181140F/Derivates/5D3D75B4-8136-4E6A-8096-861037D5CA12", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/665554F6-0147-480B-9CB6-36CC5181140F/Derivates/5D3D75B4-8136-4E6A-8096-861037D5CA12", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/665554F6-0147-480B-9CB6-36CC5181140F/Derivates/5D3D75B4-8136-4E6A-8096-861037D5CA12", } ], "isCustomerRecipe": False, "recipeIngredientGroups": [ { "localId": "com.vorwerk.ingredients.Ingredient-rpf-181", "optional": False, "quantity": {"value": 100}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-181", "ingredientNotation": "Weizenkörner", "recipeAlternativeIngredient": { "localId": "com.vorwerk.ingredients.Ingredient-rpf-185", "optional": False, "quantity": {"value": 100}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-185", "ingredientNotation": "Dinkelkörner", }, "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-7", "id": "01JBQEYRFQWG8ZQ20SMKB1JP6S", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-40", "optional": False, "quantity": {"value": 400}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-40", "ingredientNotation": "Weizenmehl", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-7", "id": "01JBQEYRFQEVSSW39WFJVNH18Y", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-269", "optional": False, "quantity": {"value": 1.5}, "unit_ref": "23-unit-rdpf3", "preparation": "", "unitNotation": "TL", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-269", "ingredientNotation": "Salz", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-5", "id": "01JBQEYRFQF6E7VNRPVDHMW9ET", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-54", "optional": False, "quantity": {"value": 220}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-54", "ingredientNotation": "Wasser", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-9", "id": "01JBQEYRFQ4F96ZZ039H8868VW", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-151", "optional": False, "quantity": {"value": 40}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-151", "ingredientNotation": "Öl", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-11", "id": "01JBQEYRFQ10JYVGWPS3E89WKR", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-345", "optional": False, "quantity": {"value": 1}, "unit_ref": "132-unit-rdpf3", "preparation": "(40 g)", "unitNotation": "Würfel", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-345", "ingredientNotation": "Hefe", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-10", "id": "01JBQEYRFQHK543XN78Z622J92", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-9", "optional": False, "quantity": {"value": 1}, "unit_ref": "65-unit-rdpf3", "preparation": "", "unitNotation": "Prise", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-9", "ingredientNotation": "Zucker", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-6", "id": "01JBQEYRFQRZT6XF19N3QDK16J", }, ], }, { "id": "r907016", "title": "Mini-Pavlova mit Orangen", "locale": "ch", "language": "de", "status": "ok", "ulid": "01JBQEYRFRD37Z0F4JB23WAAJR", "hasVariants": False, "descriptiveAssets": [ { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", }, { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c7868b18e43639134880b2777d7fcd9f/Derivates/f7e9031db49d24e9e8e37e98618a830dda45b596", }, ], "isCustomerRecipe": False, "recipeIngredientGroups": [ { "localId": "com.vorwerk.ingredients.Ingredient-rpf-9", "optional": False, "quantity": {"value": 200}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-9", "ingredientNotation": "Zucker", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-6", "id": "01JBQEYRFQAH4W0NEC6KNPBAC9", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-116", "optional": False, "quantity": {"value": 4}, "unit_ref": "", "preparation": ", kalt", "unitNotation": "", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-116", "ingredientNotation": "Eiweisse", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-10", "id": "01JBQEYRFQCWZWG0KY02BAMB3P", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-269", "optional": False, "quantity": {"value": 1}, "unit_ref": "65-unit-rdpf3", "preparation": "", "unitNotation": "Prise", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-269", "ingredientNotation": "Salz", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-5", "id": "01JBQEYRFQFVEJZYT1MSQJ147H", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-46", "optional": False, "quantity": {"value": 1}, "unit_ref": "23-unit-rdpf3", "preparation": "", "unitNotation": "TL", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-46", "ingredientNotation": "Zitronensaft", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-9", "id": "01JBQEYRFQQ39D3HZS4W6RP4D1", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-288", "optional": False, "quantity": {"value": 1}, "unit_ref": "25-unit-rdpf3", "preparation": "", "unitNotation": "EL", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-288", "ingredientNotation": "Maizena", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-7", "id": "01JBQEYRFR908830YPYSE5NECB", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-316", "optional": False, "quantity": {"value": 4}, "unit_ref": "", "preparation": ", geschält, filetiert", "unitNotation": "", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-316", "ingredientNotation": "Orangen", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-8", "id": "01JBQEYRFRRJKKDPJ39403ATTT", }, { "localId": "com.vorwerk.ingredients.Ingredient-rpf-620", "optional": False, "quantity": {"value": 180}, "unit_ref": "11-unit-rdpf3", "preparation": "", "unitNotation": "g", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-620", "ingredientNotation": "Crème fraîche", "isOwned": False, "shoppingCategory_ref": "ShoppingCategory-rpf-2", "id": "01JBQEYRFRJVE95ETRRZABSH57", }, ], }, ] } COOKIDOO_TEST_RESPONSE_EDIT_INGREDIENTS_OWNERSHIP = { "data": [ { "id": "01JBQG02JQD3XPFMM5CXE51K25", "isOwned": True, "localId": "com.vorwerk.ingredients.Ingredient-rpf-345", "optional": False, "quantity": {"value": 1}, "unit_ref": "132-unit-rdpf3", "preparation": "(40 g)", "unitNotation": "Würfel", "ingredient_ref": "com.vorwerk.ingredients.Ingredient-rpf-345", "ingredientNotation": "Hefe", "shoppingCategory_ref": "ShoppingCategory-rpf-10", "ownedTimestamp": 1730586610, } ] } COOKIDOO_TEST_RESPONSE_GET_ADDITIONAL_ITEMS = { "recipes": [], "customerRecipes": [], "additionalItems": [ {"id": "01JBQGAG2VEJM15JWW9C7BQN5W", "name": "Fleisch", "isOwned": False}, { "id": "01JBQGAG2WH9EQ9GHH7XN7NWGP", "name": "Vogel", "isOwned": True, "ownedTimestamp": 1730586951, }, ], } COOKIDOO_TEST_RESPONSE_ADD_ADDITIONAL_ITEMS = { "data": [ {"id": "01JBQGDMRMR7RJW1C8AWDGD6YP", "name": "Fleisch", "isOwned": False}, {"id": "01JBQGDMRNHAM7AMCR6YKPYKJQ", "name": "Fisch", "isOwned": False}, ] } COOKIDOO_TEST_RESPONSE_EDIT_ADDITIONAL_ITEMS_OWNERSHIP = { "data": [ { "id": "01JBQGMGMY4KD9ZGTKAS6GQME0", "name": "Fisch", "isOwned": True, "ownedTimestamp": 1730587279, } ] } COOKIDOO_TEST_RESPONSE_EDIT_ADDITIONAL_ITEMS = { "data": [ { "id": "01JBQGT72WP8Z31VCPQPT5VC6F", "name": "Vogel", "isOwned": True, "ownedTimestamp": 1730587466, } ] } COOKIDOO_TEST_RESPONSE_GET_MANAGED_COLLECTIONS = { "managedlists": [ { "id": "col500561", "created": "2024-11-06T22:28:13.202+00:00", "modified": "2024-11-06T22:28:13.202+00:00", "title": "Schneeweiss und Zuckersüss", "description": "Schneeweisse Delikatessen wie Pralinen und cremige Desserts sind ein wahres Fest für den Gaumen. In dieser Kollektion entdeckst du einige zuckersüsse Ideen, die mühelos mit deinem Thermomix® zubereitet werden können.", "chapters": [ { "title": "Schneeweiss und Zuckersüss", "recipes": [ { "id": "r907016", "title": "Mini-Pavlova mit Orangen", "type": "VORWERK", "totalTime": "6600.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", } }, }, { "id": "r907015", "title": "Kokos Pralinen", "type": "VORWERK", "totalTime": "32400.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", } }, }, { "id": "r907014", "title": "Quarkcreme mit weisser Schoggi", "type": "VORWERK", "totalTime": "4200.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485", } }, }, { "id": "r907013", "title": "Pannacotta mit Ananaskompott", "type": "VORWERK", "totalTime": "16800.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb", } }, }, { "id": "r437886", "title": "Zimtsterne", "type": "VORWERK", "totalTime": "5400.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d", } }, }, { "id": "r907056", "title": "Weisse Schoggimilch", "type": "VORWERK", "totalTime": "480.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7", } }, }, ], } ], "version": 0, "author": "Vorwerk", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/collection/ras/Assets/38cf56dff995c01c9a4ce2ce3b9f0bf8/Derivates/d8d563384bbd2652791aedfcb3200dde0e6221cb", "portrait": "", "landscape": "", } }, "listType": "MANAGEDLIST", } ], "page": {"page": 0, "totalPages": 1, "totalElements": 1}, "links": {"self": "/organize/de-CH/api/managed-list?page=0"}, } COOKIDOO_TEST_RESPONSE_ADD_MANAGED_COLLECTION = { "message": "Kollektion wurde in Meine Rezepte gespeichert!", "content": { "id": "col500561", "userId": "4a74c102-3ee6-4bcd-9227-2c403900b8de", "collectionId": "col500561", "created": "2024-11-06T22:28:13.202+00:00", "modified": "2024-11-06T22:28:13.202+00:00", "title": "Schneeweiss und Zuckersüss", "asciiTitle": "Schneeweiss und Zuckersuss", "description": "Schneeweisse Delikatessen wie Pralinen und cremige Desserts sind ein wahres Fest für den Gaumen. In dieser Kollektion entdeckst du einige zuckersüsse Ideen, die mühelos mit deinem Thermomix® zubereitet werden können.", "chapters": [ { "title": "Schneeweiss und Zuckersüss", "recipeIds": [ "r907016", "r907015", "r907014", "r907013", "r437886", "r907056", ], "recipes": [ { "id": "r907016", "title": "Mini-Pavlova mit Orangen", "type": "VORWERK", "asciiTitle": "Mini-Pavlova mit Orangen", "favoriteCount": None, "portion": None, "prepTime": "1200.0", "totalTime": "6600.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006", } }, "squareRetinaImage": "https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg", "squareImageSrcSet": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 2x", "responsiveImageSizes": "(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px", "landscapeImage": "https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg", "squareImage": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg", "responsiveImageSrcset": "https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/1659a7ed225400e0378dfc0071fa4045/Derivates/a9a2a24272093f7aa636d4afa84d99cff10ac006.jpg 276w", }, { "id": "r907015", "title": "Kokos Pralinen", "type": "VORWERK", "asciiTitle": "Kokos Pralinen", "favoriteCount": None, "portion": None, "prepTime": "2700.0", "totalTime": "32400.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", } }, "squareRetinaImage": "https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg", "squareImageSrcSet": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 2x", "responsiveImageSizes": "(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px", "landscapeImage": "https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg", "squareImage": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg", "responsiveImageSrcset": "https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e.jpg 276w", }, { "id": "r907014", "title": "Quarkcreme mit weisser Schoggi", "type": "VORWERK", "asciiTitle": "Quarkcreme mit weisser Schoggi", "favoriteCount": None, "portion": None, "prepTime": "600.0", "totalTime": "4200.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485", } }, "squareRetinaImage": "https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg", "squareImageSrcSet": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 2x", "responsiveImageSizes": "(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px", "landscapeImage": "https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg", "squareImage": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg", "responsiveImageSrcset": "https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/3d143628d95418757a3880f1b0770391/Derivates/0edd5171214ff97caa2aaa0389c1f78321637485.jpg 276w", }, { "id": "r907013", "title": "Pannacotta mit Ananaskompott", "type": "VORWERK", "asciiTitle": "Pannacotta mit Ananaskompott", "favoriteCount": None, "portion": None, "prepTime": "2400.0", "totalTime": "16800.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb", } }, "squareRetinaImage": "https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg", "squareImageSrcSet": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 2x", "responsiveImageSizes": "(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px", "landscapeImage": "https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg", "squareImage": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg", "responsiveImageSrcset": "https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/8C8D3D2B-32A5-4092-A578-E798CD2FB3D2/Derivates/5cd65bed-d9d9-4c82-b124-95f9204436fb.jpg 276w", }, { "id": "r437886", "title": "Zimtsterne", "type": "VORWERK", "asciiTitle": "Zimtsterne", "favoriteCount": None, "portion": None, "prepTime": "2700.0", "totalTime": "5400.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d", } }, "squareRetinaImage": "https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg", "squareImageSrcSet": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 2x", "responsiveImageSizes": "(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px", "landscapeImage": "https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg", "squareImage": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg", "responsiveImageSrcset": "https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/C37A9482-EDC2-4A45-AE91-E60B4173B89D/Derivates/b33e52f8c97dfb54e77a07953855f516309fca5d.jpg 276w", }, { "id": "r907056", "title": "Weisse Schoggimilch", "type": "VORWERK", "asciiTitle": "Weisse Schoggimilch", "favoriteCount": None, "portion": None, "prepTime": "480.0", "totalTime": "480.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7", } }, "squareRetinaImage": "https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg", "squareImageSrcSet": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 1x, https://assets.tmecosys.com/image/upload/t_web72x72@2x/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 2x", "responsiveImageSizes": "(min-width: 1333px) 276px, (min-width: 1024px) 286px, (min-width: 768px) 220px, 140px", "landscapeImage": "https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg", "squareImage": "https://assets.tmecosys.com/image/upload/t_web72x72/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg", "responsiveImageSrcset": "https://assets.tmecosys.com/image/upload/t_web276x230@2x/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 552w, https://assets.tmecosys.com/image/upload/t_web378x315/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 378w, https://assets.tmecosys.com/image/upload/t_web276x230/img/recipe/ras/Assets/4e81853553180b72aa33b758dbbfc778/Derivates/7cf0a02bced35066fc6031d3eefca7c9c6a79ad7.jpg 276w", }, ], } ], "recipeCount": 6, "version": 0, "author": "Vorwerk", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/collection/ras/Assets/38cf56dff995c01c9a4ce2ce3b9f0bf8/Derivates/d8d563384bbd2652791aedfcb3200dde0e6221cb", "portrait": "", "landscape": "", } }, "listType": "MANAGEDLIST", "uid": "col500561", "listTypeName": "MANAGEDLIST", }, "code": None, } COOKIDOO_TEST_RESPONSE_GET_CUSTOM_COLLECTIONS = { "customlists": [ { "id": "01JC1SRPRSW0SHE0AK8GCASABX", "title": "Testliste1", "version": 4, "created": "2024-11-06T22:33:18.873+00:00", "modified": "2024-11-06T22:36:25.914+00:00", "chapters": [{"title": "", "recipes": []}], "assets": {"images": {"square": "", "portrait": "", "landscape": ""}}, "sharedListId": None, "shared": False, "listType": "CUSTOMLIST", "author": "4a74c102-3ee6-4bcd-9227-2c403900b8de", } ], "page": {"page": 0, "totalPages": 1, "totalElements": 1}, "links": {"self": "/organize/de-CH/api/custom-list?page=0"}, } COOKIDOO_TEST_RESPONSE_ADD_CUSTOM_COLLECTION = { "message": "Rezeptliste wurde erfolgreich erstellt!", "content": { "id": "01JC1SRPRSW0SHE0AK8GCASABX", "title": "Testliste", "version": 1, "created": "2024-11-06T22:33:18.873+00:00", "modified": "2024-11-06T22:33:18.873+00:00", "chapters": [{"title": "", "recipes": []}], "assets": {"images": {"square": "", "portrait": "", "landscape": ""}}, "sharedListId": None, "author": "4a74c102-3ee6-4bcd-9227-2c403900b8de", "listType": "CUSTOMLIST", "shared": False, }, "code": None, } COOKIDOO_TEST_RESPONSE_ADD_RECIPES_TO_CUSTOM_COLLECTION = { "message": "Rezeptliste wurde erfolgreich aktualisiert!", "content": { "id": "01JC1SRPRSW0SHE0AK8GCASABX", "title": "Testliste1", "version": 3, "created": "2024-11-06T22:33:18.873+00:00", "modified": "2024-11-06T22:35:24.467+00:00", "chapters": [ { "title": "", "recipes": [ { "id": "r907015", "title": "Kokos Pralinen", "type": "VORWERK", "totalTime": "32400.0", "locale": "", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", } }, } ], } ], "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/94e7404b092f239d09803fdbd4ddbac1/Derivates/ccee0755073a78a95b79d96ba26fd5e3e89c137e", } }, "sharedListId": None, "shared": False, "listType": "CUSTOMLIST", "author": "4a74c102-3ee6-4bcd-9227-2c403900b8de", }, "code": None, } COOKIDOO_TEST_RESPONSE_REMOVE_RECIPE_FROM_CUSTOM_COLLECTION = { "message": "Rezept wurde aus dieser Rezeptliste entfernt!", "content": { "id": "01JC1SRPRSW0SHE0AK8GCASABX", "title": "Testliste1", "version": 4, "created": "2024-11-06T22:33:18.873+00:00", "modified": "2024-11-06T22:36:25.914+00:00", "chapters": [{"title": "", "recipes": []}], "assets": {"images": {"square": "", "portrait": "", "landscape": ""}}, "sharedListId": None, "author": "4a74c102-3ee6-4bcd-9227-2c403900b8de", "listType": "CUSTOMLIST", "shared": False, }, "code": None, } COOKIDOO_TEST_RESPONSE_CALENDAR_WEEK = { "myDays": [ { "id": "2025-03-04", "title": "2025-03-04", "dayKey": "2025-03-04", "created": "2025-03-04T18:56:05.424+00:00", "modified": "2025-03-04T18:56:05.424+00:00", "recipes": [ { "id": "r214846", "title": "Waffles", "totalTime": "1500.0", "locale": "gb", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3", } }, } ], "customerRecipeIds": [], "author": "4a74c102-3ee6-4bcd-9227-2c403900b8de", }, { "id": "2025-03-05", "title": "2025-03-05", "dayKey": "2025-03-05", "created": "2025-03-04T19:00:02.773+00:00", "modified": "2025-03-04T19:00:02.773+00:00", "recipes": [ { "id": "r338888", "title": "Moroccan Mint Tea", "totalTime": "1500.0", "locale": "gb", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/47d318332476db821ab3d1c5414f772e/Derivates/8d7d71b9c549cb55b11cc965a6b47be108ab44e5", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/47d318332476db821ab3d1c5414f772e/Derivates/8d7d71b9c549cb55b11cc965a6b47be108ab44e5", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/47d318332476db821ab3d1c5414f772e/Derivates/8d7d71b9c549cb55b11cc965a6b47be108ab44e5", } }, } ], "customerRecipeIds": [], "author": "4a74c102-3ee6-4bcd-9227-2c403900b8de", }, ] } COOKIDOO_TEST_RESPONSE_ADD_RECIPES_TO_CALENDAR = { "message": "Recipe Waffles planned for 4 Mar 2025!", "content": { "id": "2025-03-04", "title": "2025-03-04", "dayKey": "2025-03-04", "created": "2025-03-04T18:56:05.424+00:00", "modified": "2025-03-04T18:56:05.424+00:00", "recipes": [ { "id": "r214846", "title": "Waffles", "totalTime": "1500.0", "locale": "gb", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3", } }, } ], "customerRecipeIds": [], "author": "4a74c102-3ee6-4bcd-9227-2c403900b8de", }, "code": None, } COOKIDOO_TEST_RESPONSE_REMOVE_RECIPE_FROM_CALENDAR = { "message": "Recipe Waffles was removed!", "content": { "id": "2025-03-04", "title": "2025-03-04", "dayKey": "2025-03-04", "created": "2025-03-04T18:56:05.424+00:00", "modified": "2025-03-04T18:56:05.424+00:00", "recipes": [ { "id": "r214846", "title": "Waffles", "totalTime": "1500.0", "locale": "gb", "assets": { "images": { "square": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3", "portrait": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3", "landscape": "https://assets.tmecosys.com/image/upload/{transformation}/img/recipe/ras/Assets/c6ea15c4-52ab-4119-b886-a7fb1d052f45/Derivates/b18fcddc-3b31-4ae9-9cd1-5a81751d91b3", } }, } ], "customerRecipeIds": [], "author": "4a74c102-3ee6-4bcd-9227-2c403900b8de", }, "code": None, } miaucl-cookidoo-api-5292e37/tests/ruff.toml000066400000000000000000000015111476166105100206270ustar00rootroot00000000000000extend = "../pyproject.toml" [lint] extend-select = [ "PT001", # Use @pytest.fixture without parentheses "PT002", # Configuration for fixture specified via positional args, use kwargs "PT003", # The scope='function' is implied in @pytest.fixture() "PT006", # Single parameter in parameterize is a string, multiple a tuple "PT013", # Found incorrect pytest import, use simple import pytest instead "PT015", # Assertion always fails, replace with pytest.fail() "PT021", # use yield instead of request.addfinalizer "PT022", # No teardown in fixture, replace useless yield with return ] extend-ignore = [ "PLC", # pylint "PLE", # pylint "PLR", # pylint "PLW", # pylint "B904", # Use raise from to specify exception cause "N815", # Variable {name} in class scope should not be mixedCase ]miaucl-cookidoo-api-5292e37/tests/test_cookidoo.py000066400000000000000000002303221476166105100222130ustar00rootroot00000000000000"""Unit tests for cookidoo-api.""" from datetime import datetime from http import HTTPStatus from aiohttp import ClientError, ClientSession from aioresponses import aioresponses from dotenv import load_dotenv import pytest from cookidoo_api.cookidoo import Cookidoo from cookidoo_api.exceptions import ( CookidooAuthException, CookidooConfigException, CookidooException, CookidooParseException, CookidooRequestException, ) from cookidoo_api.helpers import get_localization_options from cookidoo_api.types import ( CookidooAdditionalItem, CookidooConfig, CookidooIngredientItem, ) from tests.responses import ( COOKIDOO_TEST_RESPONSE_ACTIVE_SUBSCRIPTION, COOKIDOO_TEST_RESPONSE_ADD_ADDITIONAL_ITEMS, COOKIDOO_TEST_RESPONSE_ADD_CUSTOM_COLLECTION, COOKIDOO_TEST_RESPONSE_ADD_INGREDIENTS_FOR_RECIPES, COOKIDOO_TEST_RESPONSE_ADD_MANAGED_COLLECTION, COOKIDOO_TEST_RESPONSE_ADD_RECIPES_TO_CALENDAR, COOKIDOO_TEST_RESPONSE_ADD_RECIPES_TO_CUSTOM_COLLECTION, COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE, COOKIDOO_TEST_RESPONSE_CALENDAR_WEEK, COOKIDOO_TEST_RESPONSE_EDIT_ADDITIONAL_ITEMS, COOKIDOO_TEST_RESPONSE_EDIT_ADDITIONAL_ITEMS_OWNERSHIP, COOKIDOO_TEST_RESPONSE_EDIT_INGREDIENTS_OWNERSHIP, COOKIDOO_TEST_RESPONSE_GET_ADDITIONAL_ITEMS, COOKIDOO_TEST_RESPONSE_GET_CUSTOM_COLLECTIONS, COOKIDOO_TEST_RESPONSE_GET_INGREDIENTS, COOKIDOO_TEST_RESPONSE_GET_MANAGED_COLLECTIONS, COOKIDOO_TEST_RESPONSE_GET_RECIPE_DETAILS, COOKIDOO_TEST_RESPONSE_GET_SHOPPING_LIST_RECIPES, COOKIDOO_TEST_RESPONSE_INACTIVE_SUBSCRIPTION, COOKIDOO_TEST_RESPONSE_REMOVE_RECIPE_FROM_CALENDAR, COOKIDOO_TEST_RESPONSE_REMOVE_RECIPE_FROM_CUSTOM_COLLECTION, COOKIDOO_TEST_RESPONSE_USER_INFO, ) load_dotenv() class TestGetterSetter: """Tests for getter and setter.""" @pytest.mark.parametrize( ("country", "language", "prefix"), [ ("ch", "de-CH", "ch"), ("de", "de-DE", "de"), ("ma", "en", "xp"), ("ie", "en-GB", "gb"), ("gb", "en-GB", "gb"), ], ) async def test_api_endpoint( self, mocked: aioresponses, session: ClientSession, country: str, language: str, prefix: str, ) -> None: """Test api endpoint for different localizations.""" cookidoo = Cookidoo( session, cfg=CookidooConfig( localization=( await get_localization_options(country=country, language=language) )[0], ), ) assert ( str(cookidoo.api_endpoint) == f"https://{prefix}.tmmobile.vorwerk-digital.com" ) class TestLogin: """Tests for login method.""" async def test_refresh_before_login( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test refresh before login.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/ciam/auth/token", status=400, ) expected = "No auth data available, please log in first" with pytest.raises(CookidooConfigException, match=expected): await cookidoo.refresh_token() async def test_mail_invalid(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test login with invalid e-mail.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/ciam/auth/token", status=400, ) expected = "Access token request failed due to bad request, please check your email or refresh token." with pytest.raises(CookidooAuthException, match=expected): await cookidoo.login() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test login with unauthorized user.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/ciam/auth/token", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) expected = "Access token request failed due to authorization failure, please check your email and password or refresh token." with pytest.raises(CookidooAuthException, match=expected): await cookidoo.login() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/ciam/auth/token", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.login() @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exceptions( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/ciam/auth/token", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.login() async def test_login_and_refresh( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test login and refresh with valid user.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/ciam/auth/token", status=HTTPStatus.OK, payload=COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE, ) assert cookidoo.auth_data is None data = await cookidoo.login() assert data.access_token == COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE["access_token"] assert ( data.refresh_token == COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE["refresh_token"] ) assert data.expires_in == COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE["expires_in"] assert cookidoo.expires_in > 0 assert cookidoo.localization is not None mocked.post( "https://ch.tmmobile.vorwerk-digital.com/ciam/auth/token", status=HTTPStatus.OK, payload=COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE, ) data = await cookidoo.refresh_token() assert data.access_token == COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE["access_token"] assert ( data.refresh_token == COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE["refresh_token"] ) assert data.expires_in == COOKIDOO_TEST_RESPONSE_AUTH_RESPONSE["expires_in"] assert cookidoo.expires_in > 0 class TestGetUserInfo: """Tests for get_user_info method.""" async def test_get_user_info( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_user_info.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/community/profile", payload=COOKIDOO_TEST_RESPONSE_USER_INFO, status=HTTPStatus.OK, ) data = await cookidoo.get_user_info() assert ( data.username == COOKIDOO_TEST_RESPONSE_USER_INFO["userInfo"]["username"] # type: ignore[index] ) assert ( data.description == COOKIDOO_TEST_RESPONSE_USER_INFO["userInfo"]["description"] # type: ignore[index] ) assert ( data.picture == COOKIDOO_TEST_RESPONSE_USER_INFO["userInfo"]["picture"] # type: ignore[index] ) @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/community/profile", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.get_user_info() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/community/profile", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.get_user_info() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/community/profile", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.get_user_info() class TestGetActiveSubscription: """Tests for get_active_subscription method.""" async def test_get_active_subscription( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_active_subscription.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/ownership/subscriptions", payload=COOKIDOO_TEST_RESPONSE_ACTIVE_SUBSCRIPTION, status=HTTPStatus.OK, ) data = await cookidoo.get_active_subscription() assert data assert data.active assert data.status == "RUNNING" async def test_get_inactive_subscription( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_active_subscription.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/ownership/subscriptions", payload=COOKIDOO_TEST_RESPONSE_INACTIVE_SUBSCRIPTION, status=HTTPStatus.OK, ) data = await cookidoo.get_active_subscription() assert data is None @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/ownership/subscriptions", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.get_active_subscription() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/ownership/subscriptions", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.get_active_subscription() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/ownership/subscriptions", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.get_active_subscription() class TestGetRecipeDetails: """Tests for get_recipe_details method.""" async def test_get_recipe_details( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_recipe_details.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/recipes/recipe/de-CH/r907015", payload=COOKIDOO_TEST_RESPONSE_GET_RECIPE_DETAILS, status=HTTPStatus.OK, ) data = await cookidoo.get_recipe_details("r907015") assert data assert isinstance(data, object) assert data.id == "r907015" assert data.name == "Kokos Pralinen" assert isinstance(data.categories, list) assert isinstance(data.collections, list) assert isinstance(data.ingredients, list) assert isinstance(data.notes, list) assert isinstance(data.utensils, list) assert isinstance(data.active_time, int) assert isinstance(data.total_time, int) assert isinstance(data.serving_size, str) @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/recipes/recipe/de-CH/r907015", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.get_recipe_details("r907015") async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/recipes/recipe/de-CH/r907015", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.get_recipe_details("r907015") @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/recipes/recipe/de-CH/r907015", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.get_recipe_details("r907015") class TestGetShoppingListRecipes: """Tests for get_shopping_list_recipes method.""" async def test_get_shopping_list_recipes( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_shopping_list_recipes.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", payload=COOKIDOO_TEST_RESPONSE_GET_SHOPPING_LIST_RECIPES, status=HTTPStatus.OK, ) data = await cookidoo.get_shopping_list_recipes() assert data assert isinstance(data, list) assert len(data) == 2 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.get_shopping_list_recipes() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.get_shopping_list_recipes() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.get_shopping_list_recipes() class TestGetIngredients: """Tests for get_ingredient_items method.""" async def test_get_ingredient_items( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_ingredient_items.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", payload=COOKIDOO_TEST_RESPONSE_GET_INGREDIENTS, status=HTTPStatus.OK, ) data = await cookidoo.get_ingredient_items() assert data assert isinstance(data, list) assert len(data) == 14 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.get_ingredient_items() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.get_ingredient_items() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.get_ingredient_items() class TestAddIngredientsForRecipes: """Tests for add_ingredient_items_for_recipes method.""" async def test_add_ingredient_items_for_recipes( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for add_ingredient_items_for_recipes.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/add", payload=COOKIDOO_TEST_RESPONSE_ADD_INGREDIENTS_FOR_RECIPES, status=HTTPStatus.OK, ) data = await cookidoo.add_ingredient_items_for_recipes(["r59322", "r907016"]) assert data assert isinstance(data, list) assert len(data) == 14 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/add", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.add_ingredient_items_for_recipes(["r59322", "r907016"]) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/add", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.add_ingredient_items_for_recipes(["r59322", "r907016"]) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/add", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.add_ingredient_items_for_recipes(["r59322", "r907016"]) class TestRemoveIngredientsForRecipes: """Tests for remove_ingredient_items_for_recipes method.""" async def test_remove_ingredient_items_for_recipes( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for remove_ingredient_items_for_recipes.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/remove", payload=None, status=HTTPStatus.OK, ) await cookidoo.remove_ingredient_items_for_recipes(["r59322", "r907016"]) @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/remove", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.remove_ingredient_items_for_recipes(["r59322", "r907016"]) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/remove", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.remove_ingredient_items_for_recipes(["r59322", "r907016"]) @pytest.mark.parametrize( ("status", "exception"), [ # (HTTPStatus.OK, CookidooParseException), # There is nothing to parse (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/recipes/remove", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.remove_ingredient_items_for_recipes(["r59322", "r907016"]) class TestEditIngredientsOwnership: """Tests for edit_ingredient_items_ownership method.""" async def test_edit_ingredient_items_ownership( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for edit_ingredient_items_ownership.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/owned-ingredients/ownership/edit", payload=COOKIDOO_TEST_RESPONSE_EDIT_INGREDIENTS_OWNERSHIP, status=HTTPStatus.OK, ) data = await cookidoo.edit_ingredient_items_ownership( [ CookidooIngredientItem( id="01JBQG02JQD3XPFMM5CXE51K25", name="Hefe", is_owned=True, description="1 Würfel", ) ] ) assert data assert isinstance(data, list) assert len(data) == 1 assert data[0].is_owned @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/owned-ingredients/ownership/edit", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.edit_ingredient_items_ownership( [ CookidooIngredientItem( id="01JBQG02JQD3XPFMM5CXE51K25", name="Hefe", is_owned=True, description="1 Würfel", ) ] ) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/owned-ingredients/ownership/edit", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.edit_ingredient_items_ownership( [ CookidooIngredientItem( id="01JBQG02JQD3XPFMM5CXE51K25", name="Hefe", is_owned=True, description="1 Würfel", ) ] ) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/owned-ingredients/ownership/edit", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.edit_ingredient_items_ownership( [ CookidooIngredientItem( id="01JBQG02JQD3XPFMM5CXE51K25", name="Hefe", is_owned=True, description="1 Würfel", ) ] ) class TestGetAdditionalItems: """Tests for get_additional_items method.""" async def test_get_additional_items( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_additional_items.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", payload=COOKIDOO_TEST_RESPONSE_GET_ADDITIONAL_ITEMS, status=HTTPStatus.OK, ) data = await cookidoo.get_additional_items() assert data assert isinstance(data, list) assert len(data) == 2 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.get_additional_items() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.get_additional_items() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.get_additional_items() class TestAddAdditionalItems: """Tests for add_additional_items method.""" async def test_add_additional_items( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for add_additional_items.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/add", payload=COOKIDOO_TEST_RESPONSE_ADD_ADDITIONAL_ITEMS, status=HTTPStatus.OK, ) data = await cookidoo.add_additional_items(["Fleisch", "Fisch"]) assert data assert isinstance(data, list) assert len(data) == 2 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/add", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.add_additional_items(["Fleisch", "Fisch"]) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/add", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.add_additional_items(["Fleisch", "Fisch"]) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/add", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.add_additional_items(["Fleisch", "Fisch"]) class TestRemoveAdditionalItems: """Tests for remove_ingredient_items_for_recipes method.""" async def test_remove_ingredient_items_for_recipes( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for remove_ingredient_items_for_recipes.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/remove", payload=None, status=HTTPStatus.OK, ) await cookidoo.remove_additional_items( ["01JBQGDMRMR7RJW1C8AWDGD6YP", "01JBQGDMRNHAM7AMCR6YKPYKJQ"] ) @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/remove", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.remove_additional_items( ["01JBQGDMRMR7RJW1C8AWDGD6YP", "01JBQGDMRNHAM7AMCR6YKPYKJQ"] ) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/remove", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.remove_additional_items( ["01JBQGDMRMR7RJW1C8AWDGD6YP", "01JBQGDMRNHAM7AMCR6YKPYKJQ"] ) @pytest.mark.parametrize( ("status", "exception"), [ # (HTTPStatus.OK, CookidooParseException), # There is nothing to parse (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/remove", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.remove_additional_items( ["01JBQGDMRMR7RJW1C8AWDGD6YP", "01JBQGDMRNHAM7AMCR6YKPYKJQ"] ) class TestEditAdditionalItemsOwnership: """Tests for edit_additional_items_ownership method.""" async def test_edit_additional_items_ownership( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for edit_additional_items_ownership.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/ownership/edit", payload=COOKIDOO_TEST_RESPONSE_EDIT_ADDITIONAL_ITEMS_OWNERSHIP, status=HTTPStatus.OK, ) data = await cookidoo.edit_additional_items_ownership( [ CookidooAdditionalItem( id="01JBQGMGMY4KD9ZGTKAS6GQME0", name="Fisch", is_owned=True, ) ] ) assert data assert isinstance(data, list) assert len(data) == 1 assert data[0].is_owned @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/ownership/edit", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.edit_additional_items_ownership( [ CookidooAdditionalItem( id="01JBQGMGMY4KD9ZGTKAS6GQME0", name="Fisch", is_owned=True, ) ] ) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/ownership/edit", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.edit_additional_items_ownership( [ CookidooAdditionalItem( id="01JBQGMGMY4KD9ZGTKAS6GQME0", name="Fisch", is_owned=True, ) ] ) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/ownership/edit", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.edit_additional_items_ownership( [ CookidooAdditionalItem( id="01JBQGMGMY4KD9ZGTKAS6GQME0", name="Fisch", is_owned=True, ) ] ) class TestEditAdditionalItems: """Tests for edit_additional_items method.""" async def test_edit_additional_items( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for edit_additional_items.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/edit", payload=COOKIDOO_TEST_RESPONSE_EDIT_ADDITIONAL_ITEMS, status=HTTPStatus.OK, ) data = await cookidoo.edit_additional_items( [ CookidooAdditionalItem( id="01JBQGT72WP8Z31VCPQPT5VC6F", name="Vogel", is_owned=True, ) ] ) assert data assert isinstance(data, list) assert len(data) == 1 assert data[0].name == "Vogel" @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/edit", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.edit_additional_items( [ CookidooAdditionalItem( id="01JBQGT72WP8Z31VCPQPT5VC6F", name="Vogel", is_owned=True, ) ] ) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/edit", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.edit_additional_items( [ CookidooAdditionalItem( id="01JBQGT72WP8Z31VCPQPT5VC6F", name="Vogel", is_owned=True, ) ] ) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH/additional-items/edit", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.edit_additional_items( [ CookidooAdditionalItem( id="01JBQGT72WP8Z31VCPQPT5VC6F", name="Vogel", is_owned=True, ) ] ) class TestClearShoppingList: """Tests for clear_shopping_list method.""" async def test_clear_shopping_list( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for clear_shopping_list.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", payload=None, status=HTTPStatus.OK, ) await cookidoo.clear_shopping_list() @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.clear_shopping_list() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.clear_shopping_list() @pytest.mark.parametrize( ("status", "exception"), [ # (HTTPStatus.OK, CookidooParseException), # There is nothing to parse (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/shopping/de-CH", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.clear_shopping_list() class TestCountManagedLists: """Tests for count_managed_lists method.""" async def test_count_managed_lists( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for count_managed_lists.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list", payload=COOKIDOO_TEST_RESPONSE_GET_MANAGED_COLLECTIONS, status=HTTPStatus.OK, ) count_recipes, count_pages = await cookidoo.count_managed_collections() assert count_recipes == 1 assert count_pages == 1 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.count_managed_collections() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.count_managed_collections() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.count_managed_collections() class TestGetManagedLists: """Tests for get_managed_lists method.""" async def test_get_managed_lists( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_managed_lists.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list?page=0", payload=COOKIDOO_TEST_RESPONSE_GET_MANAGED_COLLECTIONS, status=HTTPStatus.OK, ) data = await cookidoo.get_managed_collections() assert data assert isinstance(data, list) assert len(data) == 1 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list?page=0", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.get_managed_collections() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list?page=0", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.get_managed_collections() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list?page=0", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.get_managed_collections() class TestAddManagedCollection: """Tests for add_managed_collection method.""" async def test_add_managed_collection( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for add_managed_collection.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list", payload=COOKIDOO_TEST_RESPONSE_ADD_MANAGED_COLLECTION, status=HTTPStatus.OK, ) data = await cookidoo.add_managed_collection("col500561") assert data assert data.id == "col500561" @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.add_managed_collection("col500561") async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.add_managed_collection("col500561") @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.add_managed_collection("col500561") class TestRemoveManagedCollection: """Tests for remove_managed_collection method.""" async def test_remove_managed_collection( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for remove_managed_collection.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list/col500561", payload=None, status=HTTPStatus.OK, ) await cookidoo.remove_managed_collection("col500561") @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list/col500561", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.remove_managed_collection("col500561") async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list/col500561", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.remove_managed_collection("col500561") @pytest.mark.parametrize( ("status", "exception"), [ # (HTTPStatus.OK, CookidooParseException), # There is nothing to parse (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/managed-list/col500561", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.remove_managed_collection("col500561") class TestCountCustomLists: """Tests for count_custom_lists method.""" async def test_count_custom_lists( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for count_custom_lists.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list", payload=COOKIDOO_TEST_RESPONSE_GET_CUSTOM_COLLECTIONS, status=HTTPStatus.OK, ) count_recipes, count_pages = await cookidoo.count_custom_collections() assert count_recipes == 1 assert count_pages == 1 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.count_custom_collections() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.count_custom_collections() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.count_custom_collections() class TestGetCustomLists: """Tests for get_custom_lists method.""" async def test_get_custom_lists( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_custom_lists.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list?page=0", payload=COOKIDOO_TEST_RESPONSE_GET_CUSTOM_COLLECTIONS, status=HTTPStatus.OK, ) data = await cookidoo.get_custom_collections() assert data assert isinstance(data, list) assert len(data) == 1 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list?page=0", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.get_custom_collections() async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list?page=0", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.get_custom_collections() @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list?page=0", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.get_custom_collections() class TestAddCustomCollection: """Tests for add_custom_collection method.""" async def test_add_custom_collection( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for add_custom_collection.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list", payload=COOKIDOO_TEST_RESPONSE_ADD_CUSTOM_COLLECTION, status=HTTPStatus.OK, ) data = await cookidoo.add_custom_collection("Testliste") assert data assert data.id == "01JC1SRPRSW0SHE0AK8GCASABX" assert data.name == "Testliste" @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.add_custom_collection("TEST_COLLECTION") async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.add_custom_collection("TEST_COLLECTION") @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.post( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.add_custom_collection("TEST_COLLECTION") class TestRemoveCustomCollection: """Tests for remove_custom_collection method.""" async def test_remove_custom_collection( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for remove_custom_collection.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX", payload=None, status=HTTPStatus.OK, ) await cookidoo.remove_custom_collection("01JC1SRPRSW0SHE0AK8GCASABX") @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.remove_custom_collection("01JC1SRPRSW0SHE0AK8GCASABX") async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.remove_custom_collection("01JC1SRPRSW0SHE0AK8GCASABX") @pytest.mark.parametrize( ("status", "exception"), [ # (HTTPStatus.OK, CookidooParseException), # There is nothing to parse (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.remove_custom_collection("01JC1SRPRSW0SHE0AK8GCASABX") class TestAddRecipesToCustomCollection: """Tests for add_recipes_to_custom_collection method.""" async def test_add_recipes_to_custom_collection( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for add_recipes_to_custom_collection.""" mocked.put( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX", payload=COOKIDOO_TEST_RESPONSE_ADD_RECIPES_TO_CUSTOM_COLLECTION, status=HTTPStatus.OK, ) data = await cookidoo.add_recipes_to_custom_collection( "01JC1SRPRSW0SHE0AK8GCASABX", ["r907015"] ) assert data assert data.id == "01JC1SRPRSW0SHE0AK8GCASABX" assert data.chapters[0].recipes[0].id == "r907015" @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.put( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.add_recipes_to_custom_collection( "01JC1SRPRSW0SHE0AK8GCASABX", ["r907015"] ) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.put( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.add_recipes_to_custom_collection( "01JC1SRPRSW0SHE0AK8GCASABX", ["r907015"] ) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.put( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.add_recipes_to_custom_collection( "01JC1SRPRSW0SHE0AK8GCASABX", ["r907015"] ) class TestRemoveRecipeFromCustomCollection: """Tests for remove_recipe_from_custom_collection method.""" async def test_remove_recipe_from_custom_collection( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for remove_recipe_from_custom_collection.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX/recipes/r907015", payload=COOKIDOO_TEST_RESPONSE_REMOVE_RECIPE_FROM_CUSTOM_COLLECTION, status=HTTPStatus.OK, ) data = await cookidoo.remove_recipe_from_custom_collection( "01JC1SRPRSW0SHE0AK8GCASABX", "r907015" ) assert data assert data.id == "01JC1SRPRSW0SHE0AK8GCASABX" assert len(data.chapters[0].recipes) == 0 @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX/recipes/r907015", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.remove_recipe_from_custom_collection( "01JC1SRPRSW0SHE0AK8GCASABX", "r907015" ) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX/recipes/r907015", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.remove_recipe_from_custom_collection( "01JC1SRPRSW0SHE0AK8GCASABX", "r907015" ) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/organize/de-CH/api/custom-list/01JC1SRPRSW0SHE0AK8GCASABX/recipes/r907015", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.remove_recipe_from_custom_collection( "01JC1SRPRSW0SHE0AK8GCASABX", "r907015" ) class TestGetCalendarWeek: """Tests for get_calendar_week method.""" async def test_get_calendar_week( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for get_calendar_week.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-week/2025-03-03", payload=COOKIDOO_TEST_RESPONSE_CALENDAR_WEEK, status=HTTPStatus.OK, ) data = await cookidoo.get_recipes_in_calendar_week( datetime.fromisoformat("2025-03-03").date() ) assert data assert isinstance(data, list) assert len(data) == 2 assert data[0].id == "2025-03-04" assert data[0].recipes[0].id == "r214846" assert data[1].id == "2025-03-05" assert data[1].recipes[0].id == "r338888" @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-week/2025-03-03", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.get_recipes_in_calendar_week( datetime.fromisoformat("2025-03-03").date() ) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-week/2025-03-03", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.get_recipes_in_calendar_week( datetime.fromisoformat("2025-03-03").date() ) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.get( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-week/2025-03-03", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.get_recipes_in_calendar_week( datetime.fromisoformat("2025-03-03").date() ) class TestAddRecipesToCalendar: """Tests for add_recipes_to_calendar method.""" async def test_add_recipes_to_custom_collection( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for add_recipes_to_calendar.""" mocked.put( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-day", payload=COOKIDOO_TEST_RESPONSE_ADD_RECIPES_TO_CALENDAR, status=HTTPStatus.OK, ) data = await cookidoo.add_recipes_to_calendar( datetime.fromisoformat("2025-03-04").date(), ["r214846"] ) assert data assert data.id == "2025-03-04" assert data.recipes[0].id == "r214846" @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.put( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-day", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.add_recipes_to_calendar( datetime.fromisoformat("2025-03-04").date(), ["r214846"] ) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.put( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-day", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.add_recipes_to_calendar( datetime.fromisoformat("2025-03-04").date(), ["r214846"] ) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.put( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-day", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.add_recipes_to_calendar( datetime.fromisoformat("2025-03-04").date(), ["r214846"] ) class TestRemoveRecipeFromCalendar: """Tests for remove_recipe_from_calendar method.""" async def test_remove_recipe_from_calendar( self, mocked: aioresponses, cookidoo: Cookidoo ) -> None: """Test for remove_recipe_from_calendar.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-day/2025-03-04/recipes/r214846", payload=COOKIDOO_TEST_RESPONSE_REMOVE_RECIPE_FROM_CALENDAR, status=HTTPStatus.OK, ) data = await cookidoo.remove_recipe_from_calendar( datetime.fromisoformat("2025-03-04").date(), "r214846" ) assert data assert data.id == "2025-03-04" assert data.recipes[0].id == "r214846" @pytest.mark.parametrize( "exception", [ TimeoutError, ClientError, ], ) async def test_request_exception( self, mocked: aioresponses, cookidoo: Cookidoo, exception: Exception ) -> None: """Test request exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-day/2025-03-04/recipes/r214846", exception=exception, ) with pytest.raises(CookidooRequestException): await cookidoo.remove_recipe_from_calendar( datetime.fromisoformat("2025-03-04").date(), "r214846" ) async def test_unauthorized(self, mocked: aioresponses, cookidoo: Cookidoo) -> None: """Test unauthorized exception.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-day/2025-03-04/recipes/r214846", status=HTTPStatus.UNAUTHORIZED, payload={"error_description": ""}, ) with pytest.raises(CookidooAuthException): await cookidoo.remove_recipe_from_calendar( datetime.fromisoformat("2025-03-04").date(), "r214846" ) @pytest.mark.parametrize( ("status", "exception"), [ (HTTPStatus.OK, CookidooParseException), (HTTPStatus.UNAUTHORIZED, CookidooAuthException), ], ) async def test_parse_exception( self, mocked: aioresponses, cookidoo: Cookidoo, status: HTTPStatus, exception: type[CookidooException], ) -> None: """Test parse exceptions.""" mocked.delete( "https://ch.tmmobile.vorwerk-digital.com/planning/de-CH/api/my-day/2025-03-04/recipes/r214846", status=status, body="not json", content_type="application/json", ) with pytest.raises(exception): await cookidoo.remove_recipe_from_calendar( datetime.fromisoformat("2025-03-04").date(), "r214846" ) miaucl-cookidoo-api-5292e37/tests/test_helpers.py000066400000000000000000000026261476166105100220530ustar00rootroot00000000000000"""Unit tests for cookidoo-api.""" from typing import cast from dotenv import load_dotenv import pytest from cookidoo_api.helpers import ( cookidoo_recipe_details_from_json, get_country_options, get_language_options, get_localization_options, ) from cookidoo_api.raw_types import RecipeDetailsJSON from tests.responses import COOKIDOO_TEST_RESPONSE_GET_RECIPE_DETAILS load_dotenv() class TestLocalization: """Tests for localization functions.""" async def test_get_localization_options(self) -> None: """Test get localization options.""" assert len(await get_localization_options()) == 373 assert len(await get_localization_options(country="ch")) == 4 assert len(await get_localization_options(language="en")) == 38 async def test_get_country_options(self) -> None: """Test get country options.""" assert len(await get_country_options()) == 54 async def test_get_language_options(self) -> None: """Test get language options.""" assert len(await get_language_options()) == 31 async def test_cookidoo_recipe_details_from_json_exception(self) -> None: """Test get recipe details from json exception.""" JSON = cast(RecipeDetailsJSON, COOKIDOO_TEST_RESPONSE_GET_RECIPE_DETAILS.copy()) JSON["times"] = [] with pytest.raises(StopIteration): cookidoo_recipe_details_from_json(JSON)