Dieser Commit ist enthalten in:
Claude Project Manager
2025-07-03 21:11:05 +02:00
Commit 08ed938105
239 geänderte Dateien mit 21554 neuen und 0 gelöschten Zeilen

188
CLAUDE_PROJECT_README.md Normale Datei
Datei anzeigen

@ -0,0 +1,188 @@
# test-main
*This README was automatically generated by Claude Project Manager*
## Project Overview
- **Path**: `C:/Users/hendr/Desktop/IntelSight/test-main`
- **Files**: 125 files
- **Size**: 884.4 KB
- **Last Modified**: 2025-07-01 22:08
## Technology Stack
### Languages
- Python
## Project Structure
```
CLAUDE_PROJECT_README.md
main.py
README.md
requirements.txt
browser/
│ ├── fingerprint_protection.py
│ ├── playwright_extensions.py
│ ├── playwright_manager.py
│ ├── stealth_config.py
│ └── __init__.py
config/
│ ├── app_version.json
│ ├── browser_config.json
│ ├── email_config.json
│ ├── facebook_config.json
│ ├── instagram_config.json
│ ├── license.json
│ ├── license_config.json
│ ├── proxy_config.json
│ └── stealth_config.json
controllers/
│ ├── account_controller.py
│ ├── main_controller.py
│ ├── settings_controller.py
│ └── platform_controllers/
│ ├── base_controller.py
│ ├── instagram_controller.py
│ └── tiktok_controller.py
database/
│ ├── accounts.db
│ ├── account_repository.py
│ ├── db_manager.py
│ ├── instagram_accounts.db
│ ├── schema.sql
│ └── __init__.py
licensing/
│ ├── hardware_fingerprint.py
│ ├── license_manager.py
│ ├── license_validator.py
│ └── __init__.py
localization/
│ ├── language_manager.py
│ ├── __init__.py
│ └── languages/
│ ├── de.json
│ ├── en.json
│ ├── es.json
│ ├── fr.json
│ └── ja.json
logs/
│ ├── main.log
│ └── screenshots
resources/
│ ├── icons/
│ │ ├── de.svg
│ │ ├── en.svg
│ │ ├── es.svg
│ │ ├── facebook.svg
│ │ ├── fr.svg
│ │ ├── instagram.svg
│ │ ├── ja.svg
│ │ ├── moon.svg
│ │ ├── sun.svg
│ │ └── tiktok.svg
│ └── themes/
│ ├── dark.qss
│ └── light.qss
social_networks/
│ ├── base_automation.py
│ ├── __init__.py
│ ├── facebook/
│ │ ├── facebook_automation.py
│ │ ├── facebook_login.py
│ │ ├── facebook_registration.py
│ │ ├── facebook_selectors.py
│ │ ├── facebook_ui_helper.py
│ │ ├── facebook_utils.py
│ │ ├── facebook_verification.py
│ │ ├── facebook_workflow.py
│ │ └── __init__.py
│ ├── instagram/
│ │ ├── instagram_automation.py
│ │ ├── instagram_login.py
│ │ ├── instagram_registration.py
│ │ ├── instagram_selectors.py
│ │ ├── instagram_ui_helper.py
│ │ ├── instagram_utils.py
│ │ ├── instagram_verification.py
│ │ ├── instagram_workflow.py
│ │ └── __init__.py
│ ├── tiktok/
│ │ ├── tiktok_automation.py
│ │ ├── tiktok_login.py
│ │ ├── tiktok_registration.py
│ │ ├── tiktok_selectors.py
│ │ ├── tiktok_ui_helper.py
│ │ ├── tiktok_utils.py
│ │ ├── tiktok_verification.py
│ │ ├── tiktok_workflow.py
│ │ └── __init__.py
│ └── twitter/
│ ├── twitter_automation.py
│ ├── twitter_login.py
│ ├── twitter_registration.py
│ ├── twitter_selectors.py
│ ├── twitter_ui_helper.py
│ ├── twitter_utils.py
│ ├── twitter_verification.py
│ ├── twitter_workflow.py
│ └── __init__.py
testcases/
│ └── imap_test.py
updates/
│ ├── downloader.py
│ ├── update_checker.py
│ ├── update_v1.1.0.zip
│ ├── version.py
│ └── __init__.py
utils/
│ ├── birthday_generator.py
│ ├── email_handler.py
│ ├── human_behavior.py
│ ├── logger.py
│ ├── password_generator.py
│ ├── proxy_rotator.py
│ ├── text_similarity.py
│ ├── theme_manager.py
│ ├── update_checker.py
│ └── username_generator.py
views/
├── about_dialog.py
├── main_window.py
├── platform_selector.py
├── tabs/
│ ├── accounts_tab.py
│ ├── generator_tab.py
│ └── settings_tab.py
└── widgets/
├── language_dropdown.py
└── platform_button.py
```
## Key Files
- `README.md`
- `requirements.txt`
## Claude Integration
This project is managed with Claude Project Manager. To work with this project:
1. Open Claude Project Manager
2. Click on this project's tile
3. Claude will open in the project directory
## Notes
*Add your project-specific notes here*
---
## Development Log
- README generated on 2025-07-01 20:43:39
- README updated on 2025-07-01 21:09:06
- README updated on 2025-07-01 21:59:23
- README updated on 2025-07-01 22:08:40
- README updated on 2025-07-01 22:08:50
- README updated on 2025-07-01 22:09:15

130
README.md Normale Datei
Datei anzeigen

@ -0,0 +1,130 @@
# Social Media Account Generator
Dieses Repository enthält eine Desktopanwendung zur automatisierten Erstellung und Verwaltung von SocialMediaAccounts. Die grafische Oberfläche basiert auf **PyQt5**, die BrowserAutomatisierung erfolgt mit **Playwright**. Der Code ist modular aufgebaut und kann leicht um weitere Plattformen erweitert werden.
## Installation
1. Python 3.8 oder neuer installieren.
2. Abhängigkeiten mit `pip install -r requirements.txt` einrichten.
## Anwendung starten
```bash
python main.py
```
Beim ersten Start werden benötigte Ordner wie `logs`, `config` und `resources` automatisch angelegt. Einstellungen können im Ordner `config` angepasst werden.
## Projektstruktur (Auszug)
```text
.
├── main.py
├── browser/
│ ├── playwright_manager.py
│ └── stealth_config.py
├── controllers/
│ ├── main_controller.py
│ ├── account_controller.py
│ ├── settings_controller.py
│ └── platform_controllers/
│ ├── base_controller.py
│ ├── instagram_controller.py
│ └── tiktok_controller.py
├── views/
│ ├── main_window.py
│ ├── platform_selector.py
│ ├── about_dialog.py
│ ├── widgets/
│ │ └── platform_button.py
│ └── tabs/
│ ├── generator_tab.py
│ ├── accounts_tab.py
│ └── settings_tab.py
├── social_networks/
│ ├── base_automation.py
│ ├── instagram/
│ │ └── ...
│ ├── tiktok/
│ │ └── ...
│ ├── facebook/
│ │ └── ...
│ └── twitter/
│ └── ...
├── localization/
│ ├── language_manager.py
│ └── languages/
│ ├── de.json
│ ├── en.json
│ ├── es.json
│ ├── fr.json
│ └── ja.json
├── utils/
│ ├── logger.py
│ ├── password_generator.py
│ ├── username_generator.py
│ ├── birthday_generator.py
│ ├── email_handler.py
│ ├── proxy_rotator.py
│ ├── human_behavior.py
│ ├── text_similarity.py
│ └── theme_manager.py
├── database/
│ ├── db_manager.py
│ └── ...
├── licensing/
│ ├── license_manager.py
│ ├── hardware_fingerprint.py
│ └── license_validator.py
├── updates/
│ ├── update_checker.py
│ ├── downloader.py
│ ├── version.py
│ └── ...
├── config/
│ ├── browser_config.json
│ ├── email_config.json
│ ├── proxy_config.json
│ ├── stealth_config.json
│ ├── license_config.json
│ ├── instagram_config.json
│ ├── facebook_config.json
│ ├── twitter_config.json
│ ├── tiktok_config.json
│ ├── theme.json
│ ├── app_version.json
│ └── update_config.json
├── resources/
│ ├── icons/
│ │ ├── instagram.svg
│ │ ├── facebook.svg
│ │ ├── twitter.svg
│ │ ├── tiktok.svg
│ │ └── vk.svg
│ └── themes/
│ ├── light.qss
│ └── dark.qss
├── testcases/
│ └── imap_test.py
├── requirements.txt
└── README.md
```
Weitere Ordner:
- `logs/` – Protokolldateien und Screenshots
- `resources/` – Icons und ThemeDateien
- `updates/` – heruntergeladene Updates
## Lokalisierung
Im Ordner `localization/languages` befinden sich Übersetzungsdateien für Deutsch, Englisch, Spanisch, Französisch und Japanisch. Die aktuelle Sprache kann zur Laufzeit gewechselt werden.
## Lizenz und Updates
Die Ordner `licensing` und `updates` enthalten die Logik zur Lizenzprüfung und zum UpdateManagement. Versionsinformationen werden in `updates/version.py` verwaltet.
## Tests
Im Ordner `testcases` liegt beispielhaft `imap_test.py`, mit dem die IMAPKonfiguration getestet werden kann.

0
browser/__init__.py Normale Datei
Datei anzeigen

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -0,0 +1,721 @@
# browser/fingerprint_protection.py
"""
Schutz vor Browser-Fingerprinting - Erweiterte Methoden zum Schutz vor verschiedenen Fingerprinting-Techniken
"""
import random
import logging
import json
from typing import Dict, List, Any, Optional, Tuple
logger = logging.getLogger("fingerprint_protection")
class FingerprintProtection:
"""
Bietet erweiterte Schutzmaßnahmen gegen verschiedene Browser-Fingerprinting-Techniken.
Kann mit dem PlaywrightManager integriert werden, um die Anonymität zu verbessern.
"""
def __init__(self, context=None, stealth_config=None):
"""
Initialisiert den Fingerprint-Schutz.
Args:
context: Der Browser-Kontext, auf den die Schutzmaßnahmen angewendet werden sollen
stealth_config: Konfigurationseinstellungen für das Stealth-Verhalten
"""
self.context = context
self.stealth_config = stealth_config or {}
self.scripts = []
self.noise_level = self.stealth_config.get("noise_level", 0.5) # 0.0-1.0
# Standardwerte für Fingerprinting-Schutz
self.defaults = {
"webgl_vendor": "Google Inc. (Intel)",
"webgl_renderer": "Intel Iris OpenGL Engine",
"canvas_noise": True,
"audio_noise": True,
"webgl_noise": True,
"hardware_concurrency": 8,
"device_memory": 8,
"timezone_id": "Europe/Berlin"
}
# Einstellungen mit benutzerdefinierten Werten überschreiben
for key, value in self.stealth_config.items():
if key in self.defaults:
self.defaults[key] = value
# Schutzmaßnahmen initialisieren
self._init_protections()
def set_context(self, context):
"""Setzt den Browser-Kontext nach der Initialisierung."""
self.context = context
def _init_protections(self):
"""Initialisiert alle Fingerprint-Schutzmaßnahmen."""
self._init_canvas_protection()
self._init_webgl_protection()
self._init_audio_protection()
self._init_navigator_protection()
self._init_misc_protections()
def _init_canvas_protection(self):
"""
Initialisiert den Schutz gegen Canvas-Fingerprinting.
Dies modifiziert das Canvas-Element, um leicht abweichende Werte zurückzugeben.
"""
script = """
() => {
// Originalmethoden speichern
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
const originalToBlob = HTMLCanvasElement.prototype.toBlob;
// Funktion zum Hinzufügen von Rauschen zu Bilddaten
const addNoise = (data, noise) => {
const noiseFactor = noise || 0.03; // Standardwert für das Rauschlevel
// Nur auf Canvas über Mindestgröße anwenden, um normale Canvas nicht zu stören
if (data.width > 16 && data.height > 16) {
const pixelCount = data.width * data.height * 4; // RGBA-Kanäle
// Präzise, aber wenig wahrnehmbare Änderungen
const minPixels = Math.max(10, Math.floor(pixelCount * 0.005)); // Mindestens 10 Pixel
const maxPixels = Math.min(100, Math.floor(pixelCount * 0.01)); // Höchstens 100 Pixel
// Zufällige Anzahl an Pixeln auswählen
const pixelsToModify = Math.floor(Math.random() * (maxPixels - minPixels)) + minPixels;
// Pixel modifizieren
for (let i = 0; i < pixelsToModify; i++) {
// Zufälligen Pixel auswählen
const offset = Math.floor(Math.random() * pixelCount / 4) * 4;
// Nur RGB modifizieren, Alpha-Kanal unverändert lassen
for (let j = 0; j < 3; j++) {
// Subtile Änderungen hinzufügen (±1-2)
const mod = Math.floor(Math.random() * 3) - 1;
data.data[offset + j] = Math.max(0, Math.min(255, data.data[offset + j] + mod));
}
}
}
return data;
};
// getImageData überschreiben
CanvasRenderingContext2D.prototype.getImageData = function() {
const imageData = originalGetImageData.apply(this, arguments);
return addNoise(imageData, NOISE_LEVEL);
};
// toDataURL überschreiben
HTMLCanvasElement.prototype.toDataURL = function() {
// Temporäre Modifikation für getImageData
const tempGetImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = originalGetImageData;
// toDataURL aufrufen
let dataURL = originalToDataURL.apply(this, arguments);
// Wenn das Canvas groß genug ist und nicht von kritischen Anwendungen verwendet wird
if (this.width > 16 && this.height > 16 && !this.hasAttribute('data-fingerprint-protect-ignore')) {
// Rauschen in binären Teil des DataURL einfügen
const parts = dataURL.split(',');
if (parts.length === 2) {
// Binäre Daten dekodieren
const binary = atob(parts[1]);
// Geringfügige Änderungen bei etwa 0,1% der Bytes
let modifiedBinary = '';
for (let i = 0; i < binary.length; i++) {
if (Math.random() < 0.001 * NOISE_LEVEL) {
// Byte leicht ändern
const byte = binary.charCodeAt(i);
const mod = Math.floor(Math.random() * 3) - 1;
modifiedBinary += String.fromCharCode(Math.max(0, Math.min(255, byte + mod)));
} else {
modifiedBinary += binary[i];
}
}
// Binäre Daten wieder kodieren
dataURL = parts[0] + ',' + btoa(modifiedBinary);
}
}
// getImageData wiederherstellen
CanvasRenderingContext2D.prototype.getImageData = tempGetImageData;
return dataURL;
};
// toBlob überschreiben
HTMLCanvasElement.prototype.toBlob = function(callback) {
// Original toBlob aufrufen
originalToBlob.apply(this, [function(blob) {
// Wenn das Canvas groß genug ist und nicht von kritischen Anwendungen verwendet wird
if (this.width > 16 && this.height > 16 && !this.hasAttribute('data-fingerprint-protect-ignore')) {
// Blob zu ArrayBuffer konvertieren
const reader = new FileReader();
reader.onload = function() {
const arrayBuffer = reader.result;
const array = new Uint8Array(arrayBuffer);
// Geringfügige Änderungen bei etwa 0,1% der Bytes
for (let i = 0; i < array.length; i++) {
if (Math.random() < 0.001 * NOISE_LEVEL) {
// Byte leicht ändern
const mod = Math.floor(Math.random() * 3) - 1;
array[i] = Math.max(0, Math.min(255, array[i] + mod));
}
}
// Neuen Blob erstellen
const modifiedBlob = new Blob([array], {type: blob.type});
callback(modifiedBlob);
};
reader.readAsArrayBuffer(blob);
} else {
// Unveränderten Blob zurückgeben
callback(blob);
}
}.bind(this)]);
};
}
""".replace("NOISE_LEVEL", str(self.noise_level))
self.scripts.append(script)
def _init_webgl_protection(self):
"""
Initialisiert den Schutz gegen WebGL-Fingerprinting.
Dies modifiziert WebGL-spezifische Werte, die für Fingerprinting verwendet werden.
"""
webgl_vendor = self.defaults["webgl_vendor"]
webgl_renderer = self.defaults["webgl_renderer"]
script = f"""
() => {{
// WebGL Vendor und Renderer spoofen
const getParameterProxies = [
WebGLRenderingContext.prototype,
WebGL2RenderingContext.prototype
];
getParameterProxies.forEach(contextPrototype => {{
if (!contextPrototype) return;
const originalGetParameter = contextPrototype.getParameter;
contextPrototype.getParameter = function(parameter) {{
// WebGL Vendor (VENDOR)
if (parameter === 0x1F00) {{
return "{webgl_vendor}";
}}
// WebGL Renderer (RENDERER)
if (parameter === 0x1F01) {{
return "{webgl_renderer}";
}}
// Unshaded Language Version (SHADING_LANGUAGE_VERSION)
if (parameter === 0x8B8C) {{
const originalValue = originalGetParameter.call(this, parameter);
// Subtile Änderungen an der Version
const versionMatch = originalValue.match(/^WebGL GLSL ES ([0-9]\\.[0-9][0-9])/);
if (versionMatch) {{
return originalValue.replace(versionMatch[1],
(parseFloat(versionMatch[1]) + (Math.random() * 0.01 - 0.005)).toFixed(2));
}}
}}
// VERSION
if (parameter === 0x1F02) {{
const originalValue = originalGetParameter.call(this, parameter);
// Subtile Änderungen an der Version
const versionMatch = originalValue.match(/^WebGL ([0-9]\\.[0-9])/);
if (versionMatch) {{
return originalValue.replace(versionMatch[1],
(parseFloat(versionMatch[1]) + (Math.random() * 0.01 - 0.005)).toFixed(1));
}}
}}
return originalGetParameter.apply(this, arguments);
}};
}});
// WebGL Vertex und Fragment Shader spoofen
const shaderSourceProxies = [
WebGLRenderingContext.prototype,
WebGL2RenderingContext.prototype
];
shaderSourceProxies.forEach(contextPrototype => {{
if (!contextPrototype) return;
const originalShaderSource = contextPrototype.shaderSource;
contextPrototype.shaderSource = function(shader, source) {{
// Füge geringfügige Unterschiede in Kommentaren ein, ohne die Funktionalität zu beeinträchtigen
if (source.indexOf('//') !== -1) {{
// Zufälligen Kommentar leicht modifizieren
source = source.replace(/\\/\\/(.*?)\\n/g, (match, comment) => {{
if (Math.random() < 0.1) {{
// Füge ein Leerzeichen oder einen Bindestrich hinzu oder entferne eines
const modifications = [
' ', '-', '', ' '
];
const mod = modifications[Math.floor(Math.random() * modifications.length)];
return `//` + comment + mod + `\\n`;
}}
return match;
}});
}}
// Ersetze bestimmte Whitespace-Muster
source = source.replace(/\\s{2,}/g, match => {{
if (Math.random() < 0.05) {{
return ' '.repeat(match.length + (Math.random() < 0.5 ? 1 : -1));
}}
return match;
}});
return originalShaderSource.call(this, shader, source);
}};
}});
// Canvas-Kontext spoofen
const getContextProxies = [
HTMLCanvasElement.prototype
];
getContextProxies.forEach(canvasPrototype => {{
const originalGetContext = canvasPrototype.getContext;
canvasPrototype.getContext = function(contextType, contextAttributes) {{
const context = originalGetContext.apply(this, arguments);
if (context && (contextType === 'webgl' || contextType === 'experimental-webgl' || contextType === 'webgl2')) {{
// Zufällige Werte für verschiedene Parameter einführen
if ({str(self.defaults["webgl_noise"]).lower()}) {{
// Zufällige Modifikation für MAX_VERTEX_UNIFORM_VECTORS
const MAX_VERTEX_UNIFORM_VECTORS = context.getParameter(context.MAX_VERTEX_UNIFORM_VECTORS);
Object.defineProperty(context, 'MAX_VERTEX_UNIFORM_VECTORS', {{
get: () => MAX_VERTEX_UNIFORM_VECTORS + Math.floor(Math.random() * 3) - 1
}});
// Zufällige Modifikation für MAX_FRAGMENT_UNIFORM_VECTORS
const MAX_FRAGMENT_UNIFORM_VECTORS = context.getParameter(context.MAX_FRAGMENT_UNIFORM_VECTORS);
Object.defineProperty(context, 'MAX_FRAGMENT_UNIFORM_VECTORS', {{
get: () => MAX_FRAGMENT_UNIFORM_VECTORS + Math.floor(Math.random() * 3) - 1
}});
}}
}}
return context;
}};
}});
}}
"""
self.scripts.append(script)
def _init_audio_protection(self):
"""
Initialisiert den Schutz gegen Audio-Fingerprinting.
Dies modifiziert die Audio-API-Funktionen, die für Fingerprinting verwendet werden.
"""
script = f"""
() => {{
// Audio-Kontext spoofen
if (window.AudioContext || window.webkitAudioContext) {{
const AudioContextProxy = window.AudioContext || window.webkitAudioContext;
const originalAudioContext = AudioContextProxy;
// AudioContext überschreiben
window.AudioContext = window.webkitAudioContext = function() {{
const context = new originalAudioContext();
// createOscillator überschreiben
const originalCreateOscillator = context.createOscillator;
context.createOscillator = function() {{
const oscillator = originalCreateOscillator.apply(this, arguments);
// Frequenz leicht modifizieren
const originalFrequency = oscillator.frequency;
Object.defineProperty(oscillator, 'frequency', {{
get: function() {{
return originalFrequency;
}},
set: function(value) {{
if (typeof value === 'number') {{
// Leichte Änderung hinzufügen
const noise = (Math.random() * 0.02 - 0.01) * value;
originalFrequency.value = value + noise;
}} else {{
originalFrequency.value = value;
}}
}}
}});
return oscillator;
}};
// getChannelData überschreiben
const originalGetChannelData = context.createBuffer.prototype?.getChannelData || OfflineAudioContext.prototype?.getChannelData;
if (originalGetChannelData) {{
context.createBuffer.prototype.getChannelData = function(channel) {{
const array = originalGetChannelData.call(this, channel);
if ({str(self.defaults["audio_noise"]).lower()} && this.length > 20) {{
// Sehr subtiles Rauschen hinzufügen (bei etwa 0,1% der Samples)
const noise = {self.noise_level} * 0.0001;
// Effiziente Implementierung
const samples = Math.min(200, Math.floor(array.length * 0.001));
for (let i = 0; i < samples; i++) {{
const idx = Math.floor(Math.random() * array.length);
array[idx] += (Math.random() * 2 - 1) * noise;
}}
}}
return array;
}};
}}
// AnalyserNode.getFloatFrequencyData überschreiben
if (context.createAnalyser) {{
const originalCreateAnalyser = context.createAnalyser;
context.createAnalyser = function() {{
const analyser = originalCreateAnalyser.apply(this, arguments);
const originalGetFloatFrequencyData = analyser.getFloatFrequencyData;
analyser.getFloatFrequencyData = function(array) {{
originalGetFloatFrequencyData.call(this, array);
if ({str(self.defaults["audio_noise"]).lower()} && array.length > 20) {{
// Sehr subtiles Rauschen hinzufügen
const noise = {self.noise_level} * 0.001;
// Effiziente Implementierung für große Arrays
const samples = Math.min(20, Math.floor(array.length * 0.01));
for (let i = 0; i < samples; i++) {{
const idx = Math.floor(Math.random() * array.length);
array[idx] += (Math.random() * 2 - 1) * noise;
}}
}}
return array;
}};
return analyser;
}};
}}
return context;
}};
}}
}}
"""
self.scripts.append(script)
def _init_navigator_protection(self):
"""
Initialisiert den Schutz gegen Navigator-Objekt-Fingerprinting.
Dies modifiziert verschiedene Navigator-Eigenschaften, die für Fingerprinting verwendet werden.
"""
hardware_concurrency = self.defaults["hardware_concurrency"]
device_memory = self.defaults["device_memory"]
script = f"""
() => {{
// Navigator-Eigenschaften überschreiben
// hardwareConcurrency (CPU-Kerne)
if (navigator.hardwareConcurrency) {{
Object.defineProperty(navigator, 'hardwareConcurrency', {{
get: () => {hardware_concurrency}
}});
}}
// deviceMemory (RAM)
if (navigator.deviceMemory) {{
Object.defineProperty(navigator, 'deviceMemory', {{
get: () => {device_memory}
}});
}}
// language und languages
if (navigator.language) {{
const originalLanguage = navigator.language;
const originalLanguages = navigator.languages;
Object.defineProperty(navigator, 'language', {{
get: () => originalLanguage
}});
Object.defineProperty(navigator, 'languages', {{
get: () => originalLanguages
}});
}}
// userAgent-Konsistenz sicherstellen
if (navigator.userAgent) {{
const userAgent = navigator.userAgent;
// Wenn der userAgent bereits überschrieben wurde, stellen wir
// sicher, dass die appVersion und platform konsistent sind
const browserInfo = {{
chrome: /Chrome\\/(\\d+)/.exec(userAgent),
firefox: /Firefox\\/(\\d+)/.exec(userAgent),
safari: /Safari\\/(\\d+)/.exec(userAgent),
edge: /Edg(e|)\\/(\\d+)/.exec(userAgent)
}};
// Platform basierend auf userAgent bestimmen
let platform = '{self.defaults.get("platform", "Win32")}';
if (/Windows/.test(userAgent)) platform = 'Win32';
else if (/Macintosh/.test(userAgent)) platform = 'MacIntel';
else if (/Linux/.test(userAgent)) platform = 'Linux x86_64';
else if (/Android/.test(userAgent)) platform = 'Linux armv8l';
else if (/iPhone|iPad/.test(userAgent)) platform = 'iPhone';
Object.defineProperty(navigator, 'platform', {{
get: () => platform
}});
// appVersion konsistent machen
if (navigator.appVersion) {{
Object.defineProperty(navigator, 'appVersion', {{
get: () => userAgent.substring(8)
}});
}}
// vendor basierend auf Browser setzen
let vendor = '{self.defaults.get("vendor", "Google Inc.")}';
if (browserInfo.safari) vendor = 'Apple Computer, Inc.';
else if (browserInfo.firefox) vendor = '';
Object.defineProperty(navigator, 'vendor', {{
get: () => vendor
}});
}}
"""
self.scripts.append(script)
def _init_misc_protections(self):
"""
Initialisiert verschiedene weitere Schutzmaßnahmen gegen Fingerprinting.
"""
timezone_id = self.defaults["timezone_id"]
script = f"""
() => {{
// Date.prototype.getTimezoneOffset überschreiben
const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset;
Date.prototype.getTimezoneOffset = function() {{
// Zeitzonendaten für '{timezone_id}'
// Mitteleuropäische Zeit (CET/CEST): UTC+1 / UTC+2 (Sommerzeit)
const date = new Date(this);
// Prüfen, ob Sommerzeit
const jan = new Date(date.getFullYear(), 0, 1);
const jul = new Date(date.getFullYear(), 6, 1);
const standardOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
// Sommerzeit in Europa: Ende März bis Ende Oktober
const isDST = date.getMonth() > 2 && date.getMonth() < 10;
// Offset in Minuten: CET = -60, CEST = -120
return isDST ? -120 : -60;
}};
// Plugins und MimeTypes Schutz
if (navigator.plugins) {{
// Leere oder gefälschte Plugins
Object.defineProperty(navigator, 'plugins', {{
get: () => {{
// Plugins-Array-Eigenschaften simulieren
const plugins = {{
length: 0,
item: () => null,
namedItem: () => null,
refresh: () => {{}},
[Symbol.iterator]: function* () {{}}
}};
return plugins;
}}
}});
// MimeTypes ebenfalls leeren
if (navigator.mimeTypes) {{
Object.defineProperty(navigator, 'mimeTypes', {{
get: () => {{
// MimeTypes-Array-Eigenschaften simulieren
const mimeTypes = {{
length: 0,
item: () => null,
namedItem: () => null,
[Symbol.iterator]: function* () {{}}
}};
return mimeTypes;
}}
}});
}}
}}
// Performance.now() und Date.now() - Schutz gegen Timing-Angriffe
if (window.performance && performance.now) {{
const originalNow = performance.now;
performance.now = function() {{
const value = originalNow.call(performance);
// Subtile Abweichung hinzufügen
return value + (Math.random() * 0.01);
}};
}}
// Date.now() ebenfalls mit subtiler Abweichung
const originalDateNow = Date.now;
Date.now = function() {{
const value = originalDateNow.call(Date);
// Subtile Abweichung hinzufügen (±1ms)
return value + (Math.random() < 0.5 ? 1 : 0);
}};
// screen-Eigenschaften konsistent machen
if (window.screen) {{
const originalWidth = screen.width;
const originalHeight = screen.height;
const originalColorDepth = screen.colorDepth;
const originalPixelDepth = screen.pixelDepth;
// Abweichungen verhindern - konsistente Werte liefern
Object.defineProperties(screen, {{
'width': {{ get: () => originalWidth }},
'height': {{ get: () => originalHeight }},
'availWidth': {{ get: () => originalWidth }},
'availHeight': {{ get: () => originalHeight - 40 }}, // Taskleiste simulieren
'colorDepth': {{ get: () => originalColorDepth }},
'pixelDepth': {{ get: () => originalPixelDepth }}
}});
}}
}}
"""
self.scripts.append(script)
def apply_to_context(self, context=None):
"""
Wendet alle Skripte auf den Browser-Kontext an.
Args:
context: Der Browser-Kontext, falls er noch nicht gesetzt wurde
"""
if context:
self.context = context
if not self.context:
logger.warning("Kein Browser-Kontext zum Anwenden der Fingerprint-Schutzmaßnahmen")
return
for script in self.scripts:
self.context.add_init_script(script)
logger.info(f"Fingerprint-Schutzmaßnahmen auf Browser-Kontext angewendet ({len(self.scripts)} Skripte)")
def get_fingerprint_status(self) -> Dict[str, Any]:
"""
Gibt den aktuellen Status der Fingerprint-Schutzmaßnahmen zurück.
Returns:
Dict[str, Any]: Status der Fingerprint-Schutzmaßnahmen
"""
status = {
"active": self.context is not None,
"script_count": len(self.scripts),
"protections": {
"canvas": self.defaults["canvas_noise"],
"webgl": self.defaults["webgl_noise"],
"audio": self.defaults["audio_noise"],
"navigator": True,
"battery": "getBattery" in self.scripts[-1],
"timing": "performance.now" in self.scripts[-1]
},
"noise_level": self.noise_level,
"custom_values": {
"webgl_vendor": self.defaults["webgl_vendor"],
"webgl_renderer": self.defaults["webgl_renderer"],
"hardware_concurrency": self.defaults["hardware_concurrency"],
"device_memory": self.defaults["device_memory"],
"timezone_id": self.defaults["timezone_id"]
}
}
return status
def rotate_fingerprint(self, noise_level: Optional[float] = None):
"""
Rotiert den Fingerprint durch Neugenerierung der Schutzmaßnahmen.
Args:
noise_level: Optionales neues Rauschniveau (0.0-1.0)
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if noise_level is not None:
self.noise_level = max(0.0, min(1.0, noise_level))
try:
# Skripte zurücksetzen
self.scripts = []
# Neues WebGL-Vendor/Renderer-Paar generieren
webgl_vendors = [
"Google Inc.",
"Google Inc. (Intel)",
"Google Inc. (NVIDIA)",
"Google Inc. (AMD)",
"Intel Inc.",
"NVIDIA Corporation",
"AMD"
]
webgl_renderers = [
"ANGLE (Intel, Intel(R) HD Graphics 620 Direct3D11 vs_5_0 ps_5_0)",
"ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Direct3D11 vs_5_0 ps_5_0)",
"ANGLE (AMD, AMD Radeon RX 580 Direct3D11 vs_5_0 ps_5_0)",
"Intel Iris OpenGL Engine",
"NVIDIA GeForce GTX 980 OpenGL Engine",
"AMD Radeon Pro 560 OpenGL Engine",
"Mesa DRI Intel(R) UHD Graphics 620 (KBL GT2)",
"Mesa DRI NVIDIA GeForce GTX 1650"
]
# Zufällige Werte wählen
self.defaults["webgl_vendor"] = random.choice(webgl_vendors)
self.defaults["webgl_renderer"] = random.choice(webgl_renderers)
# Hardware-Concurrency und Device-Memory variieren
self.defaults["hardware_concurrency"] = random.choice([2, 4, 6, 8, 12, 16])
self.defaults["device_memory"] = random.choice([2, 4, 8, 16])
# Schutzmaßnahmen neu initialisieren
self._init_protections()
# Auf Kontext anwenden, falls vorhanden
if self.context:
self.apply_to_context()
logger.info(f"Fingerprint erfolgreich rotiert (Noise-Level: {self.noise_level:.2f})")
return True
except Exception as e:
logger.error(f"Fehler bei der Rotation des Fingerprints: {e}")
return False

Datei anzeigen

@ -0,0 +1,127 @@
# browser/playwright_extensions.py
"""
Erweiterungen für den PlaywrightManager - Fügt zusätzliche Funktionalität hinzu
"""
import logging
from typing import Dict, Any, Optional
from browser.fingerprint_protection import FingerprintProtection
logger = logging.getLogger("playwright_extensions")
class PlaywrightExtensions:
"""
Erweiterungsklasse für den PlaywrightManager.
Bietet zusätzliche Funktionalität, ohne die Hauptklasse zu verändern.
"""
def __init__(self, playwright_manager):
"""
Initialisiert die Erweiterungsklasse.
Args:
playwright_manager: Eine Instanz des PlaywrightManager
"""
self.playwright_manager = playwright_manager
self.fingerprint_protection = None
self.enhanced_stealth_enabled = False
def enable_enhanced_fingerprint_protection(self, config: Optional[Dict[str, Any]] = None) -> bool:
"""
Aktiviert den erweiterten Fingerprint-Schutz.
Args:
config: Optionale Konfiguration für den Fingerprint-Schutz
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Sicherstellen, dass der Browser gestartet wurde
if not hasattr(self.playwright_manager, 'context') or self.playwright_manager.context is None:
logger.warning("Browser muss zuerst gestartet werden, bevor der Fingerprint-Schutz aktiviert werden kann")
return False
# Basis-Stealth-Konfiguration aus dem PlaywrightManager verwenden
stealth_config = getattr(self.playwright_manager, 'stealth_config', {})
# Mit der benutzerdefinierten Konfiguration erweitern, falls vorhanden
if config:
stealth_config.update(config)
# Fingerprint-Schutz initialisieren
self.fingerprint_protection = FingerprintProtection(
context=self.playwright_manager.context,
stealth_config=stealth_config
)
# Schutzmaßnahmen auf den Kontext anwenden
self.fingerprint_protection.apply_to_context()
# Status aktualisieren
self.enhanced_stealth_enabled = True
logger.info("Erweiterter Fingerprint-Schutz aktiviert")
return True
except Exception as e:
logger.error(f"Fehler beim Aktivieren des erweiterten Fingerprint-Schutzes: {e}")
return False
def rotate_fingerprint(self, noise_level: Optional[float] = None) -> bool:
"""
Rotiert den Browser-Fingerprint.
Args:
noise_level: Optionales neues Rauschniveau (0.0-1.0)
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if not self.enhanced_stealth_enabled or self.fingerprint_protection is None:
logger.warning("Erweiterter Fingerprint-Schutz ist nicht aktiviert")
return False
return self.fingerprint_protection.rotate_fingerprint(noise_level)
def get_fingerprint_status(self) -> Dict[str, Any]:
"""
Gibt den aktuellen Status des Fingerprint-Schutzes zurück.
Returns:
Dict[str, Any]: Status des Fingerprint-Schutzes
"""
if not self.enhanced_stealth_enabled or self.fingerprint_protection is None:
return {"active": False, "message": "Erweiterter Fingerprint-Schutz ist nicht aktiviert"}
return self.fingerprint_protection.get_fingerprint_status()
def hook_into_playwright_manager(self) -> None:
"""
Hängt die Erweiterungsmethoden an den PlaywrightManager.
"""
if not self.playwright_manager:
logger.error("Kein PlaywrightManager zum Anhängen der Erweiterungen")
return
# Originalstart-Methode speichern
original_start = self.playwright_manager.start
# Die start-Methode überschreiben, um den Fingerprint-Schutz automatisch zu aktivieren
def enhanced_start(*args, **kwargs):
result = original_start(*args, **kwargs)
# Wenn start erfolgreich war und erweiterter Schutz aktiviert ist,
# wenden wir den Fingerprint-Schutz auf den neuen Kontext an
if result and self.enhanced_stealth_enabled and self.fingerprint_protection:
self.fingerprint_protection.set_context(self.playwright_manager.context)
self.fingerprint_protection.apply_to_context()
return result
# Methoden dynamisch zum PlaywrightManager hinzufügen
self.playwright_manager.enable_enhanced_fingerprint_protection = self.enable_enhanced_fingerprint_protection
self.playwright_manager.rotate_fingerprint = self.rotate_fingerprint
self.playwright_manager.get_fingerprint_status = self.get_fingerprint_status
self.playwright_manager.start = enhanced_start

517
browser/playwright_manager.py Normale Datei
Datei anzeigen

@ -0,0 +1,517 @@
"""
Playwright Manager - Hauptklasse für die Browser-Steuerung mit Anti-Bot-Erkennung
"""
import os
import json
import logging
import random
import time
from pathlib import Path
from typing import Dict, Optional, List, Any, Tuple
from playwright.sync_api import sync_playwright, Browser, Page, BrowserContext, ElementHandle
# Konfiguriere Logger
logger = logging.getLogger("playwright_manager")
class PlaywrightManager:
"""
Verwaltet Browser-Sitzungen mit Playwright, einschließlich Stealth-Modus und Proxy-Einstellungen.
"""
def __init__(self,
headless: bool = False,
proxy: Optional[Dict[str, str]] = None,
browser_type: str = "chromium",
user_agent: Optional[str] = None,
screenshots_dir: str = "screenshots",
slowmo: int = 0):
"""
Initialisiert den PlaywrightManager.
Args:
headless: Ob der Browser im Headless-Modus ausgeführt werden soll
proxy: Proxy-Konfiguration (z.B. {'server': 'http://myproxy.com:3128', 'username': 'user', 'password': 'pass'})
browser_type: Welcher Browser-Typ verwendet werden soll ("chromium", "firefox", oder "webkit")
user_agent: Benutzerdefinierter User-Agent
screenshots_dir: Verzeichnis für Screenshots
slowmo: Verzögerung zwischen Aktionen in Millisekunden (nützlich für Debugging)
"""
self.headless = headless
self.proxy = proxy
self.browser_type = browser_type
self.user_agent = user_agent
self.screenshots_dir = screenshots_dir
self.slowmo = slowmo
# Stelle sicher, dass das Screenshots-Verzeichnis existiert
os.makedirs(self.screenshots_dir, exist_ok=True)
# Playwright-Instanzen
self.playwright = None
self.browser = None
self.context = None
self.page = None
# Zähler für Wiederhholungsversuche
self.retry_counter = {}
# Lade Stealth-Konfigurationen
self.stealth_config = self._load_stealth_config()
def _load_stealth_config(self) -> Dict[str, Any]:
"""Lädt die Stealth-Konfigurationen aus der Datei oder verwendet Standardwerte."""
try:
config_dir = Path(__file__).parent.parent / "config"
stealth_config_path = config_dir / "stealth_config.json"
if stealth_config_path.exists():
with open(stealth_config_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.warning(f"Konnte Stealth-Konfiguration nicht laden: {e}")
# Verwende Standardwerte, wenn das Laden fehlschlägt
return {
"vendor": "Google Inc.",
"platform": "Win32",
"webdriver": False,
"accept_language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
"timezone_id": "Europe/Berlin",
"fingerprint_noise": True,
"device_scale_factor": 1.0,
}
def start(self) -> Page:
"""
Startet die Playwright-Sitzung und gibt die Browser-Seite zurück.
Returns:
Page: Die Browser-Seite
"""
if self.page is not None:
return self.page
try:
self.playwright = sync_playwright().start()
# Wähle den Browser-Typ
if self.browser_type == "firefox":
browser_instance = self.playwright.firefox
elif self.browser_type == "webkit":
browser_instance = self.playwright.webkit
else:
browser_instance = self.playwright.chromium
# Browser-Startoptionen
browser_args = []
if self.browser_type == "chromium":
# Chrome-spezifische Argumente für Anti-Bot-Erkennung
browser_args.extend([
'--disable-blink-features=AutomationControlled',
'--disable-features=IsolateOrigins,site-per-process',
'--disable-site-isolation-trials',
])
# Browser starten
self.browser = browser_instance.launch(
headless=self.headless,
args=browser_args,
slow_mo=self.slowmo
)
# Kontext-Optionen für Stealth-Modus
context_options = {
"viewport": {"width": 1920, "height": 1080},
"device_scale_factor": self.stealth_config.get("device_scale_factor", 1.0),
"locale": "de-DE",
"timezone_id": self.stealth_config.get("timezone_id", "Europe/Berlin"),
"accept_downloads": True,
}
# User-Agent setzen
if self.user_agent:
context_options["user_agent"] = self.user_agent
# Proxy-Einstellungen, falls vorhanden
if self.proxy:
context_options["proxy"] = self.proxy
# Browserkontext erstellen
self.context = self.browser.new_context(**context_options)
# JavaScript-Fingerprinting-Schutz
self._apply_stealth_scripts()
# Neue Seite erstellen
self.page = self.context.new_page()
# Event-Listener für Konsolen-Logs
self.page.on("console", lambda msg: logger.debug(f"BROWSER CONSOLE: {msg.text}"))
return self.page
except Exception as e:
logger.error(f"Fehler beim Starten des Browsers: {e}")
self.close()
raise
def _apply_stealth_scripts(self):
"""Wendet JavaScript-Skripte an, um Browser-Fingerprinting zu umgehen."""
# Diese Skripte überschreiben Eigenschaften, die für Bot-Erkennung verwendet werden
scripts = [
# WebDriver-Eigenschaft überschreiben
"""
() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
});
}
""",
# Navigator-Eigenschaften überschreiben
f"""
() => {{
const newProto = navigator.__proto__;
delete newProto.webdriver;
navigator.__proto__ = newProto;
Object.defineProperty(navigator, 'platform', {{
get: () => '{self.stealth_config.get("platform", "Win32")}'
}});
Object.defineProperty(navigator, 'languages', {{
get: () => ['de-DE', 'de', 'en-US', 'en']
}});
Object.defineProperty(navigator, 'vendor', {{
get: () => '{self.stealth_config.get("vendor", "Google Inc.")}'
}});
}}
""",
# Chrome-Objekte hinzufügen, die in normalen Browsern vorhanden sind
"""
() => {
// Fügt chrome.runtime hinzu, falls nicht vorhanden
if (!window.chrome) {
window.chrome = {};
}
if (!window.chrome.runtime) {
window.chrome.runtime = {};
window.chrome.runtime.sendMessage = function() {};
}
}
""",
# Plugin-Fingerprinting
"""
() => {
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
}
"""
]
# Wenn Fingerprint-Noise aktiviert ist, füge zufällige Variationen hinzu
if self.stealth_config.get("fingerprint_noise", True):
scripts.append("""
() => {
// Canvas-Fingerprinting leicht verändern
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(type) {
const result = originalToDataURL.apply(this, arguments);
if (this.width > 16 && this.height > 16) {
// Kleines Rauschen in Pixels einfügen
const context = this.getContext('2d');
const imageData = context.getImageData(0, 0, 2, 2);
const pixelArray = imageData.data;
// Ändere einen zufälligen Pixel leicht
const randomPixel = Math.floor(Math.random() * pixelArray.length / 4) * 4;
pixelArray[randomPixel] = (pixelArray[randomPixel] + Math.floor(Math.random() * 10)) % 256;
context.putImageData(imageData, 0, 0);
}
return result;
};
}
""")
# Skripte auf den Browser-Kontext anwenden
for script in scripts:
self.context.add_init_script(script)
def navigate_to(self, url: str, wait_until: str = "networkidle", timeout: int = 30000) -> bool:
"""
Navigiert zu einer bestimmten URL und wartet, bis die Seite geladen ist.
Args:
url: Die Ziel-URL
wait_until: Wann die Navigation als abgeschlossen gilt ("load", "domcontentloaded", "networkidle")
timeout: Timeout in Millisekunden
Returns:
bool: True bei erfolgreicher Navigation, False sonst
"""
if self.page is None:
self.start()
try:
logger.info(f"Navigiere zu: {url}")
self.page.goto(url, wait_until=wait_until, timeout=timeout)
return True
except Exception as e:
logger.error(f"Fehler bei der Navigation zu {url}: {e}")
self.take_screenshot(f"navigation_error_{int(time.time())}")
return False
def wait_for_selector(self, selector: str, timeout: int = 30000) -> Optional[ElementHandle]:
"""
Wartet auf ein Element mit dem angegebenen Selektor.
Args:
selector: CSS- oder XPath-Selektor
timeout: Timeout in Millisekunden
Returns:
Optional[ElementHandle]: Das Element oder None, wenn nicht gefunden
"""
if self.page is None:
raise ValueError("Browser nicht gestartet. Rufe zuerst start() auf.")
try:
element = self.page.wait_for_selector(selector, timeout=timeout)
return element
except Exception as e:
logger.warning(f"Element nicht gefunden: {selector} - {e}")
return None
def fill_form_field(self, selector: str, value: str, timeout: int = 5000) -> bool:
"""
Füllt ein Formularfeld aus.
Args:
selector: Selektor für das Feld
value: Einzugebender Wert
timeout: Timeout in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Auf Element warten
element = self.wait_for_selector(selector, timeout)
if not element:
return False
# Element fokussieren
element.focus()
time.sleep(random.uniform(0.1, 0.3))
# Vorhandenen Text löschen (optional)
current_value = element.evaluate("el => el.value")
if current_value:
element.fill("")
time.sleep(random.uniform(0.1, 0.2))
# Text menschenähnlich eingeben
for char in value:
element.type(char, delay=random.uniform(20, 100))
time.sleep(random.uniform(0.01, 0.05))
logger.info(f"Feld {selector} gefüllt mit: {value}")
return True
except Exception as e:
logger.error(f"Fehler beim Ausfüllen von {selector}: {e}")
key = f"fill_{selector}"
return self._retry_action(key, lambda: self.fill_form_field(selector, value, timeout))
def click_element(self, selector: str, force: bool = False, timeout: int = 5000) -> bool:
"""
Klickt auf ein Element.
Args:
selector: Selektor für das Element
force: Force-Click verwenden
timeout: Timeout in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Auf Element warten
element = self.wait_for_selector(selector, timeout)
if not element:
return False
# Scroll zum Element
self.page.evaluate("element => element.scrollIntoView({ behavior: 'smooth', block: 'center' })", element)
time.sleep(random.uniform(0.3, 0.7))
# Menschenähnliches Verhalten - leichte Verzögerung vor dem Klick
time.sleep(random.uniform(0.2, 0.5))
# Element klicken
element.click(force=force, delay=random.uniform(20, 100))
logger.info(f"Element geklickt: {selector}")
return True
except Exception as e:
logger.error(f"Fehler beim Klicken auf {selector}: {e}")
key = f"click_{selector}"
return self._retry_action(key, lambda: self.click_element(selector, force, timeout))
def select_option(self, selector: str, value: str, timeout: int = 5000) -> bool:
"""
Wählt eine Option aus einem Dropdown-Menü.
Args:
selector: Selektor für das Dropdown
value: Wert oder sichtbarer Text der Option
timeout: Timeout in Millisekunden
Returns:
bool: True bei Erfolg, False bei Fehler
"""
try:
# Auf Element warten
element = self.wait_for_selector(selector, timeout)
if not element:
return False
# Option auswählen
self.page.select_option(selector, value=value)
logger.info(f"Option '{value}' ausgewählt in {selector}")
return True
except Exception as e:
logger.error(f"Fehler bei der Auswahl von '{value}' in {selector}: {e}")
key = f"select_{selector}"
return self._retry_action(key, lambda: self.select_option(selector, value, timeout))
def is_element_visible(self, selector: str, timeout: int = 5000) -> bool:
"""
Prüft, ob ein Element sichtbar ist.
Args:
selector: Selektor für das Element
timeout: Timeout in Millisekunden
Returns:
bool: True wenn sichtbar, False sonst
"""
try:
element = self.page.wait_for_selector(selector, timeout=timeout, state="visible")
return element is not None
except:
return False
def take_screenshot(self, name: str = None) -> str:
"""
Erstellt einen Screenshot der aktuellen Seite.
Args:
name: Name für den Screenshot (ohne Dateierweiterung)
Returns:
str: Pfad zum erstellten Screenshot
"""
if self.page is None:
raise ValueError("Browser nicht gestartet. Rufe zuerst start() auf.")
timestamp = int(time.time())
filename = f"{name}_{timestamp}.png" if name else f"screenshot_{timestamp}.png"
path = os.path.join(self.screenshots_dir, filename)
self.page.screenshot(path=path, full_page=True)
logger.info(f"Screenshot erstellt: {path}")
return path
def _retry_action(self, key: str, action_func, max_retries: int = 3) -> bool:
"""
Wiederholt eine Aktion bei Fehler.
Args:
key: Eindeutiger Schlüssel für die Aktion
action_func: Funktion, die ausgeführt werden soll
max_retries: Maximale Anzahl der Wiederholungen
Returns:
bool: Ergebnis der Aktion
"""
if key not in self.retry_counter:
self.retry_counter[key] = 0
self.retry_counter[key] += 1
if self.retry_counter[key] <= max_retries:
logger.info(f"Wiederhole Aktion {key} (Versuch {self.retry_counter[key]} von {max_retries})")
time.sleep(random.uniform(0.5, 1.0))
return action_func()
else:
logger.warning(f"Maximale Anzahl von Wiederholungen für {key} erreicht")
self.retry_counter[key] = 0
return False
def close(self):
"""Schließt den Browser und gibt Ressourcen frei."""
try:
if self.page:
self.page.close()
self.page = None
if self.context:
self.context.close()
self.context = None
if self.browser:
self.browser.close()
self.browser = None
if self.playwright:
self.playwright.stop()
self.playwright = None
logger.info("Browser-Sitzung geschlossen")
except Exception as e:
logger.error(f"Fehler beim Schließen des Browsers: {e}")
def __enter__(self):
"""Kontext-Manager-Eintritt."""
self.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Kontext-Manager-Austritt."""
self.close()
# Beispielnutzung, wenn direkt ausgeführt
if __name__ == "__main__":
# Konfiguriere Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Beispiel für einen Proxy (ohne Anmeldedaten)
proxy_config = {
"server": "http://example-proxy.com:8080"
}
# Browser starten und zu einer Seite navigieren
with PlaywrightManager(headless=False) as manager:
manager.navigate_to("https://www.instagram.com")
time.sleep(5) # Kurze Pause zum Anzeigen der Seite

216
browser/stealth_config.py Normale Datei
Datei anzeigen

@ -0,0 +1,216 @@
"""
Stealth-Konfiguration für Playwright - Anti-Bot-Erkennung
"""
import json
import logging
import os
import random
import platform
from pathlib import Path
from typing import Dict, Any, List
# Konfiguriere Logger
logger = logging.getLogger("stealth_config")
class StealthConfig:
"""
Konfiguriert Anti-Bot-Erkennungs-Einstellungen für Playwright.
Generiert und verwaltet verschiedene Fingerprint-Einstellungen.
"""
# Standardwerte für User-Agents
CHROME_DESKTOP_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
]
MOBILE_AGENTS = [
"Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/135.0.0.0 Mobile/15E148 Safari/604.1"
]
# Plattformen
PLATFORMS = {
"windows": "Win32",
"macos": "MacIntel",
"linux": "Linux x86_64",
"android": "Linux armv8l",
"ios": "iPhone"
}
# Browser-Sprachen
LANGUAGES = [
"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
"de-DE,de;q=0.9,en;q=0.8",
"de;q=0.9,en-US;q=0.8,en;q=0.7",
"en-US,en;q=0.9,de;q=0.8"
]
# Zeitzone für Deutschland
TIMEZONE_ID = "Europe/Berlin"
def __init__(self, config_dir: str = None):
"""
Initialisiert die Stealth-Konfiguration.
Args:
config_dir: Verzeichnis für Konfigurationsdateien
"""
self.config_dir = config_dir or os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config")
os.makedirs(self.config_dir, exist_ok=True)
self.config_path = os.path.join(self.config_dir, "stealth_config.json")
# Lade benutzerdefinierte User-Agents, falls vorhanden
self.user_agents = self._load_user_agents()
# Lade gespeicherte Konfiguration oder erstelle eine neue
self.config = self._load_or_create_config()
def _load_user_agents(self) -> Dict[str, List[str]]:
"""Lädt benutzerdefinierte User-Agents aus der Konfigurationsdatei."""
user_agents_path = os.path.join(self.config_dir, "user_agents.json")
if os.path.exists(user_agents_path):
try:
with open(user_agents_path, 'r', encoding='utf-8') as f:
agents = json.load(f)
if isinstance(agents, dict) and "desktop" in agents and "mobile" in agents:
return agents
except Exception as e:
logger.warning(f"Fehler beim Laden von user_agents.json: {e}")
# Standardwerte zurückgeben
return {
"desktop": self.CHROME_DESKTOP_AGENTS,
"mobile": self.MOBILE_AGENTS
}
def _load_or_create_config(self) -> Dict[str, Any]:
"""Lädt die Konfiguration oder erstellt eine neue, falls keine existiert."""
if os.path.exists(self.config_path):
try:
with open(self.config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
logger.info("Stealth-Konfiguration geladen")
return config
except Exception as e:
logger.warning(f"Konnte Stealth-Konfiguration nicht laden: {e}")
# Erstelle eine neue Konfiguration
config = self.generate_config()
self.save_config(config)
return config
def generate_config(self, device_type: str = "desktop") -> Dict[str, Any]:
"""
Generiert eine neue Stealth-Konfiguration.
Args:
device_type: "desktop" oder "mobile"
Returns:
Dict[str, Any]: Die generierte Konfiguration
"""
# Wähle Plattform und entsprechenden User-Agent
if device_type == "mobile":
platform_name = random.choice(["android", "ios"])
user_agent = random.choice(self.user_agents["mobile"])
else:
# Wähle eine Plattform, die zum System passt
system = platform.system().lower()
if system == "darwin":
platform_name = "macos"
elif system == "windows":
platform_name = "windows"
else:
platform_name = "linux"
user_agent = random.choice(self.user_agents["desktop"])
platform_value = self.PLATFORMS.get(platform_name, "Win32")
# Wähle weitere Konfigurationen
config = {
"user_agent": user_agent,
"platform": platform_value,
"vendor": "Google Inc." if "Chrome" in user_agent else "Apple Computer, Inc.",
"accept_language": random.choice(self.LANGUAGES),
"timezone_id": self.TIMEZONE_ID,
"device_scale_factor": random.choice([1.0, 1.25, 1.5, 2.0]) if random.random() < 0.3 else 1.0,
"color_depth": random.choice([24, 30, 48]),
"hardware_concurrency": random.choice([2, 4, 8, 12, 16]),
"device_memory": random.choice([2, 4, 8, 16]),
"webdriver": False,
"fingerprint_noise": True,
"device_type": device_type
}
return config
def save_config(self, config: Dict[str, Any]) -> None:
"""
Speichert die Konfiguration in einer Datei.
Args:
config: Die zu speichernde Konfiguration
"""
try:
with open(self.config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2)
logger.info(f"Stealth-Konfiguration gespeichert in: {self.config_path}")
except Exception as e:
logger.error(f"Fehler beim Speichern der Stealth-Konfiguration: {e}")
def get_config(self) -> Dict[str, Any]:
"""Gibt die aktuelle Konfiguration zurück."""
return self.config
def rotate_config(self, device_type: str = None) -> Dict[str, Any]:
"""
Generiert eine neue Konfiguration und speichert sie.
Args:
device_type: "desktop" oder "mobile", oder None für bestehenden Typ
Returns:
Dict[str, Any]: Die neue Konfiguration
"""
if device_type is None:
device_type = self.config.get("device_type", "desktop")
self.config = self.generate_config(device_type)
self.save_config(self.config)
return self.config
def get_user_agent(self) -> str:
"""Gibt den aktuellen User-Agent aus der Konfiguration zurück."""
return self.config.get("user_agent", self.CHROME_DESKTOP_AGENTS[0])
# Beispielnutzung, wenn direkt ausgeführt
if __name__ == "__main__":
# Konfiguriere Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Beispiel für Konfigurationserstellung
stealth = StealthConfig()
print("Aktuelle Konfiguration:")
print(json.dumps(stealth.get_config(), indent=2))
print("\nNeue Desktop-Konfiguration:")
desktop_config = stealth.rotate_config("desktop")
print(json.dumps(desktop_config, indent=2))
print("\nNeue Mobile-Konfiguration:")
mobile_config = stealth.rotate_config("mobile")
print(json.dumps(mobile_config, indent=2))

1
config/.machine_id Normale Datei
Datei anzeigen

@ -0,0 +1 @@
ae30d891-0b45-408e-8f47-75fada7cb094

0
config/__init__.py Normale Datei
Datei anzeigen

7
config/app_version.json Normale Datei
Datei anzeigen

@ -0,0 +1,7 @@
{
"current_version": "1.0.0",
"last_check": "2025-06-22T17:43:13.744626",
"channel": "stable",
"auto_check": true,
"auto_download": false
}

0
config/browser_config.json Normale Datei
Datei anzeigen

6
config/email_config.json Normale Datei
Datei anzeigen

@ -0,0 +1,6 @@
{
"imap_server": "imap.ionos.de",
"imap_port": 993,
"imap_user": "info@z5m7q9dk3ah2v1plx6ju.com",
"imap_pass": "cz&ie.O9$!:!tYY@"
}

0
config/facebook_config.json Normale Datei
Datei anzeigen

Datei anzeigen

10
config/license.json Normale Datei
Datei anzeigen

@ -0,0 +1,10 @@
{
"key": "",
"activation_date": "",
"expiry_date": "",
"status": "inactive",
"status_text": "Keine Lizenz aktiviert",
"features": [],
"last_online_check": "",
"signature": ""
}

9
config/license_config.json Normale Datei
Datei anzeigen

@ -0,0 +1,9 @@
{
"key": "",
"status": "inactive",
"hardware_id": "",
"activation_date": null,
"expiry_date": null,
"features": [],
"last_check": null
}

15
config/proxy_config.json Normale Datei
Datei anzeigen

@ -0,0 +1,15 @@
{
"ipv4": [
"85.254.81.222:44444:14a38ed2efe94:04ed25fb1b"
],
"ipv6": [
"92.119.89.251:30015:14a4622431481:a488401704"
],
"mobile": [
"de1.4g.iproyal.com:7296:1rtSh0G:XswBCIqi1joy5dX"
],
"mobile_api": {
"marsproxies": "9zKXWpMEA1",
"iproyal": ""
}
}

14
config/stealth_config.json Normale Datei
Datei anzeigen

@ -0,0 +1,14 @@
{
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
"platform": "Win32",
"vendor": "Google Inc.",
"accept_language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
"timezone_id": "Europe/Berlin",
"device_scale_factor": 1.0,
"color_depth": 24,
"hardware_concurrency": 8,
"device_memory": 8,
"webdriver": false,
"fingerprint_noise": true,
"device_type": "desktop"
}

46
config/theme.json Normale Datei
Datei anzeigen

@ -0,0 +1,46 @@
# Path: config/theme.json
{
"dark": {
"name": "Dark",
"palette": {
"Window": "#1E1E1E",
"WindowText": "#FFFFFF",
"Base": "#2D2D30",
"AlternateBase": "#252526",
"ToolTipBase": "#2D2D30",
"ToolTipText": "#FFFFFF",
"Text": "#FFFFFF",
"Button": "#0E639C",
"ButtonText": "#FFFFFF",
"BrightText": "#FF0000",
"Link": "#3794FF",
"Highlight": "#264F78",
"HighlightedText": "#FFFFFF"
},
"icons": {
"path_suffix": "dark"
}
},
"light": {
"name": "Light",
"palette": {
"Window": "#FFFFFF",
"WindowText": "#1E1E1E",
"Base": "#F5F5F5",
"AlternateBase": "#E5E5E5",
"ToolTipBase": "#F5F5F5",
"ToolTipText": "#1E1E1E",
"Text": "#1E1E1E",
"Button": "#0078D7",
"ButtonText": "#FFFFFF",
"BrightText": "#FF0000",
"Link": "#0066CC",
"Highlight": "#CCE8FF",
"HighlightedText": "#1E1E1E"
},
"icons": {
"path_suffix": "light"
}
}
}

0
config/tiktok_config.json Normale Datei
Datei anzeigen

0
config/twitter_config.json Normale Datei
Datei anzeigen

9
config/update_config.json Normale Datei
Datei anzeigen

@ -0,0 +1,9 @@
{
"last_check": "2025-04-01 12:00:00",
"check_interval": 86400,
"auto_check": true,
"auto_download": false,
"update_channel": "stable",
"download_path": "updates",
"downloaded_updates": []
}

31
config/user_agents.json Normale Datei
Datei anzeigen

@ -0,0 +1,31 @@
{
"desktop": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0"
],
"mobile": [
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/135.0.0.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/134.0.0.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/123.0 Mobile/15E148 Safari/605.1.15",
"Mozilla/5.0 (Linux; Android 14; SM-S9180) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 14; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 14; Pixel 7 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 13; SAMSUNG SM-A536B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/24.0 Chrome/133.0.0.0 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 13; SAMSUNG SM-A546B) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/24.0 Chrome/133.0.0.0 Mobile Safari/537.36"
]
}

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -0,0 +1,149 @@
"""
Controller für die Verwaltung von Accounts.
"""
import logging
import csv
from datetime import datetime
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QObject
logger = logging.getLogger("account_controller")
class AccountController(QObject):
"""Controller für die Verwaltung von Accounts."""
def __init__(self, db_manager):
super().__init__()
self.db_manager = db_manager
self.parent_view = None
def set_parent_view(self, view):
"""Setzt die übergeordnete View für Dialoge."""
self.parent_view = view
def on_account_created(self, platform: str, account_data: dict):
"""Wird aufgerufen, wenn ein Account erstellt wurde."""
account = {
"platform": platform.lower(),
"username": account_data.get("username", ""),
"password": account_data.get("password", ""),
"email": account_data.get("email", ""),
"phone": account_data.get("phone", ""),
"full_name": account_data.get("full_name", ""),
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
self.db_manager.add_account(account)
logger.info(f"Account in Datenbank gespeichert: {account['username']}")
# Erfolgsmeldung anzeigen
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Erfolg",
f"Account erfolgreich erstellt!\n\nBenutzername: {account['username']}\nPasswort: {account['password']}\nE-Mail/Telefon: {account['email'] or account['phone']}"
)
def load_accounts(self, platform=None):
"""Lädt Accounts aus der Datenbank."""
try:
if platform and hasattr(self.db_manager, "get_accounts_by_platform"):
accounts = self.db_manager.get_accounts_by_platform(platform.lower())
else:
accounts = self.db_manager.get_all_accounts()
if platform:
accounts = [acc for acc in accounts if acc.get("platform", "").lower() == platform.lower()]
return accounts
except Exception as e:
logger.error(f"Fehler beim Laden der Accounts: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler beim Laden der Accounts:\n{str(e)}"
)
return []
def export_accounts(self, platform=None):
"""Exportiert Accounts in eine CSV-Datei."""
parent = self.parent_view or None
file_path, _ = QFileDialog.getSaveFileName(
parent,
"Konten exportieren",
"",
"CSV-Dateien (*.csv);;Alle Dateien (*)"
)
if not file_path:
return
try:
# Accounts laden
accounts = self.load_accounts(platform)
with open(file_path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
# Header
writer.writerow([
"ID", "Plattform", "Benutzername", "Passwort",
"E-Mail", "Telefon", "Name", "Erstellt am"
])
# Daten
for account in accounts:
writer.writerow([
account.get("id", ""),
account.get("platform", ""),
account.get("username", ""),
account.get("password", ""),
account.get("email", ""),
account.get("phone", ""),
account.get("full_name", ""),
account.get("created_at", "")
])
logger.info(f"Accounts erfolgreich nach {file_path} exportiert")
if parent:
QMessageBox.information(
parent,
"Export erfolgreich",
f"Konten wurden erfolgreich nach {file_path} exportiert."
)
except Exception as e:
logger.error(f"Fehler beim Exportieren der Accounts: {e}")
if parent:
QMessageBox.critical(
parent,
"Export fehlgeschlagen",
f"Fehler beim Exportieren der Konten:\n{str(e)}"
)
def delete_account(self, account_id):
"""Löscht einen Account aus der Datenbank."""
try:
success = self.db_manager.delete_account(account_id)
if not success:
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Konto mit ID {account_id} konnte nicht gelöscht werden."
)
return success
except Exception as e:
logger.error(f"Fehler beim Löschen des Accounts: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler beim Löschen des Kontos:\n{str(e)}"
)
return False

231
controllers/main_controller.py Normale Datei
Datei anzeigen

@ -0,0 +1,231 @@
"""
Hauptcontroller für die Social Media Account Generator Anwendung.
"""
import logging
from PyQt5.QtWidgets import QMessageBox, QApplication
from views.main_window import MainWindow
from controllers.platform_controllers.instagram_controller import InstagramController
from controllers.platform_controllers.tiktok_controller import TikTokController
from controllers.account_controller import AccountController
from controllers.settings_controller import SettingsController
from database.db_manager import DatabaseManager
from utils.proxy_rotator import ProxyRotator
from utils.email_handler import EmailHandler
from utils.theme_manager import ThemeManager
from localization.language_manager import LanguageManager
from licensing.license_manager import LicenseManager
from updates.update_checker import UpdateChecker
logger = logging.getLogger("main")
class MainController:
"""Hauptcontroller, der die Anwendung koordiniert."""
def __init__(self, app):
# QApplication Referenz speichern
self.app = app
# Theme Manager initialisieren
self.theme_manager = ThemeManager(app)
# Language Manager initialisieren
self.language_manager = LanguageManager(app)
# Modelle initialisieren
self.db_manager = DatabaseManager()
self.proxy_rotator = ProxyRotator()
self.email_handler = EmailHandler()
self.license_manager = LicenseManager()
self.update_checker = UpdateChecker()
# Haupt-View erstellen
self.view = MainWindow(self.theme_manager, self.language_manager, self.db_manager)
# Untercontroller erstellen
self.account_controller = AccountController(self.db_manager)
self.settings_controller = SettingsController(
self.proxy_rotator,
self.email_handler,
self.license_manager
)
# Plattform-Controller initialisieren
self.platform_controllers = {}
# Instagram Controller hinzufügen
self.platform_controllers["instagram"] = InstagramController(
self.db_manager,
self.proxy_rotator,
self.email_handler,
self.language_manager
)
# TikTok Controller hinzufügen
self.platform_controllers["tiktok"] = TikTokController(
self.db_manager,
self.proxy_rotator,
self.email_handler,
self.language_manager
)
# Hier können in Zukunft weitere Controller hinzugefügt werden:
# self.platform_controllers["facebook"] = FacebookController(...)
# self.platform_controllers["twitter"] = TwitterController(...)
# self.platform_controllers["tiktok"] = TikTokController(...)
# Signals verbinden
self.connect_signals()
# Lizenz überprüfen
self.check_license()
# Auf Updates prüfen
self.check_for_updates()
# Hauptfenster anzeigen
self.view.show()
def connect_signals(self):
"""Verbindet alle Signale mit den entsprechenden Slots."""
# Plattformauswahl-Signal verbinden
self.view.platform_selected.connect(self.on_platform_selected)
# Zurück-Button verbinden
self.view.back_to_selector_requested.connect(self.show_platform_selector)
# Theme-Toggle verbinden
self.view.theme_toggled.connect(self.on_theme_toggled)
def on_platform_selected(self, platform: str):
"""Wird aufgerufen, wenn eine Plattform ausgewählt wird."""
logger.info(f"Plattform ausgewählt: {platform}")
# Aktuelle Plattform setzen
self.current_platform = platform.lower()
# Prüfen, ob die Plattform unterstützt wird
if self.current_platform not in self.platform_controllers:
logger.error(f"Plattform '{platform}' wird nicht unterstützt")
QMessageBox.critical(
self.view,
"Nicht unterstützt",
f"Die Plattform '{platform}' ist noch nicht implementiert."
)
return
# Plattformspezifischen Controller abrufen
platform_controller = self.platform_controllers.get(self.current_platform)
# Plattform-View initialisieren
self.view.init_platform_ui(platform, platform_controller)
# Tab-Hooks verbinden
self.connect_tab_hooks(platform_controller)
# Plattformspezifische Ansicht anzeigen
self.view.show_platform_ui()
def on_theme_toggled(self):
"""Wird aufgerufen, wenn das Theme gewechselt wird."""
if self.theme_manager:
theme_name = self.theme_manager.get_current_theme()
logger.info(f"Theme gewechselt zu: {theme_name}")
# Hier kann zusätzliche Logik für Theme-Wechsel hinzugefügt werden
# z.B. UI-Elemente aktualisieren, die nicht automatisch aktualisiert werden
def connect_tab_hooks(self, platform_controller):
"""Verbindet die Tab-Hooks mit dem Plattform-Controller."""
# Generator-Tab-Hooks
if hasattr(platform_controller, "get_generator_tab"):
generator_tab = platform_controller.get_generator_tab()
generator_tab.account_created.connect(self.account_controller.on_account_created)
# Einstellungen-Tab-Hooks
if hasattr(platform_controller, "get_settings_tab"):
settings_tab = platform_controller.get_settings_tab()
settings_tab.proxy_settings_saved.connect(self.settings_controller.save_proxy_settings)
settings_tab.proxy_tested.connect(self.settings_controller.test_proxy)
settings_tab.email_settings_saved.connect(self.settings_controller.save_email_settings)
settings_tab.email_tested.connect(self.settings_controller.test_email)
settings_tab.license_activated.connect(self.settings_controller.activate_license)
def show_platform_selector(self):
"""Zeigt den Plattform-Selektor an."""
logger.info("Zurück zur Plattformauswahl")
self.view.show_platform_selector()
if hasattr(self.view, "platform_selector"):
self.view.platform_selector.load_accounts()
def check_license(self):
"""Überprüft, ob eine gültige Lizenz vorhanden ist."""
is_licensed = self.license_manager.is_licensed()
if not is_licensed:
license_info = self.license_manager.get_license_info()
status = license_info.get("status_text", "Inaktiv")
# Wenn keine Lizenz vorhanden ist, zeigen wir eine Warnung an
QMessageBox.warning(
self.view,
"Keine gültige Lizenz",
f"Status: {status}\n\nBitte aktivieren Sie eine Lizenz, um die Software zu nutzen."
)
return False
return True
def check_for_updates(self):
"""Prüft auf Updates."""
try:
update_info = self.update_checker.check_for_updates()
if update_info["has_update"]:
reply = QMessageBox.question(
self.view,
"Update verfügbar",
f"Eine neue Version ist verfügbar: {update_info['latest_version']}\n"
f"(Aktuelle Version: {update_info['current_version']})\n\n"
f"Release-Datum: {update_info['release_date']}\n"
f"Release-Notes:\n{update_info['release_notes']}\n\n"
"Möchten Sie das Update jetzt herunterladen?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if reply == QMessageBox.Yes:
self.download_update(update_info)
except Exception as e:
logger.error(f"Fehler bei der Update-Prüfung: {e}")
def download_update(self, update_info):
"""Lädt ein Update herunter."""
try:
download_result = self.update_checker.download_update(
update_info["download_url"],
update_info["latest_version"]
)
if download_result["success"]:
QMessageBox.information(
self.view,
"Download erfolgreich",
f"Update wurde heruntergeladen: {download_result['file_path']}\n\n"
"Bitte schließen Sie die Anwendung und führen Sie das Update aus."
)
else:
QMessageBox.warning(
self.view,
"Download fehlgeschlagen",
f"Fehler beim Herunterladen des Updates:\n{download_result['error']}"
)
except Exception as e:
logger.error(f"Fehler beim Herunterladen des Updates: {e}")
QMessageBox.critical(
self.view,
"Fehler",
f"Fehler beim Herunterladen des Updates:\n{str(e)}"
)

Datei anzeigen

@ -0,0 +1,132 @@
"""
Basis-Controller für Plattform-spezifische Funktionalität.
"""
import logging
from PyQt5.QtCore import QObject
from views.tabs.generator_tab import GeneratorTab
from views.tabs.accounts_tab import AccountsTab
from views.tabs.settings_tab import SettingsTab
class BasePlatformController(QObject):
"""Basis-Controller-Klasse für Plattformspezifische Logik."""
def __init__(self, platform_name, db_manager, proxy_rotator, email_handler, language_manager=None):
super().__init__()
self.platform_name = platform_name
self.logger = logging.getLogger(f"{platform_name.lower()}_controller")
# Modelle
self.db_manager = db_manager
self.proxy_rotator = proxy_rotator
self.email_handler = email_handler
self.language_manager = language_manager
# Tabs
self._generator_tab = None
self._accounts_tab = None
self._settings_tab = None
# Plattformspezifische Initialisierungen
self.init_platform()
def init_platform(self):
"""
Initialisiert plattformspezifische Komponenten.
Diese Methode sollte von Unterklassen überschrieben werden.
"""
pass
def get_generator_tab(self):
"""Gibt den Generator-Tab zurück oder erstellt ihn bei Bedarf."""
if not self._generator_tab:
self._generator_tab = self.create_generator_tab()
return self._generator_tab
def get_accounts_tab(self):
"""Gibt den Accounts-Tab zurück oder erstellt ihn bei Bedarf."""
if not self._accounts_tab:
self._accounts_tab = self.create_accounts_tab()
return self._accounts_tab
def get_settings_tab(self):
"""Gibt den Settings-Tab zurück oder erstellt ihn bei Bedarf."""
if not self._settings_tab:
self._settings_tab = self.create_settings_tab()
return self._settings_tab
def create_generator_tab(self):
"""
Erstellt den Generator-Tab.
Diese Methode sollte von Unterklassen überschrieben werden.
"""
return GeneratorTab(self.platform_name, self.language_manager)
def create_accounts_tab(self):
"""
Erstellt den Accounts-Tab.
Diese Methode sollte von Unterklassen überschrieben werden.
"""
return AccountsTab(self.platform_name, self.db_manager, self.language_manager)
def create_settings_tab(self):
"""
Erstellt den Settings-Tab.
Diese Methode sollte von Unterklassen überschrieben werden.
"""
return SettingsTab(
self.platform_name,
self.proxy_rotator,
self.email_handler,
self.language_manager
)
def start_account_creation(self, params):
"""
Startet die Account-Erstellung.
Diese Methode sollte von Unterklassen überschrieben werden.
Args:
params: Parameter für die Account-Erstellung
"""
self.logger.info(f"Account-Erstellung für {self.platform_name} gestartet")
# In Unterklassen implementieren
def validate_inputs(self, inputs):
"""
Validiert die Eingaben für die Account-Erstellung.
Args:
inputs: Eingaben für die Account-Erstellung
Returns:
(bool, str): (Ist gültig, Fehlermeldung falls nicht gültig)
"""
# Basis-Validierungen
if not inputs.get("full_name"):
return False, "Bitte geben Sie einen vollständigen Namen ein."
# Alter prüfen
age_text = inputs.get("age_text", "")
if not age_text:
return False, "Bitte geben Sie ein Alter ein."
# Alter muss eine Zahl sein
try:
age = int(age_text)
inputs["age"] = age # Füge das konvertierte Alter zu den Parametern hinzu
except ValueError:
return False, "Das Alter muss eine ganze Zahl sein."
# Alter-Bereich prüfen
if age < 13 or age > 99:
return False, "Das Alter muss zwischen 13 und 99 liegen."
# Telefonnummer prüfen, falls erforderlich
if inputs.get("registration_method") == "phone" and not inputs.get("phone_number"):
return False, "Telefonnummer erforderlich für Registrierung via Telefon."
return True, ""

Datei anzeigen

@ -0,0 +1,275 @@
"""
Controller für Instagram-spezifische Funktionalität.
Mit TextSimilarity-Integration für robusteres UI-Element-Matching.
"""
import logging
import time
import random
from PyQt5.QtCore import QThread, pyqtSignal, QObject
from controllers.platform_controllers.base_controller import BasePlatformController
from views.tabs.generator_tab import GeneratorTab
from views.tabs.accounts_tab import AccountsTab
from views.tabs.settings_tab import SettingsTab
from social_networks.instagram.instagram_automation import InstagramAutomation
from utils.text_similarity import TextSimilarity
logger = logging.getLogger("instagram_controller")
class InstagramWorkerThread(QThread):
"""Thread für die Instagram-Account-Erstellung."""
# Signale
update_signal = pyqtSignal(str)
log_signal = pyqtSignal(str)
progress_signal = pyqtSignal(int)
finished_signal = pyqtSignal(dict)
error_signal = pyqtSignal(str)
def __init__(self, params):
super().__init__()
self.params = params
self.running = True
# TextSimilarity für robustes Fehler-Matching
self.text_similarity = TextSimilarity(default_threshold=0.7)
# Fehler-Patterns für robustes Fehler-Matching
self.error_patterns = [
"Fehler", "Error", "Fehlgeschlagen", "Failed", "Problem", "Issue",
"Nicht möglich", "Not possible", "Bitte versuchen Sie es erneut",
"Please try again", "Konnte nicht", "Could not", "Timeout"
]
def run(self):
"""Führt die Account-Erstellung aus."""
try:
self.log_signal.emit("Instagram-Account-Erstellung gestartet...")
self.progress_signal.emit(10)
# Instagram-Automation initialisieren
automation = InstagramAutomation(
headless=self.params.get("headless", False),
use_proxy=self.params.get("use_proxy", False),
proxy_type=self.params.get("proxy_type"),
save_screenshots=True,
debug=self.params.get("debug", False),
email_domain=self.params.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com")
)
self.update_signal.emit("Instagram-Automation initialisiert")
self.progress_signal.emit(20)
# Account registrieren
self.log_signal.emit(f"Registriere Account für: {self.params['full_name']}")
registration_method = self.params.get("registration_method", "email")
phone_number = self.params.get("phone_number")
# Account registrieren
result = automation.register_account(
full_name=self.params["full_name"],
age=self.params["age"],
registration_method=registration_method,
phone_number=phone_number,
**self.params.get("additional_params", {})
)
self.progress_signal.emit(100)
if result["success"]:
self.log_signal.emit("Account erfolgreich erstellt!")
self.finished_signal.emit(result)
else:
# Robuste Fehlerbehandlung mit TextSimilarity
error_msg = result.get("error", "Unbekannter Fehler")
# Versuche, Fehler nutzerfreundlicher zu interpretieren
user_friendly_error = self._interpret_error(error_msg)
self.log_signal.emit(f"Fehler bei der Account-Erstellung: {user_friendly_error}")
self.error_signal.emit(user_friendly_error)
except Exception as e:
logger.error(f"Fehler im Worker-Thread: {e}")
self.log_signal.emit(f"Schwerwiegender Fehler: {str(e)}")
self.error_signal.emit(str(e))
self.progress_signal.emit(0)
def _interpret_error(self, error_msg: str) -> str:
"""
Interpretiert Fehlermeldungen und gibt eine benutzerfreundlichere Version zurück.
Verwendet TextSimilarity für robusteres Fehler-Matching.
Args:
error_msg: Die ursprüngliche Fehlermeldung
Returns:
str: Benutzerfreundliche Fehlermeldung
"""
# Bekannte Fehlermuster und deren Interpretationen
error_interpretations = {
"captcha": "Instagram hat einen Captcha-Test angefordert. Versuchen Sie es später erneut oder nutzen Sie einen anderen Proxy.",
"verification": "Es gab ein Problem mit der Verifizierung des Accounts. Bitte prüfen Sie die E-Mail-Einstellungen.",
"proxy": "Problem mit der Proxy-Verbindung. Bitte prüfen Sie Ihre Proxy-Einstellungen.",
"timeout": "Zeitüberschreitung bei der Verbindung. Bitte überprüfen Sie Ihre Internetverbindung.",
"username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig.",
"password": "Das Passwort erfüllt nicht die Anforderungen von Instagram.",
"email": "Die E-Mail-Adresse konnte nicht verwendet werden. Bitte nutzen Sie eine andere E-Mail-Domain.",
"phone": "Die Telefonnummer konnte nicht für die Registrierung verwendet werden."
}
# Versuche, den Fehler zu kategorisieren
for pattern, interpretation in error_interpretations.items():
for error_term in self.error_patterns:
if (pattern in error_msg.lower() or
self.text_similarity.is_similar(error_term, error_msg, threshold=0.7)):
return interpretation
# Fallback: Originale Fehlermeldung zurückgeben
return error_msg
def stop(self):
"""Stoppt den Thread."""
self.running = False
self.terminate()
class InstagramController(BasePlatformController):
"""Controller für Instagram-spezifische Funktionalität."""
def __init__(self, db_manager, proxy_rotator, email_handler, language_manager=None):
super().__init__("Instagram", db_manager, proxy_rotator, email_handler, language_manager)
self.worker_thread = None
# TextSimilarity für robustes UI-Element-Matching
self.text_similarity = TextSimilarity(default_threshold=0.75)
def create_generator_tab(self):
"""Erstellt den Instagram-Generator-Tab."""
generator_tab = GeneratorTab(self.platform_name, self.language_manager)
# Instagram-spezifische Anpassungen
# Diese Methode überschreiben, wenn spezifische Anpassungen benötigt werden
# Signale verbinden
generator_tab.start_requested.connect(self.start_account_creation)
generator_tab.stop_requested.connect(self.stop_account_creation)
return generator_tab
def start_account_creation(self, params):
"""Startet die Instagram-Account-Erstellung."""
super().start_account_creation(params)
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
# Worker-Thread starten
self.worker_thread = InstagramWorkerThread(params)
self.worker_thread.update_signal.connect(lambda msg: generator_tab.set_status(msg))
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.error_signal.connect(lambda msg: (generator_tab.show_error(msg), generator_tab.set_running(False)))
self.worker_thread.finished_signal.connect(lambda result: self.handle_account_created(result))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
def stop_account_creation(self):
"""Stoppt die Instagram-Account-Erstellung."""
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
generator_tab = self.get_generator_tab()
generator_tab.add_log("Account-Erstellung wurde abgebrochen")
generator_tab.set_running(False)
generator_tab.set_progress(0)
def handle_account_created(self, result):
"""Verarbeitet erfolgreich erstellte Accounts."""
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
# Account-Daten aus dem Ergebnis holen
account_data = result.get("account_data", {})
# Account-Erfolgsereignis auslösen
generator_tab.account_created.emit(self.platform_name, account_data)
# Account in der Datenbank speichern
self.save_account_to_db(account_data)
def save_account_to_db(self, account_data):
"""Speichert einen erstellten Account in der Datenbank."""
account = {
"platform": self.platform_name.lower(),
"username": account_data.get("username", ""),
"password": account_data.get("password", ""),
"email": account_data.get("email", ""),
"phone": account_data.get("phone", ""),
"full_name": account_data.get("full_name", ""),
"created_at": time.strftime("%Y-%m-%d %H:%M:%S")
}
self.db_manager.add_account(account)
logger.info(f"Account in Datenbank gespeichert: {account['username']}")
def validate_inputs(self, inputs):
"""
Validiert die Eingaben für die Account-Erstellung.
Verwendet TextSimilarity für robustere Validierung.
"""
# Basis-Validierungen von BasePlatformController verwenden
valid, error_msg = super().validate_inputs(inputs)
if not valid:
return valid, error_msg
# Instagram-spezifische Validierungen
age = inputs.get("age", 0)
if age < 13: # Änderung von 14 auf 13
return False, "Das Alter muss mindestens 13 sein (Instagram-Anforderung)."
# E-Mail-Domain-Validierung
if inputs.get("registration_method") == "email":
email_domain = inputs.get("email_domain", "")
# Blacklist von bekannten problematischen Domains
blacklisted_domains = ["temp-mail.org", "guerrillamail.com", "maildrop.cc"]
# Prüfe mit TextSimilarity auf Ähnlichkeit mit Blacklist
for domain in blacklisted_domains:
if self.text_similarity.is_similar(email_domain, domain, threshold=0.8):
return False, f"Die E-Mail-Domain '{email_domain}' kann problematisch für die Instagram-Registrierung sein. Bitte verwenden Sie eine andere Domain."
return True, ""
def get_form_field_label(self, field_type: str) -> str:
"""
Gibt einen Label-Text für ein Formularfeld basierend auf dem Feldtyp zurück.
Args:
field_type: Typ des Formularfelds
Returns:
str: Label-Text für das Formularfeld
"""
# Mapping von Feldtypen zu Labels
field_labels = {
"full_name": "Vollständiger Name",
"username": "Benutzername",
"password": "Passwort",
"email": "E-Mail-Adresse",
"phone": "Telefonnummer",
"age": "Alter",
"birthday": "Geburtsdatum"
}
return field_labels.get(field_type, field_type.capitalize())

Datei anzeigen

@ -0,0 +1,277 @@
"""
Controller für TikTok-spezifische Funktionalität.
Mit TextSimilarity-Integration für robusteres UI-Element-Matching.
"""
import logging
import time
import random
from PyQt5.QtCore import QThread, pyqtSignal, QObject
from controllers.platform_controllers.base_controller import BasePlatformController
from views.tabs.generator_tab import GeneratorTab
from views.tabs.accounts_tab import AccountsTab
from views.tabs.settings_tab import SettingsTab
from social_networks.tiktok.tiktok_automation import TikTokAutomation
from utils.text_similarity import TextSimilarity
logger = logging.getLogger("tiktok_controller")
class TikTokWorkerThread(QThread):
"""Thread für die TikTok-Account-Erstellung."""
# Signale
update_signal = pyqtSignal(str)
log_signal = pyqtSignal(str)
progress_signal = pyqtSignal(int)
finished_signal = pyqtSignal(dict)
error_signal = pyqtSignal(str)
def __init__(self, params):
super().__init__()
self.params = params
self.running = True
# TextSimilarity für robustes Fehler-Matching
self.text_similarity = TextSimilarity(default_threshold=0.7)
# Fehler-Patterns für robustes Fehler-Matching
self.error_patterns = [
"Fehler", "Error", "Fehlgeschlagen", "Failed", "Problem", "Issue",
"Nicht möglich", "Not possible", "Bitte versuchen Sie es erneut",
"Please try again", "Konnte nicht", "Could not", "Timeout"
]
def run(self):
"""Führt die Account-Erstellung aus."""
try:
self.log_signal.emit("TikTok-Account-Erstellung gestartet...")
self.progress_signal.emit(10)
# TikTok-Automation initialisieren
automation = TikTokAutomation(
headless=self.params.get("headless", False),
use_proxy=self.params.get("use_proxy", False),
proxy_type=self.params.get("proxy_type"),
save_screenshots=True,
debug=self.params.get("debug", False),
email_domain=self.params.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com")
)
self.update_signal.emit("TikTok-Automation initialisiert")
self.progress_signal.emit(20)
# Account registrieren
self.log_signal.emit(f"Registriere Account für: {self.params['full_name']}")
registration_method = self.params.get("registration_method", "email")
phone_number = self.params.get("phone_number")
# Account registrieren
result = automation.register_account(
full_name=self.params["full_name"],
age=self.params["age"],
registration_method=registration_method,
phone_number=phone_number,
**self.params.get("additional_params", {})
)
self.progress_signal.emit(100)
if result["success"]:
self.log_signal.emit("Account erfolgreich erstellt!")
self.finished_signal.emit(result)
else:
# Robuste Fehlerbehandlung mit TextSimilarity
error_msg = result.get("error", "Unbekannter Fehler")
# Versuche, Fehler nutzerfreundlicher zu interpretieren
user_friendly_error = self._interpret_error(error_msg)
self.log_signal.emit(f"Fehler bei der Account-Erstellung: {user_friendly_error}")
self.error_signal.emit(user_friendly_error)
except Exception as e:
logger.error(f"Fehler im Worker-Thread: {e}")
self.log_signal.emit(f"Schwerwiegender Fehler: {str(e)}")
self.error_signal.emit(str(e))
self.progress_signal.emit(0)
def _interpret_error(self, error_msg: str) -> str:
"""
Interpretiert Fehlermeldungen und gibt eine benutzerfreundlichere Version zurück.
Verwendet TextSimilarity für robusteres Fehler-Matching.
Args:
error_msg: Die ursprüngliche Fehlermeldung
Returns:
str: Benutzerfreundliche Fehlermeldung
"""
# Bekannte Fehlermuster und deren Interpretationen
error_interpretations = {
"captcha": "TikTok hat einen Captcha-Test angefordert. Versuchen Sie es später erneut oder nutzen Sie einen anderen Proxy.",
"verification": "Es gab ein Problem mit der Verifizierung des Accounts. Bitte prüfen Sie die E-Mail-Einstellungen.",
"proxy": "Problem mit der Proxy-Verbindung. Bitte prüfen Sie Ihre Proxy-Einstellungen.",
"timeout": "Zeitüberschreitung bei der Verbindung. Bitte überprüfen Sie Ihre Internetverbindung.",
"username": "Der gewählte Benutzername ist bereits vergeben oder nicht zulässig.",
"password": "Das Passwort erfüllt nicht die Anforderungen von TikTok.",
"email": "Die E-Mail-Adresse konnte nicht verwendet werden. Bitte nutzen Sie eine andere E-Mail-Domain.",
"phone": "Die Telefonnummer konnte nicht für die Registrierung verwendet werden.",
"age": "Das eingegebene Alter erfüllt nicht die Anforderungen von TikTok.",
"too_many_attempts": "Zu viele Registrierungsversuche. Bitte warten Sie und versuchen Sie es später erneut."
}
# Versuche, den Fehler zu kategorisieren
for pattern, interpretation in error_interpretations.items():
for error_term in self.error_patterns:
if (pattern in error_msg.lower() or
self.text_similarity.is_similar(error_term, error_msg, threshold=0.7)):
return interpretation
# Fallback: Originale Fehlermeldung zurückgeben
return error_msg
def stop(self):
"""Stoppt den Thread."""
self.running = False
self.terminate()
class TikTokController(BasePlatformController):
"""Controller für TikTok-spezifische Funktionalität."""
def __init__(self, db_manager, proxy_rotator, email_handler, language_manager=None):
super().__init__("TikTok", db_manager, proxy_rotator, email_handler, language_manager)
self.worker_thread = None
# TextSimilarity für robustes UI-Element-Matching
self.text_similarity = TextSimilarity(default_threshold=0.75)
def create_generator_tab(self):
"""Erstellt den TikTok-Generator-Tab."""
generator_tab = GeneratorTab(self.platform_name, self.language_manager)
# TikTok-spezifische Anpassungen
# Diese Methode überschreiben, wenn spezifische Anpassungen benötigt werden
# Signale verbinden
generator_tab.start_requested.connect(self.start_account_creation)
generator_tab.stop_requested.connect(self.stop_account_creation)
return generator_tab
def start_account_creation(self, params):
"""Startet die TikTok-Account-Erstellung."""
super().start_account_creation(params)
# Validiere Eingaben
is_valid, error_msg = self.validate_inputs(params)
if not is_valid:
self.get_generator_tab().show_error(error_msg)
return
# UI aktualisieren
generator_tab = self.get_generator_tab()
generator_tab.set_running(True)
generator_tab.clear_log()
generator_tab.set_progress(0)
# Worker-Thread starten
self.worker_thread = TikTokWorkerThread(params)
self.worker_thread.update_signal.connect(lambda msg: generator_tab.set_status(msg))
self.worker_thread.log_signal.connect(lambda msg: generator_tab.add_log(msg))
self.worker_thread.error_signal.connect(lambda msg: (generator_tab.show_error(msg), generator_tab.set_running(False)))
self.worker_thread.finished_signal.connect(lambda result: self.handle_account_created(result))
self.worker_thread.progress_signal.connect(lambda value: generator_tab.set_progress(value))
self.worker_thread.start()
def stop_account_creation(self):
"""Stoppt die TikTok-Account-Erstellung."""
if self.worker_thread and self.worker_thread.isRunning():
self.worker_thread.stop()
generator_tab = self.get_generator_tab()
generator_tab.add_log("Account-Erstellung wurde abgebrochen")
generator_tab.set_running(False)
generator_tab.set_progress(0)
def handle_account_created(self, result):
"""Verarbeitet erfolgreich erstellte Accounts."""
generator_tab = self.get_generator_tab()
generator_tab.set_running(False)
# Account-Daten aus dem Ergebnis holen
account_data = result.get("account_data", {})
# Account-Erfolgsereignis auslösen
generator_tab.account_created.emit(self.platform_name, account_data)
# Account in der Datenbank speichern
self.save_account_to_db(account_data)
def save_account_to_db(self, account_data):
"""Speichert einen erstellten Account in der Datenbank."""
account = {
"platform": self.platform_name.lower(),
"username": account_data.get("username", ""),
"password": account_data.get("password", ""),
"email": account_data.get("email", ""),
"phone": account_data.get("phone", ""),
"full_name": account_data.get("full_name", ""),
"created_at": time.strftime("%Y-%m-%d %H:%M:%S")
}
self.db_manager.add_account(account)
logger.info(f"Account in Datenbank gespeichert: {account['username']}")
def validate_inputs(self, inputs):
"""
Validiert die Eingaben für die Account-Erstellung.
Verwendet TextSimilarity für robustere Validierung.
"""
# Basis-Validierungen von BasePlatformController verwenden
valid, error_msg = super().validate_inputs(inputs)
if not valid:
return valid, error_msg
# TikTok-spezifische Validierungen
age = inputs.get("age", 0)
if age < 13:
return False, "Das Alter muss mindestens 13 sein (TikTok-Anforderung)."
# E-Mail-Domain-Validierung
if inputs.get("registration_method") == "email":
email_domain = inputs.get("email_domain", "")
# Blacklist von bekannten problematischen Domains
blacklisted_domains = ["temp-mail.org", "guerrillamail.com", "maildrop.cc"]
# Prüfe mit TextSimilarity auf Ähnlichkeit mit Blacklist
for domain in blacklisted_domains:
if self.text_similarity.is_similar(email_domain, domain, threshold=0.8):
return False, f"Die E-Mail-Domain '{email_domain}' kann problematisch für die TikTok-Registrierung sein. Bitte verwenden Sie eine andere Domain."
return True, ""
def get_form_field_label(self, field_type: str) -> str:
"""
Gibt einen Label-Text für ein Formularfeld basierend auf dem Feldtyp zurück.
Args:
field_type: Typ des Formularfelds
Returns:
str: Label-Text für das Formularfeld
"""
# Mapping von Feldtypen zu Labels
field_labels = {
"full_name": "Vollständiger Name",
"username": "Benutzername",
"password": "Passwort",
"email": "E-Mail-Adresse",
"phone": "Telefonnummer",
"age": "Alter",
"birthday": "Geburtsdatum"
}
return field_labels.get(field_type, field_type.capitalize())

Datei anzeigen

@ -0,0 +1,294 @@
"""
Controller für die Verwaltung von Einstellungen.
"""
import logging
import random
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtCore import QObject
logger = logging.getLogger("settings_controller")
class SettingsController(QObject):
"""Controller für die Verwaltung von Einstellungen."""
def __init__(self, proxy_rotator, email_handler, license_manager):
super().__init__()
self.proxy_rotator = proxy_rotator
self.email_handler = email_handler
self.license_manager = license_manager
self.parent_view = None
def set_parent_view(self, view):
"""Setzt die übergeordnete View für Dialoge."""
self.parent_view = view
def load_proxy_settings(self):
"""Lädt die Proxy-Einstellungen."""
try:
proxy_config = self.proxy_rotator.get_config() or {}
settings = {
"ipv4_proxies": proxy_config.get("ipv4", []),
"ipv6_proxies": proxy_config.get("ipv6", []),
"mobile_proxies": proxy_config.get("mobile", []),
"mobile_api": proxy_config.get("mobile_api", {})
}
return settings
except Exception as e:
logger.error(f"Fehler beim Laden der Proxy-Einstellungen: {e}")
return {}
def save_proxy_settings(self, settings):
"""Speichert die Proxy-Einstellungen."""
try:
# IPv4 Proxies
ipv4_proxies = settings.get("ipv4_proxies", [])
if isinstance(ipv4_proxies, str):
ipv4_proxies = [line.strip() for line in ipv4_proxies.splitlines() if line.strip()]
# IPv6 Proxies
ipv6_proxies = settings.get("ipv6_proxies", [])
if isinstance(ipv6_proxies, str):
ipv6_proxies = [line.strip() for line in ipv6_proxies.splitlines() if line.strip()]
# Mobile Proxies
mobile_proxies = settings.get("mobile_proxies", [])
if isinstance(mobile_proxies, str):
mobile_proxies = [line.strip() for line in mobile_proxies.splitlines() if line.strip()]
# API Keys
mobile_api = settings.get("mobile_api", {})
# Konfiguration aktualisieren
self.proxy_rotator.update_config({
"ipv4": ipv4_proxies,
"ipv6": ipv6_proxies,
"mobile": mobile_proxies,
"mobile_api": mobile_api
})
logger.info("Proxy-Einstellungen gespeichert")
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Erfolg",
"Proxy-Einstellungen wurden gespeichert."
)
return True
except Exception as e:
logger.error(f"Fehler beim Speichern der Proxy-Einstellungen: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Proxy-Einstellungen konnten nicht gespeichert werden:\n{str(e)}"
)
return False
def test_proxy(self, proxy_type):
"""Testet einen zufälligen Proxy des ausgewählten Typs."""
try:
# Überprüfe, ob Proxies konfiguriert sind
proxies = self.proxy_rotator.get_proxies_by_type(proxy_type)
if not proxies:
if self.parent_view:
QMessageBox.warning(
self.parent_view,
"Keine Proxies",
f"Keine {proxy_type.upper()}-Proxies konfiguriert.\nBitte fügen Sie Proxies in den Einstellungen hinzu."
)
return False
# Zufälligen Proxy auswählen
proxy = random.choice(proxies)
# Proxy testen
result = self.proxy_rotator.test_proxy(proxy_type)
if result["success"]:
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Proxy-Test erfolgreich",
f"IP: {result['ip']}\nLand: {result['country'] or 'Unbekannt'}\nAntwortzeit: {result['response_time']:.2f}s"
)
return True
else:
if self.parent_view:
QMessageBox.warning(
self.parent_view,
"Proxy-Test fehlgeschlagen",
f"Fehler: {result['error']}"
)
return False
except Exception as e:
logger.error(f"Fehler beim Testen des Proxy: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler beim Testen des Proxy:\n{str(e)}"
)
return False
def load_email_settings(self):
"""Lädt die E-Mail-Einstellungen."""
try:
email_config = self.email_handler.get_config() or {}
settings = {
"imap_server": email_config.get("imap_server", ""),
"imap_port": email_config.get("imap_port", 993),
"imap_user": email_config.get("imap_user", ""),
"imap_pass": email_config.get("imap_pass", "")
}
return settings
except Exception as e:
logger.error(f"Fehler beim Laden der E-Mail-Einstellungen: {e}")
return {}
def save_email_settings(self, settings):
"""Speichert die E-Mail-Einstellungen."""
try:
# Einstellungen aktualisieren
self.email_handler.update_config(settings)
logger.info("E-Mail-Einstellungen gespeichert")
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Erfolg",
"E-Mail-Einstellungen wurden gespeichert."
)
return True
except Exception as e:
logger.error(f"Fehler beim Speichern der E-Mail-Einstellungen: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"E-Mail-Einstellungen konnten nicht gespeichert werden:\n{str(e)}"
)
return False
def test_email(self, settings=None):
"""Testet die E-Mail-Verbindung."""
try:
if settings:
# Temporär Einstellungen aktualisieren
self.email_handler.update_credentials(
settings.get("imap_user", ""),
settings.get("imap_pass", "")
)
self.email_handler.update_server(
settings.get("imap_server", ""),
settings.get("imap_port", 993)
)
# Verbindung testen
result = self.email_handler.test_connection()
if result["success"]:
if self.parent_view:
QMessageBox.information(
self.parent_view,
"E-Mail-Test erfolgreich",
f"Verbindung zu {result['server']}:{result['port']} hergestellt.\nGefundene Postfächer: {result['mailbox_count']}"
)
return True
else:
if self.parent_view:
QMessageBox.warning(
self.parent_view,
"E-Mail-Test fehlgeschlagen",
f"Fehler: {result['error']}"
)
return False
except Exception as e:
logger.error(f"Fehler beim Testen der E-Mail-Verbindung: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler beim Testen der E-Mail-Verbindung:\n{str(e)}"
)
return False
def load_license_info(self):
"""Lädt die Lizenzinformationen."""
try:
license_info = self.license_manager.get_license_info()
return license_info
except Exception as e:
logger.error(f"Fehler beim Laden der Lizenzinformationen: {e}")
return {}
def activate_license(self, license_key):
"""Aktiviert eine Lizenz."""
try:
success, message = self.license_manager.activate_license(license_key)
if success:
if self.parent_view:
QMessageBox.information(
self.parent_view,
"Lizenz aktiviert",
message
)
else:
if self.parent_view:
QMessageBox.warning(
self.parent_view,
"Lizenzaktivierung fehlgeschlagen",
message
)
return success, message
except Exception as e:
logger.error(f"Fehler bei der Lizenzaktivierung: {e}")
if self.parent_view:
QMessageBox.critical(
self.parent_view,
"Fehler",
f"Fehler bei der Lizenzaktivierung:\n{str(e)}"
)
return False, str(e)
def check_license(self):
"""Überprüft, ob eine gültige Lizenz vorhanden ist."""
try:
is_licensed = self.license_manager.is_licensed()
if not is_licensed and self.parent_view:
license_info = self.license_manager.get_license_info()
status = license_info.get("status_text", "Inaktiv")
QMessageBox.warning(
self.parent_view,
"Keine gültige Lizenz",
f"Status: {status}\n\nBitte aktivieren Sie eine Lizenz, um die Software zu nutzen."
)
return is_licensed
except Exception as e:
logger.error(f"Fehler bei der Lizenzprüfung: {e}")
return False

0
database/__init__.py Normale Datei
Datei anzeigen

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

BIN
database/accounts.db Normale Datei

Binäre Datei nicht angezeigt.

480
database/db_manager.py Normale Datei
Datei anzeigen

@ -0,0 +1,480 @@
"""
Datenbankmanager für den Social Media Account Generator.
"""
import os
import json
import sqlite3
import logging
from datetime import datetime
from typing import Dict, List, Any, Optional, Tuple, Union
logger = logging.getLogger("db_manager")
class DatabaseManager:
"""Klasse zur Verwaltung der Datenbank für Account-Informationen."""
def __init__(self, db_path: str = "database/accounts.db"):
"""
Initialisiert den DatabaseManager.
Args:
db_path: Pfad zur Datenbank-Datei
"""
self.db_path = db_path
# Stelle sicher, dass das Datenbankverzeichnis existiert
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
# Datenbank initialisieren
self.init_db()
def init_db(self) -> None:
"""Initialisiert die Datenbank und erstellt die benötigten Tabellen, wenn sie nicht existieren."""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Accounts-Tabelle erstellen
cursor.execute('''
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
platform TEXT NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL,
email TEXT,
phone TEXT,
full_name TEXT,
created_at TEXT NOT NULL,
last_login TEXT,
notes TEXT,
cookies TEXT,
status TEXT
)
''')
# Settings-Tabelle erstellen
cursor.execute('''
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)
''')
conn.commit()
conn.close()
logger.info("Datenbank initialisiert")
except sqlite3.Error as e:
logger.error(f"Fehler bei der Datenbankinitialisierung: {e}")
def add_account(self, account_data: Dict[str, Any]) -> int:
"""
Fügt einen Account zur Datenbank hinzu.
Args:
account_data: Dictionary mit Account-Daten
Returns:
ID des hinzugefügten Accounts oder -1 im Fehlerfall
"""
try:
# Prüfe, ob erforderliche Felder vorhanden sind
required_fields = ["platform", "username", "password"]
for field in required_fields:
if field not in account_data:
logger.error(f"Fehlendes Pflichtfeld: {field}")
return -1
# Sicherstellen, dass created_at vorhanden ist
if "created_at" not in account_data:
account_data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# SQL-Anweisung vorbereiten
fields = ", ".join(account_data.keys())
placeholders = ", ".join(["?" for _ in account_data])
query = f"INSERT INTO accounts ({fields}) VALUES ({placeholders})"
# Anweisung ausführen
cursor.execute(query, list(account_data.values()))
# ID des hinzugefügten Datensatzes abrufen
account_id = cursor.lastrowid
conn.commit()
conn.close()
logger.info(f"Account hinzugefügt: {account_data['username']} (ID: {account_id})")
return account_id
except sqlite3.Error as e:
logger.error(f"Fehler beim Hinzufügen des Accounts: {e}")
return -1
def get_account(self, account_id: int) -> Optional[Dict[str, Any]]:
"""
Gibt einen Account anhand seiner ID zurück.
Args:
account_id: ID des Accounts
Returns:
Dictionary mit Account-Daten oder None, wenn der Account nicht gefunden wurde
"""
try:
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row # Für dict-like Zugriff auf Zeilen
cursor = conn.cursor()
cursor.execute("SELECT * FROM accounts WHERE id = ?", (account_id,))
row = cursor.fetchone()
conn.close()
if row:
# Konvertiere Row in Dictionary
account = dict(row)
logger.debug(f"Account gefunden: {account['username']} (ID: {account_id})")
return account
else:
logger.warning(f"Account nicht gefunden: ID {account_id}")
return None
except sqlite3.Error as e:
logger.error(f"Fehler beim Abrufen des Accounts: {e}")
return None
def get_all_accounts(self) -> List[Dict[str, Any]]:
"""
Gibt alle Accounts zurück.
Returns:
Liste von Dictionaries mit Account-Daten
"""
try:
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT * FROM accounts ORDER BY id DESC")
rows = cursor.fetchall()
conn.close()
# Konvertiere Rows in Dictionaries
accounts = [dict(row) for row in rows]
logger.info(f"{len(accounts)} Accounts abgerufen")
return accounts
except sqlite3.Error as e:
logger.error(f"Fehler beim Abrufen aller Accounts: {e}")
return []
def get_accounts_by_platform(self, platform: str) -> List[Dict[str, Any]]:
"""
Gibt alle Accounts einer bestimmten Plattform zurück.
Args:
platform: Plattformname (z.B. "instagram")
Returns:
Liste von Dictionaries mit Account-Daten
"""
try:
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT * FROM accounts WHERE platform = ? ORDER BY id DESC", (platform.lower(),))
rows = cursor.fetchall()
conn.close()
# Konvertiere Rows in Dictionaries
accounts = [dict(row) for row in rows]
logger.info(f"{len(accounts)} Accounts für Plattform '{platform}' abgerufen")
return accounts
except sqlite3.Error as e:
logger.error(f"Fehler beim Abrufen der Accounts für Plattform '{platform}': {e}")
return []
def update_account(self, account_id: int, update_data: Dict[str, Any]) -> bool:
"""
Aktualisiert einen Account in der Datenbank.
Args:
account_id: ID des zu aktualisierenden Accounts
update_data: Dictionary mit zu aktualisierenden Feldern
Returns:
True bei Erfolg, False im Fehlerfall
"""
if not update_data:
logger.warning("Keine Aktualisierungsdaten bereitgestellt")
return False
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# SQL-Anweisung vorbereiten
set_clause = ", ".join([f"{field} = ?" for field in update_data.keys()])
values = list(update_data.values())
values.append(account_id)
query = f"UPDATE accounts SET {set_clause} WHERE id = ?"
# Anweisung ausführen
cursor.execute(query, values)
conn.commit()
conn.close()
logger.info(f"Account aktualisiert: ID {account_id}")
return True
except sqlite3.Error as e:
logger.error(f"Fehler beim Aktualisieren des Accounts: {e}")
return False
def delete_account(self, account_id: int) -> bool:
"""
Löscht einen Account aus der Datenbank.
Args:
account_id: ID des zu löschenden Accounts
Returns:
True bei Erfolg, False im Fehlerfall
"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM accounts WHERE id = ?", (account_id,))
conn.commit()
conn.close()
logger.info(f"Account gelöscht: ID {account_id}")
return True
except sqlite3.Error as e:
logger.error(f"Fehler beim Löschen des Accounts: {e}")
return False
def search_accounts(self, query: str, platform: Optional[str] = None) -> List[Dict[str, Any]]:
"""
Sucht nach Accounts in der Datenbank.
Args:
query: Suchbegriff
platform: Optional, Plattform für die Einschränkung der Suche
Returns:
Liste von Dictionaries mit gefundenen Account-Daten
"""
try:
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
# Suchbegriff für LIKE-Operator vorbereiten
search_term = f"%{query}%"
if platform:
query_sql = """
SELECT * FROM accounts
WHERE (username LIKE ? OR email LIKE ? OR phone LIKE ? OR full_name LIKE ?)
AND platform = ?
ORDER BY id DESC
"""
cursor.execute(query_sql, (search_term, search_term, search_term, search_term, platform.lower()))
else:
query_sql = """
SELECT * FROM accounts
WHERE username LIKE ? OR email LIKE ? OR phone LIKE ? OR full_name LIKE ?
ORDER BY id DESC
"""
cursor.execute(query_sql, (search_term, search_term, search_term, search_term))
rows = cursor.fetchall()
conn.close()
# Konvertiere Rows in Dictionaries
accounts = [dict(row) for row in rows]
logger.info(f"{len(accounts)} Accounts gefunden für Suchbegriff '{query}'")
return accounts
except sqlite3.Error as e:
logger.error(f"Fehler bei der Suche nach Accounts: {e}")
return []
def get_account_count(self, platform: Optional[str] = None) -> int:
"""
Gibt die Anzahl der Accounts zurück.
Args:
platform: Optional, Plattform für die Einschränkung der Zählung
Returns:
Anzahl der Accounts
"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
if platform:
cursor.execute("SELECT COUNT(*) FROM accounts WHERE platform = ?", (platform.lower(),))
else:
cursor.execute("SELECT COUNT(*) FROM accounts")
count = cursor.fetchone()[0]
conn.close()
return count
except sqlite3.Error as e:
logger.error(f"Fehler beim Zählen der Accounts: {e}")
return 0
def get_setting(self, key: str, default: Any = None) -> Any:
"""
Gibt einen Einstellungswert zurück.
Args:
key: Schlüssel der Einstellung
default: Standardwert, falls die Einstellung nicht gefunden wurde
Returns:
Wert der Einstellung oder der Standardwert
"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT value FROM settings WHERE key = ?", (key,))
row = cursor.fetchone()
conn.close()
if row:
# Versuche, den Wert als JSON zu parsen
try:
return json.loads(row[0])
except json.JSONDecodeError:
# Wenn kein gültiges JSON, gib den Rohwert zurück
return row[0]
else:
return default
except sqlite3.Error as e:
logger.error(f"Fehler beim Abrufen der Einstellung '{key}': {e}")
return default
def set_setting(self, key: str, value: Any) -> bool:
"""
Setzt einen Einstellungswert.
Args:
key: Schlüssel der Einstellung
value: Wert der Einstellung (wird als JSON gespeichert, wenn es kein String ist)
Returns:
True bei Erfolg, False im Fehlerfall
"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Wert als JSON speichern, wenn es kein String ist
if not isinstance(value, str):
value = json.dumps(value)
# Prüfen, ob die Einstellung bereits existiert
cursor.execute("SELECT COUNT(*) FROM settings WHERE key = ?", (key,))
exists = cursor.fetchone()[0] > 0
if exists:
cursor.execute("UPDATE settings SET value = ? WHERE key = ?", (value, key))
else:
cursor.execute("INSERT INTO settings (key, value) VALUES (?, ?)", (key, value))
conn.commit()
conn.close()
logger.info(f"Einstellung gespeichert: {key}")
return True
except sqlite3.Error as e:
logger.error(f"Fehler beim Speichern der Einstellung '{key}': {e}")
return False
def delete_setting(self, key: str) -> bool:
"""
Löscht eine Einstellung.
Args:
key: Schlüssel der zu löschenden Einstellung
Returns:
True bei Erfolg, False im Fehlerfall
"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("DELETE FROM settings WHERE key = ?", (key,))
conn.commit()
conn.close()
logger.info(f"Einstellung gelöscht: {key}")
return True
except sqlite3.Error as e:
logger.error(f"Fehler beim Löschen der Einstellung '{key}': {e}")
return False
def backup_database(self, backup_path: Optional[str] = None) -> bool:
"""
Erstellt ein Backup der Datenbank.
Args:
backup_path: Optional, Pfad für das Backup
Returns:
True bei Erfolg, False im Fehlerfall
"""
if not backup_path:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"database/backup/accounts_{timestamp}.db"
# Stelle sicher, dass das Backup-Verzeichnis existiert
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
try:
# SQLite-Backup-API verwenden
conn = sqlite3.connect(self.db_path)
backup_conn = sqlite3.connect(backup_path)
conn.backup(backup_conn)
conn.close()
backup_conn.close()
logger.info(f"Datenbank-Backup erstellt: {backup_path}")
return True
except sqlite3.Error as e:
logger.error(f"Fehler beim Erstellen des Datenbank-Backups: {e}")
return False

BIN
database/instagram_accounts.db Normale Datei

Binäre Datei nicht angezeigt.

112
database/schema.sql Normale Datei
Datei anzeigen

@ -0,0 +1,112 @@
-- SQLite-Datenbankschema für Instagram Account Generator
-- Accounts-Tabelle
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
email TEXT,
phone TEXT,
full_name TEXT,
created_at TEXT,
notes TEXT,
status TEXT DEFAULT 'active',
proxy_used TEXT,
metadata TEXT
);
-- Proxy-Nutzungen-Tabelle
CREATE TABLE IF NOT EXISTS proxy_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
proxy_type TEXT NOT NULL,
proxy_string TEXT NOT NULL,
used_at TEXT NOT NULL,
success INTEGER DEFAULT 0,
account_id INTEGER,
FOREIGN KEY (account_id) REFERENCES accounts (id)
);
-- Fehler-Protokoll-Tabelle
CREATE TABLE IF NOT EXISTS error_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
error_type TEXT NOT NULL,
error_message TEXT NOT NULL,
stack_trace TEXT,
timestamp TEXT NOT NULL,
account_id INTEGER,
proxy_used TEXT,
FOREIGN KEY (account_id) REFERENCES accounts (id)
);
-- Einstellungen-Tabelle
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- SMS-Verifizierungen-Tabelle
CREATE TABLE IF NOT EXISTS sms_verifications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
phone_number TEXT NOT NULL,
verification_code TEXT,
service_name TEXT NOT NULL DEFAULT 'instagram',
timestamp TEXT NOT NULL,
status TEXT DEFAULT 'pending',
account_id INTEGER,
FOREIGN KEY (account_id) REFERENCES accounts (id)
);
-- E-Mail-Verifizierungen-Tabelle
CREATE TABLE IF NOT EXISTS email_verifications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email_address TEXT NOT NULL,
verification_code TEXT,
service_name TEXT NOT NULL DEFAULT 'instagram',
timestamp TEXT NOT NULL,
status TEXT DEFAULT 'pending',
account_id INTEGER,
FOREIGN KEY (account_id) REFERENCES accounts (id)
);
-- Lizenzen-Tabelle
CREATE TABLE IF NOT EXISTS licenses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
license_key TEXT NOT NULL UNIQUE,
activated_at TEXT,
expires_at TEXT,
status TEXT DEFAULT 'inactive',
hardware_id TEXT,
metadata TEXT
);
-- Nutzungsdaten-Tabelle (für Statistiken)
CREATE TABLE IF NOT EXISTS usage_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
action_type TEXT NOT NULL,
timestamp TEXT NOT NULL,
success INTEGER DEFAULT 0,
details TEXT
);
-- Indizes für bessere Performance
CREATE INDEX IF NOT EXISTS idx_accounts_username ON accounts(username);
CREATE INDEX IF NOT EXISTS idx_accounts_email ON accounts(email);
CREATE INDEX IF NOT EXISTS idx_accounts_status ON accounts(status);
CREATE INDEX IF NOT EXISTS idx_accounts_created_at ON accounts(created_at);
CREATE INDEX IF NOT EXISTS idx_proxy_usage_proxy_type ON proxy_usage(proxy_type);
CREATE INDEX IF NOT EXISTS idx_proxy_usage_used_at ON proxy_usage(used_at);
CREATE INDEX IF NOT EXISTS idx_error_logs_error_type ON error_logs(error_type);
CREATE INDEX IF NOT EXISTS idx_error_logs_timestamp ON error_logs(timestamp);
CREATE INDEX IF NOT EXISTS idx_sms_verifications_phone ON sms_verifications(phone_number);
CREATE INDEX IF NOT EXISTS idx_email_verifications_email ON email_verifications(email_address);
CREATE INDEX IF NOT EXISTS idx_licenses_key ON licenses(license_key);
CREATE INDEX IF NOT EXISTS idx_usage_stats_action_type ON usage_stats(action_type);
CREATE INDEX IF NOT EXISTS idx_usage_stats_timestamp ON usage_stats(timestamp);
-- Beispieldaten für Testzwecke
INSERT OR IGNORE INTO settings (key, value, updated_at)
VALUES ('app_version', '1.0.0', datetime('now'));
INSERT OR IGNORE INTO settings (key, value, updated_at)
VALUES ('last_update_check', datetime('now'), datetime('now'));

0
licensing/__init__.py Normale Datei
Datei anzeigen

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

450
licensing/license_manager.py Normale Datei
Datei anzeigen

@ -0,0 +1,450 @@
"""
Lizenzverwaltungsfunktionalität für den Social Media Account Generator.
"""
import os
import json
import time
import uuid
import hmac
import hashlib
import logging
import requests
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Tuple, Union
logger = logging.getLogger("license_manager")
class LicenseManager:
"""Klasse zur Verwaltung von Softwarelizenzen."""
CONFIG_FILE = os.path.join("config", "license.json")
LICENSE_SERVER_URL = "https://api.example.com/license" # Platzhalter - in der Produktion anpassen
def __init__(self):
"""Initialisiert den LicenseManager und lädt die Konfiguration."""
self.license_data = self.load_license_data()
self.machine_id = self.get_machine_id()
# Stelle sicher, dass das Konfigurationsverzeichnis existiert
os.makedirs(os.path.dirname(self.CONFIG_FILE), exist_ok=True)
# Prüfe die Lizenz beim Start
self.verify_license()
def load_license_data(self) -> Dict[str, Any]:
"""Lädt die Lizenzdaten aus der Konfigurationsdatei."""
if not os.path.exists(self.CONFIG_FILE):
return {
"key": "",
"activation_date": "",
"expiry_date": "",
"status": "inactive",
"status_text": "Keine Lizenz aktiviert",
"features": [],
"last_online_check": "",
"signature": ""
}
try:
with open(self.CONFIG_FILE, "r", encoding="utf-8") as f:
license_data = json.load(f)
logger.info(f"Lizenzdaten geladen: Status '{license_data.get('status', 'unbekannt')}'")
return license_data
except Exception as e:
logger.error(f"Fehler beim Laden der Lizenzdaten: {e}")
return {
"key": "",
"activation_date": "",
"expiry_date": "",
"status": "inactive",
"status_text": "Fehler beim Laden der Lizenz",
"features": [],
"last_online_check": "",
"signature": ""
}
def save_license_data(self) -> bool:
"""Speichert die Lizenzdaten in die Konfigurationsdatei."""
try:
with open(self.CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(self.license_data, f, indent=2)
logger.info("Lizenzdaten gespeichert")
return True
except Exception as e:
logger.error(f"Fehler beim Speichern der Lizenzdaten: {e}")
return False
def get_license_info(self) -> Dict[str, Any]:
"""Gibt die aktuellen Lizenzdaten zurück."""
return self.license_data
def get_machine_id(self) -> str:
"""
Generiert eine eindeutige Maschinen-ID.
Returns:
Eindeutige Maschinen-ID
"""
try:
# Versuche, eine eindeutige Hardware-ID zu generieren
# In der Produktion sollte dies mit einer robusteren Methode ersetzt werden
machine_id_file = os.path.join("config", ".machine_id")
if os.path.exists(machine_id_file):
# Bestehende ID laden
with open(machine_id_file, "r") as f:
return f.read().strip()
else:
# Neue ID generieren
machine_id = str(uuid.uuid4())
with open(machine_id_file, "w") as f:
f.write(machine_id)
return machine_id
except Exception as e:
logger.error(f"Fehler bei der Generierung der Maschinen-ID: {e}")
# Fallback: UUID auf Basis der aktuellen Zeit
return str(uuid.uuid5(uuid.NAMESPACE_DNS, f"fallback-{time.time()}"))
def is_licensed(self) -> bool:
"""
Überprüft, ob eine gültige Lizenz vorhanden ist.
Returns:
True, wenn eine gültige Lizenz vorhanden ist, sonst False
"""
# Prüfe den Status der Lizenz
if self.license_data["status"] not in ["active", "trial"]:
return False
# Prüfe, ob die Lizenz abgelaufen ist
if self.license_data["expiry_date"]:
try:
expiry_date = datetime.fromisoformat(self.license_data["expiry_date"])
if datetime.now() > expiry_date:
logger.warning("Lizenz ist abgelaufen")
self.license_data["status"] = "expired"
self.license_data["status_text"] = "Lizenz abgelaufen"
self.save_license_data()
return False
except Exception as e:
logger.error(f"Fehler beim Parsen des Ablaufdatums: {e}")
return False
# Prüfe, ob regelmäßige Online-Verifizierung erforderlich ist
if self.license_data["last_online_check"]:
try:
last_check = datetime.fromisoformat(self.license_data["last_online_check"])
max_offline_days = 7 # Maximale Tage ohne Online-Check
if datetime.now() > last_check + timedelta(days=max_offline_days):
logger.warning(f"Letzte Online-Überprüfung ist mehr als {max_offline_days} Tage her")
# Versuche, eine Online-Überprüfung durchzuführen
if not self.online_verification():
self.license_data["status"] = "verification_required"
self.license_data["status_text"] = "Online-Überprüfung erforderlich"
self.save_license_data()
return False
except Exception as e:
logger.error(f"Fehler bei der Überprüfung der Online-Verifizierung: {e}")
# Prüfe die Signatur (in der Produktion sollte dies erweitert werden)
if not self.verify_signature():
logger.warning("Ungültige Lizenzsignatur")
self.license_data["status"] = "invalid"
self.license_data["status_text"] = "Ungültige Lizenz (manipuliert)"
self.save_license_data()
return False
return True
def verify_license(self) -> bool:
"""
Überprüft die aktuelle Lizenz.
Returns:
True, wenn die Lizenz gültig ist, sonst False
"""
# Lizenzschlüssel vorhanden?
if not self.license_data["key"]:
logger.info("Kein Lizenzschlüssel vorhanden")
self.license_data["status"] = "inactive"
self.license_data["status_text"] = "Keine Lizenz aktiviert"
self.save_license_data()
return False
return self.is_licensed()
def create_signature(self, data: str) -> str:
"""
Erstellt eine Signatur für die angegebenen Daten.
Args:
data: Zu signierende Daten
Returns:
Signatur als Hexadezimalstring
"""
# In der Produktion sollte ein sicherer Schlüssel verwendet werden
secret_key = "development_secret_key"
# HMAC-SHA256-Signatur erstellen
signature = hmac.new(
secret_key.encode(),
data.encode(),
hashlib.sha256
).hexdigest()
return signature
def verify_signature(self) -> bool:
"""
Überprüft die Signatur der Lizenzdaten.
Returns:
True, wenn die Signatur gültig ist, sonst False
"""
if not self.license_data["signature"]:
return False
# Daten für die Signaturprüfung vorbereiten
data_to_verify = f"{self.license_data['key']}|{self.machine_id}|{self.license_data['activation_date']}|{self.license_data['expiry_date']}"
# Signatur erstellen
computed_signature = self.create_signature(data_to_verify)
# Signatur vergleichen
return computed_signature == self.license_data["signature"]
def online_verification(self) -> bool:
"""
Führt eine Online-Überprüfung der Lizenz durch.
Returns:
True, wenn die Überprüfung erfolgreich war, sonst False
"""
if not self.license_data["key"]:
return False
try:
# Daten für die Lizenzüberprüfung
verification_data = {
"license_key": self.license_data["key"],
"machine_id": self.machine_id,
"product_version": "1.0.0", # In der Produktion aus einer Konfiguration laden
"timestamp": time.time()
}
# Anfrage an den Lizenzserver senden
response = requests.post(
self.LICENSE_SERVER_URL + "/verify",
json=verification_data,
timeout=10
)
if response.status_code == 200:
result = response.json()
if result.get("status") == "active":
# Lizenz ist gültig
logger.info("Online-Lizenzüberprüfung erfolgreich")
# Aktualisiere das Datum der letzten Überprüfung
self.license_data["last_online_check"] = datetime.now().isoformat()
self.save_license_data()
return True
else:
# Lizenz ist ungültig
logger.warning(f"Lizenz ungültig: {result.get('message', 'Unbekannter Fehler')}")
self.license_data["status"] = result.get("status", "invalid")
self.license_data["status_text"] = result.get("message", "Lizenz ungültig")
self.save_license_data()
return False
else:
logger.warning(f"Fehler bei der Online-Überprüfung: HTTP {response.status_code}")
return False
except requests.RequestException as e:
logger.error(f"Netzwerkfehler bei der Online-Überprüfung: {e}")
# Bei Verbindungsproblemen sollte die lokale Lizenz weiterhin gültig bleiben
# In der Produktion kann hier eine Begrenzung der Offline-Zeit implementiert werden
return True
except Exception as e:
logger.error(f"Unerwarteter Fehler bei der Online-Überprüfung: {e}")
return False
def activate_license(self, license_key: str) -> Tuple[bool, str]:
"""
Aktiviert eine Lizenz mit dem angegebenen Schlüssel.
Args:
license_key: Zu aktivierender Lizenzschlüssel
Returns:
(Erfolg, Nachricht)
"""
if not license_key:
return False, "Bitte geben Sie einen Lizenzschlüssel ein."
try:
# In der Produktionsumgebung sollte hier eine Online-Aktivierung erfolgen
# Für Entwicklungszwecke implementieren wir eine einfache lokale Aktivierung
# Simulierte Online-Aktivierung
activation_data = {
"license_key": license_key,
"machine_id": self.machine_id,
"product_version": "1.0.0",
"timestamp": time.time()
}
# Nur für Entwicklung: Prüfe, ob der Lizenzschlüssel bekannt ist
if license_key.startswith("DEV-"):
# Entwicklungslizenzen haben unbegrenzte Laufzeit
expiry_date = (datetime.now() + timedelta(days=365)).isoformat()
activation_response = {
"status": "active",
"message": "Entwicklungslizenz aktiviert",
"activation_date": datetime.now().isoformat(),
"expiry_date": expiry_date,
"features": ["all"]
}
elif license_key.startswith("TRIAL-"):
# Trial-Lizenzen haben begrenzte Laufzeit
expiry_date = (datetime.now() + timedelta(days=30)).isoformat()
activation_response = {
"status": "trial",
"message": "Trial-Lizenz aktiviert (30 Tage)",
"activation_date": datetime.now().isoformat(),
"expiry_date": expiry_date,
"features": ["basic"]
}
else:
# Alle anderen Schlüssel simulieren eine Online-Aktivierung
try:
# Anfrage an den Lizenzserver senden
response = requests.post(
self.LICENSE_SERVER_URL + "/activate",
json=activation_data,
timeout=10
)
if response.status_code == 200:
activation_response = response.json()
else:
logger.warning(f"Fehler bei der Lizenzaktivierung: HTTP {response.status_code}")
return False, f"Fehler bei der Lizenzaktivierung: HTTP {response.status_code}"
except requests.RequestException as e:
logger.error(f"Netzwerkfehler bei der Lizenzaktivierung: {e}")
return False, f"Netzwerkfehler bei der Lizenzaktivierung: {e}"
except Exception as e:
logger.error(f"Unerwarteter Fehler bei der Lizenzaktivierung: {e}")
return False, f"Unerwarteter Fehler bei der Lizenzaktivierung: {e}"
# Lizenzdaten aktualisieren
self.license_data["key"] = license_key
self.license_data["status"] = activation_response.get("status", "inactive")
self.license_data["status_text"] = activation_response.get("message", "Unbekannter Status")
self.license_data["activation_date"] = activation_response.get("activation_date", datetime.now().isoformat())
self.license_data["expiry_date"] = activation_response.get("expiry_date", "")
self.license_data["features"] = activation_response.get("features", [])
self.license_data["last_online_check"] = datetime.now().isoformat()
# Signatur erstellen
data_to_sign = f"{self.license_data['key']}|{self.machine_id}|{self.license_data['activation_date']}|{self.license_data['expiry_date']}"
self.license_data["signature"] = self.create_signature(data_to_sign)
# Lizenzdaten speichern
self.save_license_data()
logger.info(f"Lizenz '{license_key}' erfolgreich aktiviert: {self.license_data['status_text']}")
return True, self.license_data["status_text"]
except Exception as e:
error_msg = f"Fehler bei der Lizenzaktivierung: {e}"
logger.error(error_msg)
return False, error_msg
def deactivate_license(self) -> Tuple[bool, str]:
"""
Deaktiviert die aktuelle Lizenz.
Returns:
(Erfolg, Nachricht)
"""
if not self.license_data["key"]:
return False, "Keine Lizenz aktiviert"
old_key = self.license_data["key"]
try:
# Online-Deaktivierung simulieren
deactivation_data = {
"license_key": self.license_data["key"],
"machine_id": self.machine_id,
"timestamp": time.time()
}
# Anfrage für die Produktionsumgebung
# response = requests.post(
# self.LICENSE_SERVER_URL + "/deactivate",
# json=deactivation_data,
# timeout=10
# )
# Lizenzdaten zurücksetzen
self.license_data = {
"key": "",
"activation_date": "",
"expiry_date": "",
"status": "inactive",
"status_text": "Keine Lizenz aktiviert",
"features": [],
"last_online_check": "",
"signature": ""
}
# Lizenzdaten speichern
self.save_license_data()
logger.info(f"Lizenz '{old_key}' erfolgreich deaktiviert")
return True, "Lizenz erfolgreich deaktiviert"
except Exception as e:
error_msg = f"Fehler bei der Lizenzdeaktivierung: {e}"
logger.error(error_msg)
return False, error_msg
def has_feature(self, feature_name: str) -> bool:
"""
Überprüft, ob die aktuelle Lizenz eine bestimmte Funktion unterstützt.
Args:
feature_name: Name der zu überprüfenden Funktion
Returns:
True, wenn die Funktion unterstützt wird, sonst False
"""
if not self.is_licensed():
return False
# "all" bedeutet, dass alle Funktionen unterstützt werden
if "all" in self.license_data["features"]:
return True
return feature_name in self.license_data["features"]

304
licensing/license_validator.py Normale Datei
Datei anzeigen

@ -0,0 +1,304 @@
"""
Lizenzvalidator - Validiert Lizenzschlüssel und enthält Sicherheitsalgorithmen
"""
import os
import logging
import hashlib
import hmac
import base64
import json
import time
import random
import string
from datetime import datetime, timedelta
from typing import Dict, Optional, Tuple, Any, List
# Konfiguriere Logger
logger = logging.getLogger("license_validator")
class LicenseValidator:
"""
Validiert Lizenzschlüssel und führt kryptografische Operationen durch.
Enthält Platzhaltercode für die Lizenzvalidierung.
"""
# Sicherheitsschlüssel (würde in einer echten Implementierung nicht im Code stehen)
SECRET_KEY = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
def __init__(self):
"""Initialisiert den LicenseValidator."""
logger.info("Lizenzvalidator initialisiert")
def validate_key_format(self, license_key: str) -> bool:
"""
Prüft, ob der Lizenzschlüssel das richtige Format hat.
Args:
license_key: Der zu prüfende Lizenzschlüssel
Returns:
bool: True, wenn das Format gültig ist, False sonst
"""
# Einfacher Formatcheck für XXXXX-XXXXX-XXXXX-XXXXX
import re
return bool(re.match(r'^[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}$', license_key))
def validate_key_checksum(self, license_key: str) -> bool:
"""
Prüft, ob die Prüfsumme des Lizenzschlüssels gültig ist.
Args:
license_key: Der zu prüfende Lizenzschlüssel
Returns:
bool: True, wenn die Prüfsumme gültig ist, False sonst
"""
# Platzhalterimplementierung - in einer echten Implementierung würde hier
# eine Prüfsummenberechnung stehen
# Entferne Bindestriche für die Verarbeitung
key_parts = license_key.split('-')
if len(key_parts) != 4:
return False
# Einfacher Check: Letzter Buchstabe des ersten Teils ist abhängig von den ersten Buchstaben
# der anderen Teile (XOR der ASCII-Werte)
try:
check_char = key_parts[0][-1]
calculated_char = chr(ord(key_parts[1][0]) ^ ord(key_parts[2][0]) ^ ord(key_parts[3][0]))
# In einer echten Implementierung wäre diese Prüfung viel stärker
return check_char == calculated_char
except IndexError:
return False
except Exception as e:
logger.error(f"Fehler bei der Prüfsummenberechnung: {e}")
return False
def decrypt_license_data(self, license_key: str) -> Optional[Dict[str, Any]]:
"""
Entschlüsselt Lizenzinformationen aus dem Schlüssel.
Args:
license_key: Der zu entschlüsselnde Lizenzschlüssel
Returns:
Optional[Dict[str, Any]]: Entschlüsselte Lizenzdaten oder None bei Fehler
"""
# Platzhalterimplementierung - in einer echten Implementierung würde hier
# eine Entschlüsselung stehen
if not self.validate_key_format(license_key):
return None
# Mock-Daten generieren
key_parts = license_key.split('-')
# Aus dem Schlüssel Informationen "ableiten"
try:
# Verwende den ersten Teil für die Lizenzart
license_type_index = sum(ord(c) for c in key_parts[0]) % 3
license_types = ["basic", "premium", "enterprise"]
license_type = license_types[license_type_index]
# Verwende den zweiten Teil für die Gültigkeitsdauer
validity_months = (sum(ord(c) for c in key_parts[1]) % 12) + 1
# Verwende den dritten Teil für die Funktionen
features_count = (sum(ord(c) for c in key_parts[2]) % 5) + 1
all_features = ["multi_account", "proxy_rotation", "advanced_analytics",
"sms_verification", "captcha_solving", "phone_verification",
"export", "scheduling"]
features = all_features[:features_count]
# Generiere ein "verschlüsseltes" Token
token = hashlib.sha256(license_key.encode()).hexdigest()
# Aktuelle Zeit für Aktivierung
now = datetime.now()
activation_date = now.strftime("%Y-%m-%d %H:%M:%S")
expiry_date = (now + timedelta(days=30*validity_months)).strftime("%Y-%m-%d %H:%M:%S")
# Lizenzdaten zusammenstellen
license_data = {
"license_type": license_type,
"features": features,
"activation_date": activation_date,
"expiry_date": expiry_date,
"token": token
}
return license_data
except Exception as e:
logger.error(f"Fehler bei der Entschlüsselung des Lizenzschlüssels: {e}")
return None
def generate_license_key(self, license_type: str = "basic", validity_months: int = 12,
features: List[str] = None) -> str:
"""
Generiert einen Lizenzschlüssel.
Args:
license_type: Art der Lizenz ("basic", "premium", "enterprise")
validity_months: Gültigkeitsdauer in Monaten
features: Liste der Funktionen
Returns:
str: Generierter Lizenzschlüssel
"""
# Platzhalterimplementierung - in einer echten Implementierung würde hier
# eine sichere Schlüsselgenerierung stehen
# Verwende die Eingabeparameter als Seed für die Generierung
seed = f"{license_type}{validity_months}{','.join(features or [])}{time.time()}"
random.seed(hashlib.md5(seed.encode()).hexdigest())
# Generiere 4 Teile mit jeweils 5 Zeichen (Großbuchstaben und Zahlen)
chars = string.ascii_uppercase + string.digits
parts = []
for _ in range(4):
part = ''.join(random.choice(chars) for _ in range(5))
parts.append(part)
# Stelle sicher, dass der letzte Buchstabe des ersten Teils
# ein XOR der ersten Buchstaben der anderen Teile ist
# (für die einfache Prüfsumme)
calc_char = chr(ord(parts[1][0]) ^ ord(parts[2][0]) ^ ord(parts[3][0]))
parts[0] = parts[0][:-1] + calc_char
# Verbinde die Teile mit Bindestrichen
license_key = '-'.join(parts)
return license_key
def sign_data(self, data: str) -> str:
"""
Signiert Daten mit dem geheimen Schlüssel.
Args:
data: Zu signierende Daten
Returns:
str: Signatur
"""
return hmac.new(
self.SECRET_KEY.encode(),
data.encode(),
hashlib.sha256
).hexdigest()
def verify_signature(self, data: str, signature: str) -> bool:
"""
Überprüft die Signatur von Daten.
Args:
data: Signierte Daten
signature: Zu überprüfende Signatur
Returns:
bool: True, wenn die Signatur gültig ist, False sonst
"""
expected_signature = self.sign_data(data)
return hmac.compare_digest(expected_signature, signature)
def encode_license_data(self, data: Dict[str, Any]) -> str:
"""
Kodiert Lizenzdaten zur sicheren Übertragung.
Args:
data: Zu kodierende Lizenzdaten
Returns:
str: Kodierte Lizenzdaten
"""
# Daten in JSON konvertieren
json_data = json.dumps(data, sort_keys=True)
# Signatur hinzufügen
signature = self.sign_data(json_data)
# Zusammen mit der Signatur kodieren
combined = f"{json_data}|{signature}"
encoded = base64.b64encode(combined.encode()).decode()
return encoded
def decode_license_data(self, encoded: str) -> Optional[Dict[str, Any]]:
"""
Dekodiert und überprüft kodierte Lizenzdaten.
Args:
encoded: Kodierte Lizenzdaten
Returns:
Optional[Dict[str, Any]]: Dekodierte Lizenzdaten oder None bei Fehler
"""
try:
# Dekodieren
decoded = base64.b64decode(encoded).decode()
# In Daten und Signatur aufteilen
json_data, signature = decoded.split('|', 1)
# Signatur überprüfen
if not self.verify_signature(json_data, signature):
logger.warning("Ungültige Signatur in lizenzierten Daten")
return None
# JSON parsen
data = json.loads(json_data)
return data
except Exception as e:
logger.error(f"Fehler beim Dekodieren der Lizenzdaten: {e}")
return None
# Beispielnutzung, wenn direkt ausgeführt
if __name__ == "__main__":
# Konfiguriere Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Beispiel für LicenseValidator
validator = LicenseValidator()
# Generiere einen Lizenzschlüssel
features = ["multi_account", "proxy_rotation", "advanced_analytics"]
key = validator.generate_license_key("premium", 12, features)
print(f"Generierter Lizenzschlüssel: {key}")
# Validiere den Schlüssel
is_valid_format = validator.validate_key_format(key)
is_valid_checksum = validator.validate_key_checksum(key)
print(f"Format gültig: {is_valid_format}")
print(f"Prüfsumme gültig: {is_valid_checksum}")
# Entschlüssele Lizenzdaten
license_data = validator.decrypt_license_data(key)
if license_data:
print("\nEntschlüsselte Lizenzdaten:")
for k, v in license_data.items():
print(f" {k}: {v}")
# Beispiel für Kodierung und Dekodierung
test_data = {
"name": "Test License",
"type": "premium",
"expires": "2026-01-01"
}
encoded = validator.encode_license_data(test_data)
print(f"\nKodierte Daten: {encoded}")
decoded = validator.decode_license_data(encoded)
if decoded:
print("\nDekodierte Daten:")
for k, v in decoded.items():
print(f" {k}: {v}")

0
localization/__init__.py Normale Datei
Datei anzeigen

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -0,0 +1,272 @@
# Path: localization/language_manager.py
"""
Sprachmanager für die Übersetzung der Benutzeroberfläche.
"""
import os
import json
import logging
import time
from typing import Dict, Any, Optional
from PyQt5.QtCore import QObject, pyqtSignal, QSettings
logger = logging.getLogger("language_manager")
class LanguageManager(QObject):
"""Verwaltet die Sprachen und Übersetzungen der Anwendung."""
# Signal, das ausgelöst wird, wenn sich die Sprache ändert
language_changed = pyqtSignal(str)
# Signal, das den Status des Sprachwechsels anzeigt (True = läuft, False = abgeschlossen)
language_change_status = pyqtSignal(bool)
def __init__(self, app=None):
"""
Initialisiert den Sprachmanager.
Args:
app: Die QApplication-Instanz
"""
super().__init__()
self.app = app
self.current_language = "de" # Standard ist Deutsch
self.translations = {}
self.available_languages = {}
self.last_change_time = 0 # Zeitpunkt des letzten Sprachwechsels
self.change_cooldown = 0.5 # Cooldown in Sekunden zwischen Sprachwechseln
self.is_changing_language = False # Status-Variable für laufenden Sprachwechsel
# Basisverzeichnis für Sprachdateien ermitteln
self.base_dir = os.path.dirname(os.path.abspath(__file__))
self.languages_dir = os.path.join(self.base_dir, "languages")
# Verfügbare Sprachen ermitteln und laden
self._discover_languages()
# Lade die gespeicherte Sprache, falls vorhanden
self.settings = QSettings("Chimaira", "SocialMediaAccountGenerator")
saved_language = self.settings.value("language", "de")
if saved_language in self.available_languages:
self.current_language = saved_language
self._load_language(self.current_language)
logger.info(f"Sprachmanager initialisiert mit Sprache: {self.current_language}")
def _discover_languages(self):
"""Ermittelt die verfügbaren Sprachen aus den Sprachdateien."""
self.available_languages = {}
try:
# Alle JSON-Dateien im Sprachverzeichnis suchen
language_files = []
for filename in os.listdir(self.languages_dir):
if filename.endswith(".json"):
language_code = filename.split(".")[0]
language_path = os.path.join(self.languages_dir, filename)
language_files.append((language_code, language_path))
# Definierte Reihenfolge der Sprachen
ordered_codes = ["de", "en", "fr", "es", "ja"]
# Sortierte Liste erstellen
for code in ordered_codes:
for language_code, language_path in language_files:
if language_code == code:
# Sprachinformationen aus der Datei lesen
try:
with open(language_path, 'r', encoding='utf-8') as file:
language_data = json.load(file)
language_name = language_data.get("language_name", language_code)
self.available_languages[language_code] = {
"name": language_name,
"path": language_path
}
except Exception as e:
logger.error(f"Fehler beim Laden der Sprachinformationen für {language_code}: {e}")
# Eventuelle restliche Sprachen hinzufügen
for language_code, language_path in language_files:
if language_code not in self.available_languages:
try:
with open(language_path, 'r', encoding='utf-8') as file:
language_data = json.load(file)
language_name = language_data.get("language_name", language_code)
self.available_languages[language_code] = {
"name": language_name,
"path": language_path
}
except Exception as e:
logger.error(f"Fehler beim Laden der Sprachinformationen für {language_code}: {e}")
logger.info(f"Verfügbare Sprachen: {', '.join(self.available_languages.keys())}")
except Exception as e:
logger.error(f"Fehler beim Ermitteln der verfügbaren Sprachen: {e}")
def _load_language(self, language_code: str) -> bool:
"""
Lädt die Übersetzungen für eine Sprache.
Args:
language_code: Der Sprachcode (z.B. "de", "en")
Returns:
bool: True bei Erfolg, False bei Fehler
"""
if language_code not in self.available_languages:
logger.error(f"Sprache {language_code} nicht verfügbar")
return False
try:
language_path = self.available_languages[language_code]["path"]
with open(language_path, 'r', encoding='utf-8') as file:
self.translations = json.load(file)
self.current_language = language_code
logger.info(f"Sprache {language_code} geladen")
# Signal auslösen, dass sich die Sprache geändert hat
self.language_changed.emit(language_code)
return True
except Exception as e:
logger.error(f"Fehler beim Laden der Sprache {language_code}: {e}")
return False
def get_text(self, key: str, default: str = None) -> str:
"""
Gibt den übersetzten Text für einen Schlüssel zurück.
Args:
key: Der Schlüssel für den Text (z.B. "main.title")
default: Der Standardtext, falls der Schlüssel nicht gefunden wurde
Returns:
str: Der übersetzte Text oder der Standardtext
"""
# Wenn kein Standardtext angegeben wurde, verwende den Schlüssel
if default is None:
default = key
# Versuche, den Text aus den Übersetzungen zu holen
try:
# Unterstütze verschachtelte Schlüssel mit Punktnotation
parts = key.split('.')
result = self.translations
for part in parts:
if part in result:
result = result[part]
else:
return default
# Wenn das Ergebnis ein unterstützter Datentyp ist, gib es zurück
if isinstance(result, (str, list, dict)):
return result
else:
logger.warning(
f"Schlüssel {key} hat unerwarteten Typ: {type(result)} - {result}"
)
return default
except Exception as e:
logger.warning(f"Fehler beim Abrufen des Textes für Schlüssel {key}: {e}")
return default
def change_language(self, language_code: str) -> bool:
"""
Ändert die aktive Sprache.
Args:
language_code: Der Sprachcode (z.B. "de", "en")
Returns:
bool: True bei Erfolg, False bei Fehler
"""
# Wenn bereits ein Sprachwechsel läuft, blockiere weitere Wechsel
if self.is_changing_language:
logger.debug(f"Ein Sprachwechsel läuft bereits, ignoriere Wechsel zu {language_code}")
return False
# Cooldown prüfen
current_time = time.time()
if current_time - self.last_change_time < self.change_cooldown:
logger.debug("Sprachwechsel zu schnell hintereinander")
return False
# Wenn es die gleiche Sprache ist, nichts tun
if language_code == self.current_language:
return True
# Status auf "Sprachwechsel läuft" setzen
self.is_changing_language = True
self.language_change_status.emit(True)
# Versuche die Sprache zu wechseln
success = self._load_language(language_code)
if success:
# Sprache in Einstellungen speichern
self.settings.setValue("language", language_code)
self.settings.sync()
# Aktualisiere den Zeitpunkt des letzten Sprachwechsels
self.last_change_time = time.time()
# Nach einer kurzen Verzögerung den Status zurücksetzen
# um sicherzustellen, dass die UI-Aktualisierung abgeschlossen ist
from PyQt5.QtCore import QTimer
QTimer.singleShot(500, self._reset_change_status)
return success
def _reset_change_status(self):
"""Setzt den Status des Sprachwechsels zurück."""
self.is_changing_language = False
self.language_change_status.emit(False)
logger.debug("Sprachwechsel-Status zurückgesetzt")
def get_current_language(self) -> str:
"""Gibt den Code der aktuellen Sprache zurück."""
return self.current_language
def get_language_name(self, language_code: str = None) -> str:
"""
Gibt den Namen einer Sprache zurück.
Args:
language_code: Der Sprachcode oder None für die aktuelle Sprache
Returns:
str: Der Name der Sprache
"""
if language_code is None:
language_code = self.current_language
if language_code in self.available_languages:
return self.available_languages[language_code].get("name", language_code)
else:
return language_code
def get_available_languages(self) -> Dict[str, str]:
"""
Gibt eine Liste der verfügbaren Sprachen zurück.
Returns:
Dict[str, str]: Ein Dictionary mit Sprachcodes als Schlüssel und Sprachnamen als Werten
"""
return {code: info.get("name", code) for code, info in self.available_languages.items()}
def is_language_change_in_progress(self) -> bool:
"""
Gibt zurück, ob gerade ein Sprachwechsel im Gange ist.
Returns:
bool: True wenn ein Sprachwechsel läuft, False sonst
"""
return self.is_changing_language

102
localization/languages/de.json Normale Datei
Datei anzeigen

@ -0,0 +1,102 @@
{
"language_name": "Deutsch",
"main": {
"title": "Social Media Account Generator",
"subtitle": "Wählen Sie eine Plattform",
"version": "Version 1.0.0",
"overview": "Übersicht"
},
"buttons": {
"back": "↩ Zurück",
"create": "Account erstellen",
"cancel": "Abbrechen",
"refresh": "Aktualisieren",
"export": "Exportieren",
"delete": "Löschen",
"test_proxy": "Proxy testen",
"save_proxy": "Proxy-Einstellungen speichern",
"test_email": "E-Mail testen",
"save_email": "E-Mail-Einstellungen speichern",
"activate_license": "Lizenz aktivieren",
"check_updates": "Auf Updates prüfen",
"ok": "OK"
},
"tabs": {
"generator": "Account Generator",
"accounts": "Konten",
"settings": "Einstellungen",
"about": "Über"
},
"menu": {
"about": "Über",
"about_app": "Über die Anwendung",
"language": "Sprache"
},
"status": {
"ready": "Bereit"
},
"generator_tab": {
"form_title": "Account-Informationen",
"first_name_label": "Vorname:",
"first_name_placeholder": "z.B. Max",
"last_name_label": "Nachname:",
"last_name_placeholder": "z.B. Mustermann",
"age_label": "Alter:",
"age_placeholder": "Alter zwischen 13 und 99",
"registration_method_label": "Registrierungsmethode:",
"email_radio": "E-Mail",
"phone_radio": "Telefon",
"phone_label": "Telefonnummer:",
"phone_placeholder": "z.B. +49123456789",
"email_domain_label": "E-Mail-Domain:",
"proxy_use": "Proxy verwenden",
"proxy_label": "Proxy:",
"proxy_type_label": "Typ:",
"proxy_type_ipv4": "IPv4",
"proxy_type_ipv6": "IPv6",
"proxy_type_mobile": "Mobile",
"headless": "Browser im Hintergrund ausführen",
"debug": "Debug-Modus (detaillierte Protokollierung)",
"log_title": "Log",
"error_title": "Fehler",
"first_name_error": "Bitte geben Sie einen Vornamen ein.",
"last_name_error": "Bitte geben Sie einen Nachnamen ein.",
"age_empty_error": "Bitte geben Sie ein Alter ein.",
"age_int_error": "Das Alter muss eine ganze Zahl sein.",
"age_range_error": "Das Alter muss zwischen 13 und 99 liegen.",
"phone_error": "Bitte geben Sie eine Telefonnummer ein.",
"tiktok_category_label": "Kategorie/Nische:",
"tiktok_category_general": "Allgemein",
"tiktok_category_gaming": "Gaming",
"tiktok_category_fashion": "Mode",
"tiktok_category_fitness": "Fitness",
"tiktok_category_travel": "Reisen",
"tiktok_category_cooking": "Kochen",
"tiktok_category_technology": "Technologie",
"tiktok_category_education": "Bildung"
},
"accounts_tab": {
"headers": [
"ID",
"Benutzername",
"Passwort",
"E-Mail",
"Handynummer",
"Name",
"Plattform",
"Erstellt am"
],
"no_selection_title": "Kein Konto ausgewählt",
"no_selection_text": "Bitte wählen Sie ein Konto zum Löschen aus.",
"delete_title": "Konto löschen",
"delete_text": "Möchten Sie das Konto '{username}' wirklich löschen?",
"delete_success_title": "Erfolg",
"delete_success_text": "Konto '{username}' wurde gelöscht.",
"delete_error_title": "Fehler",
"delete_error_text": "Konto '{username}' konnte nicht gelöscht werden."
},
"about_dialog": {
"support": "Für Support kontaktieren Sie uns unter: support@example.com",
"license": "Diese Software ist lizenzpflichtig und darf nur mit gültiger Lizenz verwendet werden."
}
}

102
localization/languages/en.json Normale Datei
Datei anzeigen

@ -0,0 +1,102 @@
{
"language_name": "English",
"main": {
"title": "Social Media Account Generator",
"subtitle": "Select a platform",
"version": "Version 1.0.0",
"overview": "Overview"
},
"buttons": {
"back": "↩ Back",
"create": "Create Account",
"cancel": "Cancel",
"refresh": "Refresh",
"export": "Export",
"delete": "Delete",
"test_proxy": "Test Proxy",
"save_proxy": "Save Proxy Settings",
"test_email": "Test Email",
"save_email": "Save Email Settings",
"activate_license": "Activate License",
"check_updates": "Check for Updates",
"ok": "OK"
},
"tabs": {
"generator": "Account Generator",
"accounts": "Accounts",
"settings": "Settings",
"about": "About"
},
"menu": {
"about": "About",
"about_app": "About the Application",
"language": "Language"
},
"status": {
"ready": "Ready"
},
"generator_tab": {
"form_title": "Account Information",
"first_name_label": "First Name:",
"first_name_placeholder": "e.g. Max",
"last_name_label": "Last Name:",
"last_name_placeholder": "e.g. Mustermann",
"age_label": "Age:",
"age_placeholder": "Age between 13 and 99",
"registration_method_label": "Registration Method:",
"email_radio": "Email",
"phone_radio": "Phone",
"phone_label": "Phone Number:",
"phone_placeholder": "e.g. +49123456789",
"email_domain_label": "Email Domain:",
"proxy_use": "Use Proxy",
"proxy_label": "Proxy:",
"proxy_type_label": "Type:",
"proxy_type_ipv4": "IPv4",
"proxy_type_ipv6": "IPv6",
"proxy_type_mobile": "Mobile",
"headless": "Run browser headless",
"debug": "Debug mode (detailed logging)",
"log_title": "Log",
"error_title": "Error",
"first_name_error": "Please enter a first name.",
"last_name_error": "Please enter a last name.",
"age_empty_error": "Please enter an age.",
"age_int_error": "Age must be a whole number.",
"age_range_error": "Age must be between 13 and 99.",
"phone_error": "Please enter a phone number.",
"tiktok_category_label": "Category/Niche:",
"tiktok_category_general": "General",
"tiktok_category_gaming": "Gaming",
"tiktok_category_fashion": "Fashion",
"tiktok_category_fitness": "Fitness",
"tiktok_category_travel": "Travel",
"tiktok_category_cooking": "Cooking",
"tiktok_category_technology": "Technology",
"tiktok_category_education": "Education"
},
"accounts_tab": {
"headers": [
"ID",
"Username",
"Password",
"Email",
"Phone",
"Name",
"Platform",
"Created At"
],
"no_selection_title": "No Account Selected",
"no_selection_text": "Please select an account to delete.",
"delete_title": "Delete Account",
"delete_text": "Do you really want to delete the account '{username}'?",
"delete_success_title": "Success",
"delete_success_text": "Account '{username}' was deleted.",
"delete_error_title": "Error",
"delete_error_text": "Account '{username}' could not be deleted."
},
"about_dialog": {
"support": "For support contact us at: support@example.com",
"license": "This software is licensed and may only be used with a valid license."
}
}

102
localization/languages/es.json Normale Datei
Datei anzeigen

@ -0,0 +1,102 @@
{
"language_name": "Español",
"main": {
"title": "Generador de Cuentas de Redes Sociales",
"subtitle": "Seleccione una plataforma",
"version": "Versión 1.0.0",
"overview": "Resumen"
},
"buttons": {
"back": "↩ Volver",
"create": "Crear cuenta",
"cancel": "Cancelar",
"refresh": "Actualizar",
"export": "Exportar",
"delete": "Eliminar",
"test_proxy": "Probar proxy",
"save_proxy": "Guardar ajustes de proxy",
"test_email": "Probar correo",
"save_email": "Guardar ajustes de correo",
"activate_license": "Activar licencia",
"check_updates": "Buscar actualizaciones",
"ok": "Aceptar"
},
"tabs": {
"generator": "Generador de Cuentas",
"accounts": "Cuentas",
"settings": "Configuración",
"about": "Acerca de"
},
"menu": {
"about": "Acerca de",
"about_app": "Acerca de la aplicación",
"language": "Idioma"
},
"status": {
"ready": "Listo"
},
"generator_tab": {
"form_title": "Información de la cuenta",
"first_name_label": "Nombre:",
"first_name_placeholder": "p.ej. Max",
"last_name_label": "Apellido:",
"last_name_placeholder": "p.ej. Mustermann",
"age_label": "Edad:",
"age_placeholder": "Edad entre 13 y 99",
"registration_method_label": "Método de registro:",
"email_radio": "Correo electrónico",
"phone_radio": "Teléfono",
"phone_label": "Número de teléfono:",
"phone_placeholder": "p.ej. +49123456789",
"email_domain_label": "Dominio de correo:",
"proxy_use": "Usar proxy",
"proxy_label": "Proxy:",
"proxy_type_label": "Tipo:",
"proxy_type_ipv4": "IPv4",
"proxy_type_ipv6": "IPv6",
"proxy_type_mobile": "Móvil",
"headless": "Ejecutar navegador en segundo plano",
"debug": "Modo depuración (registro detallado)",
"log_title": "Registro",
"error_title": "Error",
"first_name_error": "Por favor, introduzca un nombre.",
"last_name_error": "Por favor, introduzca un apellido.",
"age_empty_error": "Por favor, introduzca una edad.",
"age_int_error": "La edad debe ser un número entero.",
"age_range_error": "La edad debe estar entre 13 y 99.",
"phone_error": "Por favor, introduzca un número de teléfono.",
"tiktok_category_label": "Categoría/Nicho:",
"tiktok_category_general": "General",
"tiktok_category_gaming": "Juegos",
"tiktok_category_fashion": "Moda",
"tiktok_category_fitness": "Fitness",
"tiktok_category_travel": "Viajes",
"tiktok_category_cooking": "Cocina",
"tiktok_category_technology": "Tecnología",
"tiktok_category_education": "Educación"
},
"accounts_tab": {
"headers": [
"ID",
"Nombre de usuario",
"Contraseña",
"Correo",
"Teléfono",
"Nombre",
"Plataforma",
"Creado el"
],
"no_selection_title": "Ninguna cuenta seleccionada",
"no_selection_text": "Seleccione una cuenta para eliminar.",
"delete_title": "Eliminar cuenta",
"delete_text": "¿Realmente desea eliminar la cuenta '{username}'?",
"delete_success_title": "Éxito",
"delete_success_text": "La cuenta '{username}' fue eliminada.",
"delete_error_title": "Error",
"delete_error_text": "No se pudo eliminar la cuenta '{username}'."
},
"about_dialog": {
"support": "Para soporte contáctenos en: support@example.com",
"license": "Este software está sujeto a licencia y solo puede utilizarse con una licencia válida."
}
}

102
localization/languages/fr.json Normale Datei
Datei anzeigen

@ -0,0 +1,102 @@
{
"language_name": "Français",
"main": {
"title": "Générateur de Comptes de Médias Sociaux",
"subtitle": "Sélectionnez une plateforme",
"version": "Version 1.0.0",
"overview": "Aperçu"
},
"buttons": {
"back": "↩ Retour",
"create": "Créer un compte",
"cancel": "Annuler",
"refresh": "Rafraîchir",
"export": "Exporter",
"delete": "Supprimer",
"test_proxy": "Tester le proxy",
"save_proxy": "Enregistrer le proxy",
"test_email": "Tester l'e-mail",
"save_email": "Enregistrer l'e-mail",
"activate_license": "Activer la licence",
"check_updates": "Vérifier les mises à jour",
"ok": "OK"
},
"tabs": {
"generator": "Générateur de Compte",
"accounts": "Comptes",
"settings": "Paramètres",
"about": "À Propos"
},
"menu": {
"about": "À propos",
"about_app": "À propos de l'application",
"language": "Langue"
},
"status": {
"ready": "Prêt"
},
"generator_tab": {
"form_title": "Informations du compte",
"first_name_label": "Prénom:",
"first_name_placeholder": "ex. Max",
"last_name_label": "Nom:",
"last_name_placeholder": "ex. Mustermann",
"age_label": "Âge:",
"age_placeholder": "Âge entre 13 et 99",
"registration_method_label": "Méthode d'enregistrement:",
"email_radio": "E-mail",
"phone_radio": "Téléphone",
"phone_label": "Numéro de téléphone:",
"phone_placeholder": "ex. +49123456789",
"email_domain_label": "Domaine e-mail:",
"proxy_use": "Utiliser un proxy",
"proxy_label": "Proxy:",
"proxy_type_label": "Type:",
"proxy_type_ipv4": "IPv4",
"proxy_type_ipv6": "IPv6",
"proxy_type_mobile": "Mobile",
"headless": "Exécuter le navigateur en arrière-plan",
"debug": "Mode débogage (journal détaillé)",
"log_title": "Journal",
"error_title": "Erreur",
"first_name_error": "Veuillez saisir un prénom.",
"last_name_error": "Veuillez saisir un nom.",
"age_empty_error": "Veuillez saisir un âge.",
"age_int_error": "L'âge doit être un nombre entier.",
"age_range_error": "L'âge doit être compris entre 13 et 99.",
"phone_error": "Veuillez saisir un numéro de téléphone.",
"tiktok_category_label": "Catégorie/Niche:",
"tiktok_category_general": "Général",
"tiktok_category_gaming": "Jeux",
"tiktok_category_fashion": "Mode",
"tiktok_category_fitness": "Fitness",
"tiktok_category_travel": "Voyage",
"tiktok_category_cooking": "Cuisine",
"tiktok_category_technology": "Technologie",
"tiktok_category_education": "Éducation"
},
"accounts_tab": {
"headers": [
"ID",
"Nom d'utilisateur",
"Mot de passe",
"E-mail",
"Téléphone",
"Nom",
"Plateforme",
"Créé le"
],
"no_selection_title": "Aucun compte sélectionné",
"no_selection_text": "Veuillez sélectionner un compte à supprimer.",
"delete_title": "Supprimer le compte",
"delete_text": "Voulez-vous vraiment supprimer le compte '{username}' ?",
"delete_success_title": "Succès",
"delete_success_text": "Le compte '{username}' a été supprimé.",
"delete_error_title": "Erreur",
"delete_error_text": "Le compte '{username}' n'a pas pu être supprimé."
},
"about_dialog": {
"support": "Pour toute assistance, contactez-nous à : support@example.com",
"license": "Ce logiciel est soumis à licence et ne peut être utilisé qu'avec une licence valide."
}
}

102
localization/languages/ja.json Normale Datei
Datei anzeigen

@ -0,0 +1,102 @@
{
"language_name": "日本語",
"main": {
"title": "ソーシャルメディアアカウントジェネレーター",
"subtitle": "プラットフォームを選択",
"version": "バージョン 1.0.0",
"overview": "概要"
},
"buttons": {
"back": "↩ 戻る",
"create": "アカウント作成",
"cancel": "キャンセル",
"refresh": "更新",
"export": "エクスポート",
"delete": "削除",
"test_proxy": "プロキシテスト",
"save_proxy": "プロキシ設定を保存",
"test_email": "メールをテスト",
"save_email": "メール設定を保存",
"activate_license": "ライセンスを有効化",
"check_updates": "アップデートを確認",
"ok": "OK"
},
"tabs": {
"generator": "アカウント生成",
"accounts": "アカウント",
"settings": "設定",
"about": "情報"
},
"menu": {
"about": "情報",
"about_app": "アプリについて",
"language": "言語"
},
"status": {
"ready": "完了"
},
"generator_tab": {
"form_title": "アカウント情報",
"first_name_label": "名:",
"first_name_placeholder": "例: Max",
"last_name_label": "姓:",
"last_name_placeholder": "例: Mustermann",
"age_label": "年齢:",
"age_placeholder": "1399歳",
"registration_method_label": "登録方法:",
"email_radio": "メール",
"phone_radio": "電話",
"phone_label": "電話番号:",
"phone_placeholder": "例: +49123456789",
"email_domain_label": "メールドメイン:",
"proxy_use": "プロキシを使用",
"proxy_label": "プロキシ:",
"proxy_type_label": "タイプ:",
"proxy_type_ipv4": "IPv4",
"proxy_type_ipv6": "IPv6",
"proxy_type_mobile": "モバイル",
"headless": "ブラウザをバックグラウンドで実行",
"debug": "デバッグモード (詳細ログ)",
"log_title": "ログ",
"error_title": "エラー",
"first_name_error": "名を入力してください。",
"last_name_error": "姓を入力してください。",
"age_empty_error": "年齢を入力してください。",
"age_int_error": "年齢は整数である必要があります。",
"age_range_error": "年齢は13から99の間である必要があります。",
"phone_error": "電話番号を入力してください。",
"tiktok_category_label": "カテゴリ/ニッチ:",
"tiktok_category_general": "一般",
"tiktok_category_gaming": "ゲーム",
"tiktok_category_fashion": "ファッション",
"tiktok_category_fitness": "フィットネス",
"tiktok_category_travel": "旅行",
"tiktok_category_cooking": "料理",
"tiktok_category_technology": "テクノロジー",
"tiktok_category_education": "教育"
},
"accounts_tab": {
"headers": [
"ID",
"ユーザー名",
"パスワード",
"メール",
"電話",
"名前",
"プラットフォーム",
"作成日"
],
"no_selection_title": "アカウントが選択されていません",
"no_selection_text": "削除するアカウントを選択してください。",
"delete_title": "アカウントの削除",
"delete_text": "アカウント '{username}' を本当に削除しますか?",
"delete_success_title": "成功",
"delete_success_text": "アカウント '{username}' は削除されました。",
"delete_error_title": "エラー",
"delete_error_text": "アカウント '{username}' を削除できませんでした。"
},
"about_dialog": {
"support": "サポートが必要な場合は次へご連絡ください: support@example.com",
"license": "このソフトウェアはライセンス制で、有効なライセンスでのみ使用できます。"
}
}

0
logs/.gitkeep Normale Datei
Datei anzeigen

34
logs/main.log Normale Datei
Datei anzeigen

@ -0,0 +1,34 @@
2025-05-05 21:38:41,856 - main - INFO - Anwendung wird gestartet...
2025-05-05 21:57:27,120 - main - INFO - Anwendung wird gestartet...
2025-05-11 14:34:47,588 - main - INFO - Anwendung wird gestartet...
2025-05-11 14:35:09,257 - main - INFO - Plattform ausgew<65>hlt: instagram
2025-05-11 22:14:11,965 - main - INFO - Anwendung wird gestartet...
2025-05-11 22:14:15,499 - main - INFO - Plattform ausgew<65>hlt: tiktok
2025-05-11 22:51:23,397 - main - INFO - Zur<75>ck zur Plattformauswahl
2025-05-11 22:55:24,731 - main - INFO - Anwendung wird gestartet...
2025-05-11 22:55:27,782 - main - INFO - Plattform ausgew<65>hlt: tiktok
2025-05-21 00:35:56,633 - main - INFO - Anwendung wird gestartet...
2025-05-21 00:36:03,187 - main - INFO - Plattform ausgew<65>hlt: instagram
2025-05-21 00:36:04,994 - main - INFO - Zur<75>ck zur Plattformauswahl
2025-05-21 00:36:07,722 - main - INFO - Plattform ausgew<65>hlt: vk
2025-05-21 00:36:07,722 - main - ERROR - Plattform 'vk' wird nicht unterst<73>tzt
2025-05-21 00:36:12,032 - main - INFO - Plattform ausgew<65>hlt: tiktok
2025-05-21 02:02:11,243 - main - INFO - Anwendung wird gestartet...
2025-06-16 19:57:45,252 - main - INFO - Anwendung wird gestartet...
2025-06-16 19:57:54,936 - main - INFO - Plattform ausgew<65>hlt: instagram
2025-06-16 19:57:56,286 - main - INFO - Zur<75>ck zur Plattformauswahl
2025-06-16 19:58:06,367 - main - INFO - Plattform ausgew<65>hlt: instagram
2025-06-16 19:58:07,276 - main - INFO - Zur<75>ck zur Plattformauswahl
2025-06-16 19:58:07,706 - main - INFO - Plattform ausgew<65>hlt: facebook
2025-06-16 19:58:07,706 - main - ERROR - Plattform 'facebook' wird nicht unterst<73>tzt
2025-06-16 19:58:09,011 - main - INFO - Plattform ausgew<65>hlt: tiktok
2025-06-16 19:58:10,828 - main - INFO - Zur<75>ck zur Plattformauswahl
2025-06-16 19:58:11,603 - main - INFO - Plattform ausgew<65>hlt: twitter
2025-06-16 19:58:11,603 - main - ERROR - Plattform 'twitter' wird nicht unterst<73>tzt
2025-06-16 19:58:13,674 - main - INFO - Plattform ausgew<65>hlt: tiktok
2025-06-16 19:58:14,871 - main - INFO - Zur<75>ck zur Plattformauswahl
2025-06-22 17:43:11,967 - main - INFO - Anwendung wird gestartet...
2025-06-22 17:52:27,938 - main - INFO - Plattform ausgew<65>hlt: instagram
2025-06-22 17:52:35,074 - main - INFO - Zur<75>ck zur Plattformauswahl
2025-06-22 17:52:48,316 - main - INFO - Plattform ausgew<65>hlt: instagram
2025-06-22 17:52:56,088 - main - INFO - Zur<75>ck zur Plattformauswahl

46
main.py Normale Datei
Datei anzeigen

@ -0,0 +1,46 @@
"""
Social Media Account Generator - Hauptanwendung (Einstiegspunkt)
"""
import os
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt
# Stelle sicher, dass das Hauptverzeichnis im Pythonpfad ist
if os.path.dirname(os.path.abspath(__file__)) not in sys.path:
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
# Import der Hauptcontroller-Klasse
from controllers.main_controller import MainController
from utils.logger import setup_logger
# Stelle sicher, dass benötigte Verzeichnisse existieren
os.makedirs("logs", exist_ok=True)
os.makedirs("config", exist_ok=True)
os.makedirs(os.path.join("logs", "screenshots"), exist_ok=True)
os.makedirs("resources", exist_ok=True)
os.makedirs(os.path.join("resources", "themes"), exist_ok=True)
os.makedirs(os.path.join("resources", "icons"), exist_ok=True)
def main():
"""Hauptfunktion für die Anwendung."""
# Logger initialisieren
logger = setup_logger()
logger.info("Anwendung wird gestartet...")
# QApplication erstellen
app = QApplication(sys.argv)
# High DPI Skalierung aktivieren
app.setAttribute(Qt.AA_EnableHighDpiScaling, True)
app.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
# Hauptcontroller initialisieren (mit QApplication-Instanz)
controller = MainController(app)
# Anwendung starten
sys.exit(app.exec_())
if __name__ == "__main__":
main()

27
requirements.txt Normale Datei
Datei anzeigen

@ -0,0 +1,27 @@
# requirements.txt
# Core dependencies
PyQt5>=5.15.0
playwright>=1.20.0
requests>=2.25.0
# Database
SQLite3>=3.30.0
# Email handling
IMAPClient>=2.1.0
email>=6.0.0
# Utilities
python-dateutil>=2.8.1
difflib>=3.7.0
# Logging
logging>=0.5.1
# Type hints
typing>=3.7.4
# Web automation and anti-detection
undetected-playwright>=0.1.0
random-user-agent>=1.0.1

2
resources/icons/de.svg Normale Datei
Datei anzeigen

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--emojione" preserveAspectRatio="xMidYMid meet"><path d="M31.9 2C18.8 2 7.7 10.4 3.6 22h56.6C56.1 10.4 45 2 31.9 2z" fill="#3e4347"></path><path d="M31.9 62c13.1 0 24.2-8.3 28.3-20H3.6c4.1 11.7 15.2 20 28.3 20z" fill="#ffe62e"></path><path d="M3.6 22c-1.1 3.1-1.7 6.5-1.7 10s.6 6.9 1.7 10h56.6c1.1-3.1 1.7-6.5 1.7-10s-.6-6.9-1.7-10H3.6" fill="#ed4c5c"></path></svg>

Nachher

Breite:  |  Höhe:  |  Größe: 668 B

50
resources/icons/en.svg Normale Datei
Datei anzeigen

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--emojione" preserveAspectRatio="xMidYMid meet">
<g fill="#2a5f9e">
<path d="M22 60.3V46.5l-10.3 7.6c2.9 2.7 6.4 4.8 10.3 6.2">
</path>
<path d="M42 60.3c3.9-1.4 7.4-3.5 10.3-6.2L42 46.4v13.9">
</path>
<path d="M3.7 42c.3 1 .7 1.9 1.2 2.9L8.8 42H3.7">
</path>
<path d="M55.2 42l3.9 2.9c.4-.9.8-1.9 1.2-2.9h-5.1">
</path>
</g>
<g fill="#ffffff">
<path d="M23.5 38H2.6c.3 1.4.7 2.7 1.1 4h5.1l-3.9 2.9c.8 1.7 1.7 3.2 2.8 4.7L18 42h4v2l-11.7 8.6l1.4 1.4L22 46.5v13.8c1.3.5 2.6.8 4 1.1V38h-2.5">
</path>
<path d="M61.4 38H38v23.4c1.4-.3 2.7-.7 4-1.1V46.5L52.3 54c1.4-1.3 2.6-2.7 3.8-4.2L45.4 42h6.8l6.1 4.5c.3-.5.6-1.1.8-1.6L55.2 42h5.1c.4-1.3.8-2.6 1.1-4">
</path>
</g>
<g fill="#ed4c5c">
<path d="M7.7 49.6c.8 1.1 1.6 2.1 2.5 3.1L22 44.1v-2h-4L7.7 49.6">
</path>
<path d="M45.5 42l10.7 7.8c.4-.5.7-1 1.1-1.5c.1-.1.1-.2.2-.2c.3-.5.7-1.1 1-1.6L52.2 42h-6.7">
</path>
</g>
<g fill="#2a5f9e">

Nachher

Breite:  |  Höhe:  |  Größe: 2.2 KiB

114
resources/icons/es.svg Normale Datei
Datei anzeigen

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--emojione" preserveAspectRatio="xMidYMid meet">
<path d="M2 32c0 5.9 1.7 11.4 4.6 16h50.7c2.9-4.6 4.6-10.1 4.6-16s-1.7-11.4-4.6-16H6.6C3.7 20.6 2 26.1 2 32z" fill="#ffce31">
</path>
<g fill="#ed4c5c">
<path d="M57.4 16C52.1 7.6 42.7 2 32 2S11.9 7.6 6.6 16h50.8z">
</path>
<path d="M6.6 48c5.3 8.4 14.7 14 25.4 14s20.1-5.6 25.4-14H6.6z">
</path>
</g>
<g fill="#c8b100">
<path d="M9.2 28.7h3.2v1.8H9.2z">
</path>
<path d="M9.2 41.9h3.3v1.7H9.2z">
</path>
</g>
<path d="M8.9 39.1c-.3.2-.5.4-.5.5c0 .1.1.2.3.3c.2.1.4.3.3.5c.2-.2.3-.4.3-.6c0-.3-.2-.6-.4-.7" fill="#ed4c5c">
</path>
<path fill="#ffffff" d="M9.7 30.5H12v11.4H9.7z">
</path>
<g fill="#ed4c5c">
<path d="M14.4 34.7c-.5-.2-1.4-.4-2.4-.4c-.3 0-.7 0-1.1.1c-1.4.2-2.5.8-2.4 1.2L8 34.5c-.1-.5 1.1-1.1 2.6-1.4c.5-.1 1-.1 1.4-.1c1 0 1.9.1 2.4.3v1.4">
</path>
<path d="M9.7 36.2c-.6 0-1.1-.2-1.1-.5c0-.2.2-.5.6-.7h.6l-.1 1.2">
</path>
<path d="M12 35.3c.4.1.7.2.9.3c.1.1-.3.5-.9.8v-1.1">
</path>
<path d="M8.2 38.4c-.1-.2.6-.6 1.5-.9c.4-.1.7-.3 1.2-.5c1.2-.5 2.2-1.2 2-1.4l.2 1.2c.1.2-.7.8-1.9 1.4c-.4.2-1.1.5-1.5.6c-.7.2-1.3.6-1.3.7l-.2-1.1">
</path>
</g>
<g fill="#c8b100">
<path d="M30.7 28.7h3.2v1.8h-3.2z">
</path>
<path d="M30.6 41.9h3.3v1.7h-3.3z">
</path>
</g>
<path d="M34.2 39.1c.3.2.5.4.5.5c0 .1-.1.2-.3.3c-.2.2-.4.5-.3.6c-.2-.2-.3-.4-.3-.6c0-.4.2-.7.4-.8" fill="#ed4c5c">
</path>
<path fill="#ffffff" d="M31.1 30.5h2.3v11.4h-2.3z">
</path>
<g fill="#ed4c5c">
<path d="M28.7 34.7c.5-.2 1.4-.4 2.4-.4c.3 0 .7 0 1.1.1c1.4.2 2.5.8 2.4 1.2l.5-1.2c.1-.5-1.1-1.1-2.6-1.4h-1.4c-1 0-1.9.1-2.4.3v1.4">
</path>
<path d="M33.4 36.2c.6 0 1.1-.2 1.1-.5c0-.2-.2-.5-.6-.7h-.6l.1 1.2">
</path>
<path d="M31.1 35.3c-.4.1-.7.2-.9.3c-.1.1.3.5.9.8v-1.1">
</path>
<path d="M34.9 38.4c.1-.2-.6-.6-1.5-.9c-.4-.1-.7-.3-1.2-.5c-1.2-.5-2.2-1.2-2-1.4l-.2 1.2c-.1.2.7.8 1.9 1.4c.4.2 1.1.5 1.5.6c.7.2 1.3.7 1.2.8l.3-1.2">
</path>
<path d="M21.5 22.3c1.9 0 5.8.4 7.2 1.8c-1.5 3.6-3.9 2.1-7.2 2.1c-3.2 0-5.7 1.5-7.2-2.1c1.4-1.4 5.2-1.8 7.2-1.8">
</path>
</g>
<g fill="#c8b100">
<path d="M26.4 26.3c-1.2-.7-3-.8-4.9-.8c-1.9 0-3.7.2-4.9.8L17 28c1.1.3 2.7.5 4.5.5c1.8 0 3.3-.2 4.5-.5l.4-1.7">
</path>
<path d="M28.1 22c-.4-.3-1.2-.6-1.9-.6c-.3 0-.6 0-.9.1c0 0-.6-.8-2-.8c-.5 0-.9.1-1.3.3v-.1c-.1-.2-.3-.4-.5-.4s-.5.3-.5.5v.1c-.4-.2-.8-.3-1.3-.3c-1.4 0-2 .9-2 .8c-.3-.1-.6-.1-.9-.1c-4.6 0-2.3 3.1-2.3 3.1l.5-.6c-1.1-1.4-.1-2.2 1.9-2.2c.3 0 .5 0 .7.1c-.7 1 .6 1.9.6 1.9l.3-.5c-.7-.5-.8-2.2 1.2-2.2c.5 0 .9.1 1.3.4c0 .1-.1 1.5-.2 1.7l.8.7l.8-.7c-.1-.3-.2-1.6-.2-1.7c.3-.2.8-.4 1.3-.4c2.1 0 2.1 1.7 1.2 2.2l.3.5s1.1-.9.6-1.9c.2 0 .5-.1.7-.1c2.4 0 2.5 1.8 1.9 2.2l.4.6c-.2 0 .9-1.4-.5-2.6">
</path>

Nachher

Breite:  |  Höhe:  |  Größe: 5.5 KiB

17
resources/icons/facebook.svg Normale Datei
Datei anzeigen

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Facebook-color</title>
<desc>Created with Sketch.</desc>
<defs>
</defs>
<g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Color-" transform="translate(-200.000000, -160.000000)" fill="#4460A0">
<path d="M225.638355,208 L202.649232,208 C201.185673,208 200,206.813592 200,205.350603 L200,162.649211 C200,161.18585 201.185859,160 202.649232,160 L245.350955,160 C246.813955,160 248,161.18585 248,162.649211 L248,205.350603 C248,206.813778 246.813769,208 245.350955,208 L233.119305,208 L233.119305,189.411755 L239.358521,189.411755 L240.292755,182.167586 L233.119305,182.167586 L233.119305,177.542641 C233.119305,175.445287 233.701712,174.01601 236.70929,174.01601 L240.545311,174.014333 L240.545311,167.535091 C239.881886,167.446808 237.604784,167.24957 234.955552,167.24957 C229.424834,167.24957 225.638355,170.625526 225.638355,176.825209 L225.638355,182.167586 L219.383122,182.167586 L219.383122,189.411755 L225.638355,189.411755 L225.638355,208 L225.638355,208 Z" id="Facebook">
</path>
</g>
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 1.4 KiB

2
resources/icons/fr.svg Normale Datei
Datei anzeigen

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--emojione" preserveAspectRatio="xMidYMid meet"><path d="M1.9 32c0 13.1 8.4 24.2 20 28.3V3.7C10.3 7.8 1.9 18.9 1.9 32z" fill="#428bc1"></path><path d="M61.9 32c0-13.1-8.3-24.2-20-28.3v56.6c11.7-4.1 20-15.2 20-28.3" fill="#ed4c5c"></path><path d="M21.9 60.3c3.1 1.1 6.5 1.7 10 1.7s6.9-.6 10-1.7V3.7C38.8 2.6 35.5 2 31.9 2s-6.9.6-10 1.7v56.6" fill="#ffffff"></path></svg>

Nachher

Breite:  |  Höhe:  |  Größe: 672 B

Datei anzeigen

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="28" height="28" rx="6" fill="url(#paint0_radial_87_7153)"/>
<rect x="2" y="2" width="28" height="28" rx="6" fill="url(#paint1_radial_87_7153)"/>
<rect x="2" y="2" width="28" height="28" rx="6" fill="url(#paint2_radial_87_7153)"/>
<path d="M23 10.5C23 11.3284 22.3284 12 21.5 12C20.6716 12 20 11.3284 20 10.5C20 9.67157 20.6716 9 21.5 9C22.3284 9 23 9.67157 23 10.5Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 21C18.7614 21 21 18.7614 21 16C21 13.2386 18.7614 11 16 11C13.2386 11 11 13.2386 11 16C11 18.7614 13.2386 21 16 21ZM16 19C17.6569 19 19 17.6569 19 16C19 14.3431 17.6569 13 16 13C14.3431 13 13 14.3431 13 16C13 17.6569 14.3431 19 16 19Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 15.6C6 12.2397 6 10.5595 6.65396 9.27606C7.2292 8.14708 8.14708 7.2292 9.27606 6.65396C10.5595 6 12.2397 6 15.6 6H16.4C19.7603 6 21.4405 6 22.7239 6.65396C23.8529 7.2292 24.7708 8.14708 25.346 9.27606C26 10.5595 26 12.2397 26 15.6V16.4C26 19.7603 26 21.4405 25.346 22.7239C24.7708 23.8529 23.8529 24.7708 22.7239 25.346C21.4405 26 19.7603 26 16.4 26H15.6C12.2397 26 10.5595 26 9.27606 25.346C8.14708 24.7708 7.2292 23.8529 6.65396 22.7239C6 21.4405 6 19.7603 6 16.4V15.6ZM15.6 8H16.4C18.1132 8 19.2777 8.00156 20.1779 8.0751C21.0548 8.14674 21.5032 8.27659 21.816 8.43597C22.5686 8.81947 23.1805 9.43139 23.564 10.184C23.7234 10.4968 23.8533 10.9452 23.9249 11.8221C23.9984 12.7223 24 13.8868 24 15.6V16.4C24 18.1132 23.9984 19.2777 23.9249 20.1779C23.8533 21.0548 23.7234 21.5032 23.564 21.816C23.1805 22.5686 22.5686 23.1805 21.816 23.564C21.5032 23.7234 21.0548 23.8533 20.1779 23.9249C19.2777 23.9984 18.1132 24 16.4 24H15.6C13.8868 24 12.7223 23.9984 11.8221 23.9249C10.9452 23.8533 10.4968 23.7234 10.184 23.564C9.43139 23.1805 8.81947 22.5686 8.43597 21.816C8.27659 21.5032 8.14674 21.0548 8.0751 20.1779C8.00156 19.2777 8 18.1132 8 16.4V15.6C8 13.8868 8.00156 12.7223 8.0751 11.8221C8.14674 10.9452 8.27659 10.4968 8.43597 10.184C8.81947 9.43139 9.43139 8.81947 10.184 8.43597C10.4968 8.27659 10.9452 8.14674 11.8221 8.0751C12.7223 8.00156 13.8868 8 15.6 8Z" fill="white"/>
<defs>
<radialGradient id="paint0_radial_87_7153" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(12 23) rotate(-55.3758) scale(25.5196)">
<stop stop-color="#B13589"/>
<stop offset="0.79309" stop-color="#C62F94"/>
<stop offset="1" stop-color="#8A3AC8"/>
</radialGradient>
<radialGradient id="paint1_radial_87_7153" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11 31) rotate(-65.1363) scale(22.5942)">
<stop stop-color="#E0E8B7"/>
<stop offset="0.444662" stop-color="#FB8A2E"/>
<stop offset="0.71474" stop-color="#E2425C"/>
<stop offset="1" stop-color="#E2425C" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint2_radial_87_7153" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(0.500002 3) rotate(-8.1301) scale(38.8909 8.31836)">
<stop offset="0.156701" stop-color="#406ADC"/>
<stop offset="0.467799" stop-color="#6A45BE"/>
<stop offset="1" stop-color="#6A45BE" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 3.3 KiB

2
resources/icons/ja.svg Normale Datei
Datei anzeigen

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--emojione" preserveAspectRatio="xMidYMid meet"><circle cx="32" cy="32" r="30" fill="#f5f5f5"></circle><circle cx="32" cy="32" r="12" fill="#ed4c5c"></circle></svg>

Nachher

Breite:  |  Höhe:  |  Größe: 467 B

4
resources/icons/moon.svg Normale Datei
Datei anzeigen

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.0672 11.8568L20.4253 11.469L21.0672 11.8568ZM12.1432 2.93276L11.7553 2.29085V2.29085L12.1432 2.93276ZM21.25 12C21.25 17.1086 17.1086 21.25 12 21.25V22.75C17.9371 22.75 22.75 17.9371 22.75 12H21.25ZM12 21.25C6.89137 21.25 2.75 17.1086 2.75 12H1.25C1.25 17.9371 6.06294 22.75 12 22.75V21.25ZM2.75 12C2.75 6.89137 6.89137 2.75 12 2.75V1.25C6.06294 1.25 1.25 6.06294 1.25 12H2.75ZM15.5 14.25C12.3244 14.25 9.75 11.6756 9.75 8.5H8.25C8.25 12.5041 11.4959 15.75 15.5 15.75V14.25ZM20.4253 11.469C19.4172 13.1373 17.5882 14.25 15.5 14.25V15.75C18.1349 15.75 20.4407 14.3439 21.7092 12.2447L20.4253 11.469ZM9.75 8.5C9.75 6.41182 10.8627 4.5828 12.531 3.57467L11.7553 2.29085C9.65609 3.5593 8.25 5.86509 8.25 8.5H9.75ZM12 2.75C11.9115 2.75 11.8077 2.71008 11.7324 2.63168C11.6686 2.56527 11.6538 2.50244 11.6503 2.47703C11.6461 2.44587 11.6482 2.35557 11.7553 2.29085L12.531 3.57467C13.0342 3.27065 13.196 2.71398 13.1368 2.27627C13.0754 1.82126 12.7166 1.25 12 1.25V2.75ZM21.7092 12.2447C21.6444 12.3518 21.5541 12.3539 21.523 12.3497C21.4976 12.3462 21.4347 12.3314 21.3683 12.2676C21.2899 12.1923 21.25 12.0885 21.25 12H22.75C22.75 11.2834 22.1787 10.9246 21.7237 10.8632C21.286 10.804 20.7293 10.9658 20.4253 11.469L21.7092 12.2447Z" fill="#1C274C"/>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 1.5 KiB

7
resources/icons/sun.svg Normale Datei
Datei anzeigen

@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg fill="#FFFFFF" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 207.628 207.628" xml:space="preserve" stroke="#FFFFFF">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>

Nachher

Breite:  |  Höhe:  |  Größe: 2.4 KiB

15
resources/icons/tiktok.svg Normale Datei
Datei anzeigen

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
<g clip-rule="evenodd" fill-rule="evenodd">
<path d="M25 0h200c13.808 0 25 11.192 25 25v200c0 13.808-11.192 25-25 25H25c-13.808 0-25-11.192-25-25V25C0 11.192 11.192 0 25 0z" fill="#010101"/>
<path d="M156.98 230c7.607 0 13.774-6.117 13.774-13.662s-6.167-13.663-13.774-13.663h-2.075c7.607 0 13.774 6.118 13.774 13.663S162.512 230 154.905 230z" fill="#ee1d51"/>
<path d="M154.717 202.675h-2.075c-7.607 0-13.775 6.118-13.775 13.663S145.035 230 152.642 230h2.075c-7.608 0-13.775-6.117-13.775-13.662s6.167-13.663 13.775-13.663z" fill="#66c8cf"/>
<ellipse cx="154.811" cy="216.338" fill="#010101" rx="6.699" ry="6.643"/>

Nachher

Breite:  |  Höhe:  |  Größe: 3.3 KiB

2
resources/icons/twitter.svg Normale Datei
Datei anzeigen

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="-1.5 0 19 19" xmlns="http://www.w3.org/2000/svg" class="cf-icon-svg"><path d="M15.917 1.666v15.833H.083V1.666zM13.743 6.02a4.702 4.702 0 0 1-1.353.37 2.365 2.365 0 0 0 1.036-1.303 4.725 4.725 0 0 1-1.496.572 2.359 2.359 0 0 0-4.017 2.149 6.685 6.685 0 0 1-4.856-2.462 2.357 2.357 0 0 0 .728 3.146 2.339 2.339 0 0 1-1.067-.295v.03a2.359 2.359 0 0 0 1.89 2.311 2.362 2.362 0 0 1-1.064.04 2.36 2.36 0 0 0 2.202 1.637 4.733 4.733 0 0 1-2.928 1.01 4.838 4.838 0 0 1-.562-.034 6.702 6.702 0 0 0 10.318-5.647c0-.102-.002-.203-.007-.304a4.785 4.785 0 0 0 1.176-1.22z"/></svg>

Nachher

Breite:  |  Höhe:  |  Größe: 744 B

7
resources/icons/vk.svg Normale Datei
Datei anzeigen

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg xmlns="http://www.w3.org/2000/svg"
aria-label="VK" role="img"
viewBox="0 0 512 512"><rect
width="512" height="512"
rx="15%"
fill="#5281b8"/><path fill="#ffffff" d="M274 363c5-1 14-3 14-15 0 0-1-30 13-34s32 29 51 42c14 9 25 8 25 8l51-1s26-2 14-23c-1-2-9-15-39-42-31-30-26-25 11-76 23-31 33-50 30-57-4-7-20-6-20-6h-57c-6 0-9 1-12 6 0 0-9 25-21 45-25 43-35 45-40 42-9-5-7-24-7-37 0-45 7-61-13-65-13-2-59-4-73 3-7 4-11 11-8 12 3 0 12 1 17 7 8 13 9 75-2 81-15 11-53-62-62-86-2-6-5-7-12-9H79c-6 0-15 1-11 13 27 56 83 193 184 192z"/></svg>

Nachher

Breite:  |  Höhe:  |  Größe: 656 B

1
resources/themes/dark.qss Normale Datei
Datei anzeigen

@ -0,0 +1 @@
/* Auto-generated empty stylesheet */

1
resources/themes/light.qss Normale Datei
Datei anzeigen

@ -0,0 +1 @@
/* Auto-generated empty stylesheet */

0
social_networks/__init__.py Normale Datei
Datei anzeigen

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen