diff --git a/.env.example b/.env.example index b11cd4e..11c8d37 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,4 @@ +SERVICE="" # 1und1, sim24 USERNAME="" PASSWORD="" CHECK_INTERVAL=300 \ No newline at end of file diff --git a/README.md b/README.md index 841b647..cffd5c7 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# SIM24 Auto Extender +# Unlimited OnDemand Auto Extender -Dieses Tool automatisiert das Nachbuchen von Datenvolumen bei SIM24-Unlimited-Tarifen. Bei diesen Tarifen muss nach Verbrauch der ersten 50GB das Datenvolumen manuell in 2GB-Schritten nachgebucht werden. Dieser Prozess wird durch dieses Script vollautomatisch erledigt. +Dieses Tool automatisiert das Nachbuchen von Datenvolumen bei SIM24- und 1&1-Unlimited-Demand-Tarifen. Bei diesen Tarifen muss nach Verbrauch der ersten 50GB das Datenvolumen manuell in Schritten nachgebucht werden. Dieser Prozess wird durch dieses Script vollautomatisch erledigt. ## Features -- Automatische Anmeldung im SIM24-Portal +- Automatische Anmeldung im SIM24- oder 1&1-Portal - Kontinuierliche Überwachung des Datenvolumens - Automatisches Nachbuchen bei Bedarf - Ausführliche Logging-Funktionen @@ -13,54 +13,56 @@ Dieses Tool automatisiert das Nachbuchen von Datenvolumen bei SIM24-Unlimited-Ta ## Voraussetzungen - Docker auf dem System installiert -- SIM24 Account-Zugangsdaten -- Ein aktiver SIM24-Unlimited-Tarif +- SIM24 oder 1&1 Account-Zugangsdaten +- Ein aktiver Unlimited-Demand-Tarif bei einem der unterstützten Anbieter ## Installation & Einrichtung 1. Image herunterladen: ```bash -docker pull ghcr.io/danielwte/sim24-auto-extender:latest +docker pull ghcr.io/danielwte/unlimited-ondemand-auto-extender:latest ``` 2. Container starten: ```bash docker run -d \ - -e USERNAME="sim24-username" \ - -e PASSWORD="sim24-password" \ + -e USERNAME="service-username" \ + -e PASSWORD="service-password" \ + -e SERVICE="service" \ -e CHECK_INTERVAL=300 \ - --name sim24-auto-extender \ - ghcr.io/danielwte/sim24-auto-extender:latest + --name unlimited-ondemand-auto-extender \ + ghcr.io/danielwte/unlimited-ondemand-auto-extender:latest ``` ### Umgebungsvariablen -- `USERNAME`: Der SIM24 Benutzername -- `PASSWORD`: Das SIM24 Passwort +- `USERNAME`: Der Benutzername für das entsprechende Portal +- `PASSWORD`: Das Passwort für das entsprechende Portal - `CHECK_INTERVAL`: Prüfintervall in Sekunden (Standard: 300) +- `SERVICE`: Der zu überwachende Service (Standard: sim24, Optionen: sim24, 1und1) ## Logs einsehen Die Logs können wie folgt eingesehen werden: ```bash -docker logs sim24-auto-extender +docker logs unlimited-ondemand-auto-extender ``` ## Container-Verwaltung Container neustarten: ```bash -docker restart sim24-auto-extender +docker restart unlimited-ondemand-auto-extender ``` Container stoppen: ```bash -docker stop sim24-auto-extender +docker stop unlimited-ondemand-auto-extender ``` Container entfernen: ```bash -docker rm sim24-auto-extender +docker rm unlimited-ondemand-auto-extender ``` ## Automatischer Start nach Systemneustart @@ -69,19 +71,20 @@ Für einen automatischen Start nach einem Systemneustart: ```bash docker run -d \ --restart unless-stopped \ - -e USERNAME="sim24-username" \ - -e PASSWORD="sim24-password" \ + -e USERNAME="service-username" \ + -e PASSWORD="service-password" \ + -e SERVICE="service" \ -e CHECK_INTERVAL=300 \ - --name sim24-auto-extender \ - ghcr.io/danielwte/sim24-auto-extender:latest + --name unlimited-ondemand-auto-extender \ + ghcr.io/danielwte/unlimited-ondemand-auto-extender:latest ``` ## Sicherheit - Die Zugangsdaten werden nur innerhalb des Containers verwendet - Es werden keine Daten persistent gespeichert -- Die Kommunikation erfolgt direkt mit dem SIM24-Portal +- Die Kommunikation erfolgt direkt mit dem Portal des entsprechenden Anbieters ## Disclaimer -Dieses Tool ist ein inoffizielles Hilfsprogramm und steht in keiner Verbindung zu SIM24. Die Nutzung erfolgt auf eigene Verantwortung. \ No newline at end of file +Dieses Tool ist ein inoffizielles Hilfsprogramm und steht in keiner Verbindung zu SIM24 oder 1&1. Die Nutzung erfolgt auf eigene Verantwortung. \ No newline at end of file diff --git a/checker/oneandone.py b/checker/oneandone.py new file mode 100644 index 0000000..87e316d --- /dev/null +++ b/checker/oneandone.py @@ -0,0 +1,63 @@ +import time +from playwright.sync_api import sync_playwright +import logging + +def check_1und1(username, password): + logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s") + + try: + with sync_playwright() as p: + browser = p.chromium.launch(headless=False) + page = browser.new_page() + + logging.info("Öffne Login-Seite...") + page.goto('https://account.1und1.de/') + + logging.info("Führe Login durch...") + page.fill('#login-form-user', username) + page.fill('#login-form-password', password) + + page.click('#login-button') + logging.info("Login erfolgreich") + + logging.info("Cookies ablehnen...") + try: + page.click('#consent_wall_optout') + except: + logging.info("Cookie-Banner nicht vorhanden oder bereits geschlossen.") + + logging.info("Weiterleitung zu Verbrauchsübersicht...") + page.goto('https://control-center.1und1.de/usages.html') + + time.sleep(3) + + page.wait_for_selector('div[data-testid="usage-volume-used"] strong') + used_data = page.locator('div[data-testid="usage-volume-used"] strong').nth(-1).text_content() + if used_data: + logging.info(f"Verbrauchte Daten: {used_data}") + else: + logging.warning("Verbrauchsdaten nicht gefunden.") + + button = page.locator('button:has-text("+1 GB")') + if button: + is_disabled = button.get_attribute('disabled') is not None + if is_disabled: + logging.info("Button gefunden, aber er ist deaktiviert.") + else: + logging.info("Button gefunden und aktiv. Versuche zu klicken...") + button.click() + logging.info("Button erfolgreich geklickt.") + time.sleep(3) + confirm_button = page.locator('button:has-text("Ok")') + if confirm_button: + confirm_button.click() + logging.info("Bestätigungsdialog erfolgreich geschlossen.") + + else: + logging.warning("Button '+1 GB' nicht gefunden.") + + logging.info("Schließe Browser.") + browser.close() + + except Exception as e: + logging.error(f"Fehler bei der Ausführung: {str(e)}") \ No newline at end of file diff --git a/checker/sim24.py b/checker/sim24.py new file mode 100644 index 0000000..17e1797 --- /dev/null +++ b/checker/sim24.py @@ -0,0 +1,60 @@ +from playwright.sync_api import sync_playwright +import logging + +def check_sim24(username, password): + try: + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page() + + logging.info("Öffne Login-Seite...") + page.goto('https://service.sim24.de/mytariff/invoice/showGprsDataUsage') + + logging.info("Führe Login durch...") + page.fill('input[name="UserLoginType[alias]"]', username) + page.fill('input[name="UserLoginType[password]"]', password) + + page.click('a.c-button.submitOnEnter[title="Login"]') + + logging.info("Deny Cookies...") + consent_button = page.query_selector('#consent_wall_optout') + if consent_button and consent_button.is_visible(): + consent_button.click() + else: + logging.info("Consent button not visible, skipping...") + + logging.info("Suche nach Button...") + try: + button = page.wait_for_selector('#ButtonBuchen-ChangeServiceType-showGprsDataUsage-0V5I3', timeout=10000) + stats = page.wait_for_selector('.dataUsageBar-info-numbers', timeout=10000) + + if stats: + used_data = stats.query_selector('.font-weight-bold').inner_text() + total_data = stats.query_selector('.l-txt-small').inner_text().replace('von', '').strip() + logging.info(f"Verbrauchte Daten: {used_data} von {total_data}") + + if button: + is_disabled = button.get_attribute('disabled') is not None + + if is_disabled: + logging.info("Button gefunden, aber deaktiviert") + else: + logging.info("Button gefunden und aktiv - Klicke...") + button.click() + logging.info("Button erfolgreich geklickt") + + page.click('#ButtonAktivieren-ChangeServiceType-getChangeServiceInfo-1V5I3') + + logging.info("Prozess erfolgreich beendet") + return + else: + logging.warning("Button nicht gefunden") + + except Exception as e: + logging.warning(f"Button nicht gefunden oder nicht klickbar: {str(e)}") + + logging.info("Schließe Browser") + browser.close() + + except Exception as e: + logging.error(f"Fehler bei der Ausführung: {str(e)}") \ No newline at end of file diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index a610a24..a57e057 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -4,11 +4,13 @@ set -e create_env_file() { if [ ! -f .env ]; then echo "Creating .env file from environment variables..." + : ${SERVICE:=""} : ${USERNAME:=""} : ${PASSWORD:=""} : ${CHECK_INTERVAL:=300} - echo "USERNAME=\"$USERNAME\"" > .env + echo "SERVICE=\"$SERVICE\"" > .env + echo "USERNAME=\"$USERNAME\"" >> .env echo "PASSWORD=\"$PASSWORD\"" >> .env echo "CHECK_INTERVAL=$CHECK_INTERVAL" >> .env fi diff --git a/main.py b/main.py index 5339ce7..4dc7013 100644 --- a/main.py +++ b/main.py @@ -4,79 +4,35 @@ import logging from dotenv import load_dotenv import os +from checker.sim24 import check_sim24 +from checker.oneandone import check_1und1 load_dotenv() +SERVICE = os.getenv("SERVICE") USERNAME = os.getenv("USERNAME") PASSWORD = os.getenv("PASSWORD") CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL")) logging.basicConfig( level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', + format='%(asctime)s - %(levelname)s - [%(prefix)s] %(message)s', handlers=[ logging.FileHandler('automation.log'), logging.StreamHandler() ] ) -def check_sim24(): - try: - with sync_playwright() as p: - browser = p.chromium.launch(headless=True) - page = browser.new_page() +class PrefixFilter(logging.Filter): + def __init__(self, prefix=""): + self.prefix = prefix + super().__init__() - logging.info("Öffne Login-Seite...") - page.goto('https://service.sim24.de/mytariff/invoice/showGprsDataUsage') - - logging.info("Führe Login durch...") - page.fill('input[name="UserLoginType[alias]"]', USERNAME) - page.fill('input[name="UserLoginType[password]"]', PASSWORD) - - page.click('a.c-button.submitOnEnter[title="Login"]') - - logging.info("Deny Cookies...") - consent_button = page.query_selector('#consent_wall_optout') - if consent_button and consent_button.is_visible(): - consent_button.click() - else: - logging.info("Consent button not visible, skipping...") - - logging.info("Suche nach Button...") - try: - button = page.wait_for_selector('#ButtonBuchen-ChangeServiceType-showGprsDataUsage-0V5I3', timeout=10000) - stats = page.wait_for_selector('.dataUsageBar-info-numbers', timeout=10000) - - if stats: - used_data = stats.query_selector('.font-weight-bold').inner_text() - total_data = stats.query_selector('.l-txt-small').inner_text().replace('von', '').strip() - logging.info(f"Verbrauchte Daten: {used_data} von {total_data}") - - if button: - is_disabled = button.get_attribute('disabled') is not None - - if is_disabled: - logging.info("Button gefunden, aber deaktiviert") - else: - logging.info("Button gefunden und aktiv - Klicke...") - button.click() - logging.info("Button erfolgreich geklickt") - - page.click('#ButtonAktivieren-ChangeServiceType-getChangeServiceInfo-1V5I3') - - logging.info("Prozess erfolgreich beendet") - return - else: - logging.warning("Button nicht gefunden") - - except Exception as e: - logging.warning(f"Button nicht gefunden oder nicht klickbar: {str(e)}") - - logging.info("Schließe Browser") - browser.close() - - except Exception as e: - logging.error(f"Fehler bei der Ausführung: {str(e)}") + def filter(self, record): + record.prefix = self.prefix + return True + +logging.getLogger().addFilter(PrefixFilter(SERVICE)) def main(): logging.info("Starte Automatisierung...") @@ -86,7 +42,12 @@ def main(): logging.info(f"Starte neue Überprüfung um {current_time}") try: - check_sim24() + if SERVICE == "sim24": + check_sim24(USERNAME, PASSWORD) + elif SERVICE == "1und1": + check_1und1(USERNAME, PASSWORD) + else: + logging.error(f"Unbekannte Service-ID: {SERVICE}") except Exception as e: logging.error(f"Fehler im Hauptprozess: {str(e)}")