Reduce default login wait to 60 seconds
This commit is contained in:
parent
390ffe1d40
commit
5c6398eac1
5 changed files with 56 additions and 16 deletions
10
README.md
10
README.md
|
|
@ -30,6 +30,12 @@ Explizit fuer Deutschland (Sprache/Zeitzone):
|
||||||
python main.py configure --marketplace de --locale de-DE --timezone Europe/Berlin --currency EUR --download-dir "C:\Users\<USER>\Downloads\amazon_rechnungen"
|
python main.py configure --marketplace de --locale de-DE --timezone Europe/Berlin --currency EUR --download-dir "C:\Users\<USER>\Downloads\amazon_rechnungen"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Falls Amazon trotzdem Englisch zeigt, Sprache explizit per URL erzwingen:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python main.py configure --marketplace de --amazon-language de_DE --locale de-DE --timezone Europe/Berlin --currency EUR
|
||||||
|
```
|
||||||
|
|
||||||
Dann oeffnet sich ein Browser. Dort bei Amazon anmelden und auf Enter im Terminal druecken.
|
Dann oeffnet sich ein Browser. Dort bei Amazon anmelden und auf Enter im Terminal druecken.
|
||||||
Die Session wird lokal gespeichert in:
|
Die Session wird lokal gespeichert in:
|
||||||
|
|
||||||
|
|
@ -76,7 +82,7 @@ Dann im Browser auf deinem PC oeffnen:
|
||||||
http://<SERVER-IP>:6080/vnc.html
|
http://<SERVER-IP>:6080/vnc.html
|
||||||
```
|
```
|
||||||
|
|
||||||
Im noVNC-Fenster bei Amazon einloggen. Die Session wird nach `LOGIN_WAIT_SECONDS` (Standard: 300s) automatisch gespeichert.
|
Im noVNC-Fenster bei Amazon einloggen. Die Session wird nach `LOGIN_WAIT_SECONDS` (Standard: 60s) automatisch gespeichert.
|
||||||
Anschließend kannst du den noVNC-Container beenden (`Ctrl+C` im Terminal).
|
Anschließend kannst du den noVNC-Container beenden (`Ctrl+C` im Terminal).
|
||||||
|
|
||||||
Alternative per Hilfsskript:
|
Alternative per Hilfsskript:
|
||||||
|
|
@ -119,7 +125,7 @@ Optionen:
|
||||||
- `--headless true|false`: Browser sichtbar oder unsichtbar
|
- `--headless true|false`: Browser sichtbar oder unsichtbar
|
||||||
- `--debug`: zeigt, wie viele Detailseiten und Rechnungslinks gefunden werden
|
- `--debug`: zeigt, wie viele Detailseiten und Rechnungslinks gefunden werden
|
||||||
- `--debug-json [pfad]`: schreibt Laufdetails als JSON (ohne Pfad: Standarddatei)
|
- `--debug-json [pfad]`: schreibt Laufdetails als JSON (ohne Pfad: Standarddatei)
|
||||||
- `configure --locale de-DE --timezone Europe/Berlin`: erzwingt deutsche Sprache und Berliner Zeitzone im Browser-Kontext
|
- `configure --locale de-DE --timezone Europe/Berlin --amazon-language de_DE`: erzwingt deutsche Sprache (inkl. `language=de_DE`) und Berliner Zeitzone
|
||||||
|
|
||||||
## Hinweise
|
## Hinweise
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,11 @@
|
||||||
environment:
|
environment:
|
||||||
- MARKETPLACE=de
|
- MARKETPLACE=de
|
||||||
- DOWNLOAD_DIR=/downloads
|
- DOWNLOAD_DIR=/downloads
|
||||||
- LOGIN_WAIT_SECONDS=300
|
- LOGIN_WAIT_SECONDS=60
|
||||||
- NOVNC_PORT=6080
|
- NOVNC_PORT=6080
|
||||||
ports:
|
ports:
|
||||||
- "6080:6080"
|
- "6080:6080"
|
||||||
volumes:
|
volumes:
|
||||||
- ./state:/root/.amazon_invoice_downloader
|
- ./state:/root/.amazon_invoice_downloader
|
||||||
- ./downloads:/downloads
|
- ./downloads:/downloads
|
||||||
|
|
||||||
|
|
|
||||||
49
main.py
49
main.py
|
|
@ -6,7 +6,7 @@ from dataclasses import dataclass
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import parse_qsl, urlencode, urljoin, urlparse, urlunparse
|
||||||
|
|
||||||
import dateparser
|
import dateparser
|
||||||
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
|
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
|
||||||
|
|
@ -121,8 +121,16 @@ def years_for_range(start_date: date, end_date: date) -> list[int]:
|
||||||
return list(range(end_date.year, start_date.year - 1, -1))
|
return list(range(end_date.year, start_date.year - 1, -1))
|
||||||
|
|
||||||
|
|
||||||
def build_orders_url(marketplace: str, year: int) -> str:
|
def with_amazon_language(url: str, amazon_language: str) -> str:
|
||||||
return f"https://www.amazon.{marketplace}/your-orders/orders?timeFilter=year-{year}"
|
parsed = urlparse(url)
|
||||||
|
query = dict(parse_qsl(parsed.query, keep_blank_values=True))
|
||||||
|
query["language"] = amazon_language
|
||||||
|
return urlunparse(parsed._replace(query=urlencode(query)))
|
||||||
|
|
||||||
|
|
||||||
|
def build_orders_url(marketplace: str, year: int, amazon_language: str) -> str:
|
||||||
|
base = f"https://www.amazon.{marketplace}/your-orders/orders?timeFilter=year-{year}"
|
||||||
|
return with_amazon_language(base, amazon_language)
|
||||||
|
|
||||||
|
|
||||||
def text_contains_invoice_hint(text: str) -> bool:
|
def text_contains_invoice_hint(text: str) -> bool:
|
||||||
|
|
@ -214,10 +222,20 @@ def extract_orders_from_overview(page, base_url: str, debug: bool = False) -> li
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def extract_invoice_candidates_from_detail(context, detail_url: str, base_url: str, debug: bool = False) -> Optional[OrderInvoice]:
|
def extract_invoice_candidates_from_detail(
|
||||||
|
context,
|
||||||
|
detail_url: str,
|
||||||
|
base_url: str,
|
||||||
|
amazon_language: str,
|
||||||
|
debug: bool = False,
|
||||||
|
) -> Optional[OrderInvoice]:
|
||||||
detail_page = context.new_page()
|
detail_page = context.new_page()
|
||||||
try:
|
try:
|
||||||
detail_page.goto(detail_url, wait_until="domcontentloaded", timeout=15000)
|
detail_page.goto(
|
||||||
|
with_amazon_language(detail_url, amazon_language),
|
||||||
|
wait_until="domcontentloaded",
|
||||||
|
timeout=15000,
|
||||||
|
)
|
||||||
detail_page.wait_for_timeout(1200)
|
detail_page.wait_for_timeout(1200)
|
||||||
body_text = detail_page.inner_text("body", timeout=4000)
|
body_text = detail_page.inner_text("body", timeout=4000)
|
||||||
order_date = parse_order_date_from_text(body_text)
|
order_date = parse_order_date_from_text(body_text)
|
||||||
|
|
@ -284,6 +302,7 @@ def configure(args) -> None:
|
||||||
"locale": args.locale,
|
"locale": args.locale,
|
||||||
"timezone": args.timezone,
|
"timezone": args.timezone,
|
||||||
"currency": args.currency,
|
"currency": args.currency,
|
||||||
|
"amazon_language": args.amazon_language,
|
||||||
}
|
}
|
||||||
save_config(config)
|
save_config(config)
|
||||||
ensure_app_dir()
|
ensure_app_dir()
|
||||||
|
|
@ -292,7 +311,13 @@ def configure(args) -> None:
|
||||||
browser = p.chromium.launch(headless=False)
|
browser = p.chromium.launch(headless=False)
|
||||||
context = browser.new_context(**build_context_options(config))
|
context = browser.new_context(**build_context_options(config))
|
||||||
page = context.new_page()
|
page = context.new_page()
|
||||||
page.goto(f"https://www.amazon.{args.marketplace}/your-orders/orders", wait_until="domcontentloaded")
|
page.goto(
|
||||||
|
with_amazon_language(
|
||||||
|
f"https://www.amazon.{args.marketplace}/your-orders/orders",
|
||||||
|
args.amazon_language,
|
||||||
|
),
|
||||||
|
wait_until="domcontentloaded",
|
||||||
|
)
|
||||||
print("Bitte im Browser bei Amazon einloggen.")
|
print("Bitte im Browser bei Amazon einloggen.")
|
||||||
if args.login_wait_seconds > 0:
|
if args.login_wait_seconds > 0:
|
||||||
print(
|
print(
|
||||||
|
|
@ -321,6 +346,7 @@ def download(args) -> None:
|
||||||
raise SystemExit("'from' muss kleiner/gleich 'to' sein.")
|
raise SystemExit("'from' muss kleiner/gleich 'to' sein.")
|
||||||
|
|
||||||
marketplace = config["marketplace"]
|
marketplace = config["marketplace"]
|
||||||
|
amazon_language = config.get("amazon_language", "de_DE")
|
||||||
download_dir = Path(args.output or config["download_dir"]).expanduser().resolve()
|
download_dir = Path(args.output or config["download_dir"]).expanduser().resolve()
|
||||||
download_dir.mkdir(parents=True, exist_ok=True)
|
download_dir.mkdir(parents=True, exist_ok=True)
|
||||||
debug_json_target = args.debug_json or (str(DEFAULT_DEBUG_JSON_PATH) if args.debug else None)
|
debug_json_target = args.debug_json or (str(DEFAULT_DEBUG_JSON_PATH) if args.debug else None)
|
||||||
|
|
@ -348,7 +374,7 @@ def download(args) -> None:
|
||||||
}
|
}
|
||||||
|
|
||||||
for year in years:
|
for year in years:
|
||||||
filtered_url = build_orders_url(marketplace, year)
|
filtered_url = build_orders_url(marketplace, year, amazon_language)
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print(f"[debug] Wechsle auf Jahresfilter {year}: {filtered_url}")
|
print(f"[debug] Wechsle auf Jahresfilter {year}: {filtered_url}")
|
||||||
page.goto(filtered_url, wait_until="domcontentloaded", timeout=15000)
|
page.goto(filtered_url, wait_until="domcontentloaded", timeout=15000)
|
||||||
|
|
@ -402,7 +428,11 @@ def download(args) -> None:
|
||||||
if not next_page_url:
|
if not next_page_url:
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
page.goto(next_page_url, wait_until="domcontentloaded", timeout=15000)
|
page.goto(
|
||||||
|
with_amazon_language(next_page_url, amazon_language),
|
||||||
|
wait_until="domcontentloaded",
|
||||||
|
timeout=15000,
|
||||||
|
)
|
||||||
except PlaywrightTimeoutError:
|
except PlaywrightTimeoutError:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -485,10 +515,11 @@ def build_parser() -> argparse.ArgumentParser:
|
||||||
p_config.add_argument("--locale", default="de-DE", help="Browser-Locale, z. B. de-DE")
|
p_config.add_argument("--locale", default="de-DE", help="Browser-Locale, z. B. de-DE")
|
||||||
p_config.add_argument("--timezone", default="Europe/Berlin", help="Zeitzone, z. B. Europe/Berlin")
|
p_config.add_argument("--timezone", default="Europe/Berlin", help="Zeitzone, z. B. Europe/Berlin")
|
||||||
p_config.add_argument("--currency", default="EUR", help="Waehrungshinweis fuer Konfiguration")
|
p_config.add_argument("--currency", default="EUR", help="Waehrungshinweis fuer Konfiguration")
|
||||||
|
p_config.add_argument("--amazon-language", default="de_DE", help="Amazon URL-Sprache, z. B. de_DE")
|
||||||
p_config.add_argument(
|
p_config.add_argument(
|
||||||
"--login-wait-seconds",
|
"--login-wait-seconds",
|
||||||
type=int,
|
type=int,
|
||||||
default=0,
|
default=60,
|
||||||
help="Optional: wartet X Sekunden vor Session-Speicherung (fuer noVNC/Serverbetrieb).",
|
help="Optional: wartet X Sekunden vor Session-Speicherung (fuer noVNC/Serverbetrieb).",
|
||||||
)
|
)
|
||||||
p_config.set_defaults(func=configure)
|
p_config.set_defaults(func=configure)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
DISPLAY_NUM=${DISPLAY_NUM:-:99}
|
DISPLAY_NUM=${DISPLAY_NUM:-:99}
|
||||||
|
|
@ -6,7 +6,7 @@ VNC_PORT=${VNC_PORT:-5900}
|
||||||
NOVNC_PORT=${NOVNC_PORT:-6080}
|
NOVNC_PORT=${NOVNC_PORT:-6080}
|
||||||
MARKETPLACE=${MARKETPLACE:-de}
|
MARKETPLACE=${MARKETPLACE:-de}
|
||||||
DOWNLOAD_DIR=${DOWNLOAD_DIR:-/downloads}
|
DOWNLOAD_DIR=${DOWNLOAD_DIR:-/downloads}
|
||||||
LOGIN_WAIT_SECONDS=${LOGIN_WAIT_SECONDS:-300}
|
LOGIN_WAIT_SECONDS=${LOGIN_WAIT_SECONDS:-60}
|
||||||
|
|
||||||
export DISPLAY="$DISPLAY_NUM"
|
export DISPLAY="$DISPLAY_NUM"
|
||||||
|
|
||||||
|
|
@ -19,3 +19,4 @@ echo "noVNC bereit unter: http://<SERVER-IP>:$NOVNC_PORT/vnc.html"
|
||||||
echo "Melde dich bei Amazon an. Session wird nach $LOGIN_WAIT_SECONDS Sekunden gespeichert."
|
echo "Melde dich bei Amazon an. Session wird nach $LOGIN_WAIT_SECONDS Sekunden gespeichert."
|
||||||
|
|
||||||
python /app/main.py configure --marketplace "$MARKETPLACE" --download-dir "$DOWNLOAD_DIR" --login-wait-seconds "$LOGIN_WAIT_SECONDS"
|
python /app/main.py configure --marketplace "$MARKETPLACE" --download-dir "$DOWNLOAD_DIR" --login-wait-seconds "$LOGIN_WAIT_SECONDS"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
MARKETPLACE="${MARKETPLACE:-de}"
|
MARKETPLACE="${MARKETPLACE:-de}"
|
||||||
LOGIN_WAIT_SECONDS="${LOGIN_WAIT_SECONDS:-300}"
|
LOGIN_WAIT_SECONDS="${LOGIN_WAIT_SECONDS:-60}"
|
||||||
NOVNC_PORT="${NOVNC_PORT:-6080}"
|
NOVNC_PORT="${NOVNC_PORT:-6080}"
|
||||||
DOWNLOAD_DIR="${DOWNLOAD_DIR:-/downloads}"
|
DOWNLOAD_DIR="${DOWNLOAD_DIR:-/downloads}"
|
||||||
|
|
||||||
|
|
@ -20,3 +20,4 @@ echo "Starte noVNC-Configure auf Port ${NOVNC_PORT} ..."
|
||||||
echo "Oeffne im Browser: http://<SERVER-IP>:${NOVNC_PORT}/vnc.html"
|
echo "Oeffne im Browser: http://<SERVER-IP>:${NOVNC_PORT}/vnc.html"
|
||||||
|
|
||||||
docker compose --profile configure-novnc up --build amazon-invoice-configure-novnc
|
docker compose --profile configure-novnc up --build amazon-invoice-configure-novnc
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue