unrealon 2.0.12__py3-none-any.whl → 2.0.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unrealon-2.0.12.dist-info → unrealon-2.0.14.dist-info}/METADATA +1 -1
- {unrealon-2.0.12.dist-info → unrealon-2.0.14.dist-info}/RECORD +13 -9
- {unrealon-2.0.12.dist-info → unrealon-2.0.14.dist-info}/top_level.txt +1 -0
- unrealon_driver/__init__.py +2 -10
- unrealon_driver/utils/__init__.py +0 -8
- unrealon_installer/__init__.py +11 -0
- unrealon_installer/browser_fixes.py +227 -0
- unrealon_installer/core.py +166 -0
- unrealon_installer/platform.py +122 -0
- unrealon_installer/templates.py +60 -0
- unrealon_driver/utils/platform_compatibility.py +0 -205
- {unrealon-2.0.12.dist-info → unrealon-2.0.14.dist-info}/LICENSE +0 -0
- {unrealon-2.0.12.dist-info → unrealon-2.0.14.dist-info}/WHEEL +0 -0
- {unrealon-2.0.12.dist-info → unrealon-2.0.14.dist-info}/entry_points.txt +0 -0
|
@@ -83,7 +83,7 @@ unrealon_core/monitoring/health_check.py,sha256=UAdZ1Of0LCWFVfVXQP0BF-4HDOe4rxyh
|
|
|
83
83
|
unrealon_core/monitoring/metrics.py,sha256=UPCRoridgGVxQ1kh--NuXzH-2dY0WyKljfXDH96OroU,11705
|
|
84
84
|
unrealon_core/utils/__init__.py,sha256=L4nJum_ekqh3Rn324pcIASfr6QQtnT_PwWKCtl2L0mU,179
|
|
85
85
|
unrealon_core/utils/time.py,sha256=27iWhOEl4wHgBOag7gqFlI0hl5cTHPnKHoe8GxmnKHA,1356
|
|
86
|
-
unrealon_driver/__init__.py,sha256=
|
|
86
|
+
unrealon_driver/__init__.py,sha256=un_W-fmvFJ8TIIEKpEOK0Fi62InXTUL8cRCiKgKUtwY,1975
|
|
87
87
|
unrealon_driver/core_module/__init__.py,sha256=v4bHF0eqSQBAUBBEl8RnQWrvb3LjAeIAQCeIXrRTy3w,620
|
|
88
88
|
unrealon_driver/core_module/base.py,sha256=RbC6MVQSA-1145gnDMdsMJ_QnMpQT52pIg86UfRSSMc,6135
|
|
89
89
|
unrealon_driver/core_module/config.py,sha256=N0XGWsoYmv2ruKZOZKOyfaHOZ_jstUDqH7aTqURG2TI,1023
|
|
@@ -123,12 +123,16 @@ unrealon_driver/managers/proxy.py,sha256=b2w6DteMJWnwxZmL3NfwBMdE_mscchoMwPs-XFK
|
|
|
123
123
|
unrealon_driver/managers/registry.py,sha256=--oNPU-65e8J21ubJufyEOc1TirnzJIvpvuY_j7rH7Q,2666
|
|
124
124
|
unrealon_driver/managers/threading.py,sha256=djw5cSC99dfBKmep3IJ_8IgxQceMXtNvCp5fIxHM0TY,1702
|
|
125
125
|
unrealon_driver/managers/update.py,sha256=-hohVxGXpj5bZ6ZTQN6NH1RK9Pd6GVzCMtu3GS2SdcQ,3582
|
|
126
|
-
unrealon_driver/utils/__init__.py,sha256=
|
|
127
|
-
unrealon_driver/utils/platform_compatibility.py,sha256=kydNJhxswBq-z249YQBBVxP0QKEP3vQHuvGuYcBVH-w,7617
|
|
126
|
+
unrealon_driver/utils/__init__.py,sha256=2Sz3eats5q4O2fDmefDuJt8M_zkN6xrS-9xXntWZWFc,168
|
|
128
127
|
unrealon_driver/utils/time.py,sha256=Oxk1eicKeZl8ZWbf7gu1Ll716k6CpXmVj67FHSnPIsA,184
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
unrealon-2.0.
|
|
128
|
+
unrealon_installer/__init__.py,sha256=JxA2hKat5E6R6nt-batuAc-2Cr0nO2M0XW0vOFU_xy0,333
|
|
129
|
+
unrealon_installer/browser_fixes.py,sha256=I09aEMWwvN8-SbGJqJQHcnvsR4_JmjoiRKoyIqft4Rc,7754
|
|
130
|
+
unrealon_installer/core.py,sha256=Ib_10WwXBixHw_jozY_JsAG2eq6dmDCxT0Ucoy_WKVU,5259
|
|
131
|
+
unrealon_installer/platform.py,sha256=NI6-j6674jLWjF0RFRDwXW01D0OueEhhijjevgmbAVs,3122
|
|
132
|
+
unrealon_installer/templates.py,sha256=jU3Fznp_VfZedfWNutVzepGUv2QYI2Q4LM_j8H38CrU,1896
|
|
133
|
+
unrealon-2.0.14.dist-info/LICENSE,sha256=eEH8mWZW49YMpl4Sh5MtKqkZ8aVTzKQXiNPEnvL14ns,1070
|
|
134
|
+
unrealon-2.0.14.dist-info/METADATA,sha256=HIr3X3TBCqGy3nZ0-VVvAZSqHrAFVIngrg4x0RcYGJI,15689
|
|
135
|
+
unrealon-2.0.14.dist-info/WHEEL,sha256=pL8R0wFFS65tNSRnaOVrsw9EOkOqxLrlUPenUYnJKNo,91
|
|
136
|
+
unrealon-2.0.14.dist-info/entry_points.txt,sha256=k0qM-eotpajkKUq-almJmxj9afhXprZ6IkvQkSdcKhI,104
|
|
137
|
+
unrealon-2.0.14.dist-info/top_level.txt,sha256=qN6Q72fe4_i8mTOhYcO3fhGa3g4dmBgvZOsqmK4j8D8,66
|
|
138
|
+
unrealon-2.0.14.dist-info/RECORD,,
|
unrealon_driver/__init__.py
CHANGED
|
@@ -60,12 +60,8 @@ from .decorators import (
|
|
|
60
60
|
timing
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
-
# Utilities -
|
|
64
|
-
from .utils import
|
|
65
|
-
PlatformCompatibility,
|
|
66
|
-
ensure_platform_compatibility,
|
|
67
|
-
get_platform_info
|
|
68
|
-
)
|
|
63
|
+
# Utilities - Time utilities only (platform compatibility moved to installer)
|
|
64
|
+
from .utils import time
|
|
69
65
|
|
|
70
66
|
__version__ = get_driver_version()
|
|
71
67
|
__author__ = "UnrealOn Team"
|
|
@@ -108,8 +104,4 @@ __all__ = [
|
|
|
108
104
|
"schedule",
|
|
109
105
|
"timing",
|
|
110
106
|
|
|
111
|
-
# Utilities
|
|
112
|
-
"PlatformCompatibility",
|
|
113
|
-
"ensure_platform_compatibility",
|
|
114
|
-
"get_platform_info",
|
|
115
107
|
]
|
|
@@ -4,15 +4,7 @@ Utility modules for UnrealOn Driver.
|
|
|
4
4
|
Contains cross-platform compatibility and other utility functions.
|
|
5
5
|
"""
|
|
6
6
|
from .time import utc_now
|
|
7
|
-
from .platform_compatibility import (
|
|
8
|
-
PlatformCompatibility,
|
|
9
|
-
ensure_platform_compatibility,
|
|
10
|
-
get_platform_info
|
|
11
|
-
)
|
|
12
7
|
|
|
13
8
|
__all__ = [
|
|
14
9
|
'utc_now',
|
|
15
|
-
'PlatformCompatibility',
|
|
16
|
-
'ensure_platform_compatibility',
|
|
17
|
-
'get_platform_info'
|
|
18
10
|
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
UnrealOn Universal Installer - Clean Edition
|
|
3
|
+
|
|
4
|
+
One function installs everything. Simple and reliable.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .core import install_parser
|
|
8
|
+
from .browser_fixes import fix_browsers, diagnose_browsers, get_browser_status
|
|
9
|
+
|
|
10
|
+
__version__ = "2.0.0"
|
|
11
|
+
__all__ = ["install_parser", "fix_browsers", "diagnose_browsers", "get_browser_status"]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Browser troubleshooting and fixes for Windows.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import subprocess
|
|
8
|
+
import platform
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
import shutil
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BrowserFixer:
|
|
15
|
+
"""Windows browser troubleshooting and fixes."""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.is_windows = platform.system() == "Windows"
|
|
19
|
+
self.playwright_cache = self._get_playwright_cache_path()
|
|
20
|
+
|
|
21
|
+
def _get_playwright_cache_path(self) -> Optional[Path]:
|
|
22
|
+
"""Get Playwright cache directory path."""
|
|
23
|
+
if self.is_windows:
|
|
24
|
+
return Path(os.environ.get("USERPROFILE", "")) / "AppData" / "Local" / "ms-playwright"
|
|
25
|
+
elif platform.system() == "Darwin": # macOS
|
|
26
|
+
return Path.home() / "Library" / "Caches" / "ms-playwright"
|
|
27
|
+
else: # Linux
|
|
28
|
+
return Path.home() / ".cache" / "ms-playwright"
|
|
29
|
+
|
|
30
|
+
def diagnose_browser_issues(self) -> Dict[str, bool]:
|
|
31
|
+
"""Diagnose common browser issues."""
|
|
32
|
+
issues = {}
|
|
33
|
+
|
|
34
|
+
# Check if Playwright is installed
|
|
35
|
+
issues['playwright_installed'] = self._check_playwright_installed()
|
|
36
|
+
|
|
37
|
+
# Check if browsers are installed
|
|
38
|
+
issues['browsers_installed'] = self._check_browsers_installed()
|
|
39
|
+
|
|
40
|
+
# Check cache directory
|
|
41
|
+
issues['cache_exists'] = self._check_cache_directory()
|
|
42
|
+
|
|
43
|
+
# Check if browsers can launch
|
|
44
|
+
issues['browser_launch_test'] = self._test_browser_launch()
|
|
45
|
+
|
|
46
|
+
return issues
|
|
47
|
+
|
|
48
|
+
def _check_playwright_installed(self) -> bool:
|
|
49
|
+
"""Check if Playwright is installed."""
|
|
50
|
+
try:
|
|
51
|
+
subprocess.run([sys.executable, "-c", "import playwright"],
|
|
52
|
+
check=True, capture_output=True)
|
|
53
|
+
return True
|
|
54
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def _check_browsers_installed(self) -> bool:
|
|
58
|
+
"""Check if Playwright browsers are installed."""
|
|
59
|
+
try:
|
|
60
|
+
result = subprocess.run([sys.executable, "-m", "playwright", "--version"],
|
|
61
|
+
check=True, capture_output=True, text=True)
|
|
62
|
+
return "version" in result.stdout.lower()
|
|
63
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
def _check_cache_directory(self) -> bool:
|
|
67
|
+
"""Check if Playwright cache directory exists."""
|
|
68
|
+
if not self.playwright_cache:
|
|
69
|
+
return False
|
|
70
|
+
return self.playwright_cache.exists()
|
|
71
|
+
|
|
72
|
+
def _test_browser_launch(self) -> bool:
|
|
73
|
+
"""Test if browser can launch (quick test)."""
|
|
74
|
+
try:
|
|
75
|
+
test_code = """
|
|
76
|
+
from playwright.sync_api import sync_playwright
|
|
77
|
+
try:
|
|
78
|
+
with sync_playwright() as p:
|
|
79
|
+
browser = p.chromium.launch(headless=True)
|
|
80
|
+
browser.close()
|
|
81
|
+
print("OK")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
print(f"ERROR: {e}")
|
|
84
|
+
"""
|
|
85
|
+
result = subprocess.run([sys.executable, "-c", test_code],
|
|
86
|
+
capture_output=True, text=True, timeout=30)
|
|
87
|
+
return "OK" in result.stdout
|
|
88
|
+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
def fix_browser_issues(self, force_reinstall: bool = False) -> bool:
|
|
92
|
+
"""Fix common browser issues."""
|
|
93
|
+
print("🔧 Fixing browser issues...")
|
|
94
|
+
|
|
95
|
+
success = True
|
|
96
|
+
|
|
97
|
+
# Step 1: Clear cache if it exists
|
|
98
|
+
if self._clear_browser_cache():
|
|
99
|
+
print("✅ Browser cache cleared")
|
|
100
|
+
else:
|
|
101
|
+
print("⚠️ Could not clear browser cache")
|
|
102
|
+
|
|
103
|
+
# Step 2: Reinstall Playwright if needed or forced
|
|
104
|
+
if force_reinstall or not self._check_playwright_installed():
|
|
105
|
+
if self._reinstall_playwright():
|
|
106
|
+
print("✅ Playwright reinstalled")
|
|
107
|
+
else:
|
|
108
|
+
print("❌ Failed to reinstall Playwright")
|
|
109
|
+
success = False
|
|
110
|
+
|
|
111
|
+
# Step 3: Install browsers
|
|
112
|
+
if self._install_browsers():
|
|
113
|
+
print("✅ Browsers installed")
|
|
114
|
+
else:
|
|
115
|
+
print("❌ Failed to install browsers")
|
|
116
|
+
success = False
|
|
117
|
+
|
|
118
|
+
# Step 4: Test installation
|
|
119
|
+
if self._test_browser_launch():
|
|
120
|
+
print("✅ Browser test passed")
|
|
121
|
+
else:
|
|
122
|
+
print("❌ Browser test failed")
|
|
123
|
+
success = False
|
|
124
|
+
|
|
125
|
+
return success
|
|
126
|
+
|
|
127
|
+
def _clear_browser_cache(self) -> bool:
|
|
128
|
+
"""Clear Playwright browser cache."""
|
|
129
|
+
try:
|
|
130
|
+
if self.playwright_cache and self.playwright_cache.exists():
|
|
131
|
+
shutil.rmtree(self.playwright_cache, ignore_errors=True)
|
|
132
|
+
print(f"Cleared cache: {self.playwright_cache}")
|
|
133
|
+
return True
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f"Cache clear error: {e}")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def _reinstall_playwright(self) -> bool:
|
|
139
|
+
"""Reinstall Playwright completely."""
|
|
140
|
+
try:
|
|
141
|
+
# Uninstall
|
|
142
|
+
subprocess.run([sys.executable, "-m", "pip", "uninstall", "-y", "playwright"],
|
|
143
|
+
check=False, capture_output=True)
|
|
144
|
+
|
|
145
|
+
# Clear pip cache
|
|
146
|
+
subprocess.run([sys.executable, "-m", "pip", "cache", "purge"],
|
|
147
|
+
check=False, capture_output=True)
|
|
148
|
+
|
|
149
|
+
# Reinstall
|
|
150
|
+
subprocess.run([sys.executable, "-m", "pip", "install", "playwright"],
|
|
151
|
+
check=True, capture_output=True)
|
|
152
|
+
|
|
153
|
+
return True
|
|
154
|
+
except subprocess.CalledProcessError:
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
def _install_browsers(self) -> bool:
|
|
158
|
+
"""Install Playwright browsers."""
|
|
159
|
+
try:
|
|
160
|
+
# Install chromium (most stable)
|
|
161
|
+
subprocess.run([sys.executable, "-m", "playwright", "install", "chromium"],
|
|
162
|
+
check=True, capture_output=True)
|
|
163
|
+
|
|
164
|
+
# Try to install firefox too (optional)
|
|
165
|
+
subprocess.run([sys.executable, "-m", "playwright", "install", "firefox"],
|
|
166
|
+
check=False, capture_output=True)
|
|
167
|
+
|
|
168
|
+
return True
|
|
169
|
+
except subprocess.CalledProcessError:
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
def get_browser_info(self) -> Dict[str, str]:
|
|
173
|
+
"""Get browser installation information."""
|
|
174
|
+
info = {}
|
|
175
|
+
|
|
176
|
+
# Playwright version
|
|
177
|
+
try:
|
|
178
|
+
result = subprocess.run([sys.executable, "-m", "playwright", "--version"],
|
|
179
|
+
capture_output=True, text=True)
|
|
180
|
+
info['playwright_version'] = result.stdout.strip()
|
|
181
|
+
except:
|
|
182
|
+
info['playwright_version'] = "Not installed"
|
|
183
|
+
|
|
184
|
+
# Cache directory
|
|
185
|
+
info['cache_path'] = str(self.playwright_cache) if self.playwright_cache else "Unknown"
|
|
186
|
+
info['cache_exists'] = str(self._check_cache_directory())
|
|
187
|
+
|
|
188
|
+
# Browser test
|
|
189
|
+
info['browser_test'] = "PASS" if self._test_browser_launch() else "FAIL"
|
|
190
|
+
|
|
191
|
+
return info
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def fix_browsers(force_reinstall: bool = False) -> bool:
|
|
195
|
+
"""
|
|
196
|
+
Quick browser fix function.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
force_reinstall: Force complete reinstall
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
True if successful
|
|
203
|
+
"""
|
|
204
|
+
fixer = BrowserFixer()
|
|
205
|
+
return fixer.fix_browser_issues(force_reinstall)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def diagnose_browsers() -> Dict[str, bool]:
|
|
209
|
+
"""
|
|
210
|
+
Quick browser diagnosis.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Dictionary with diagnosis results
|
|
214
|
+
"""
|
|
215
|
+
fixer = BrowserFixer()
|
|
216
|
+
return fixer.diagnose_browser_issues()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def get_browser_status() -> Dict[str, str]:
|
|
220
|
+
"""
|
|
221
|
+
Get browser status information.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Dictionary with browser info
|
|
225
|
+
"""
|
|
226
|
+
fixer = BrowserFixer()
|
|
227
|
+
return fixer.get_browser_info()
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core installer logic. Clean and simple.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import subprocess
|
|
8
|
+
import platform
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
from .platform import apply_platform_fixes, check_system_requirements
|
|
13
|
+
from .templates import TemplateEngine
|
|
14
|
+
from .browser_fixes import fix_browsers, diagnose_browsers
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def install_parser(parser_name: str, parser_path: str = ".") -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Install everything for a UnrealOn parser.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
parser_name: Name like "upbit_concurrent"
|
|
23
|
+
parser_path: Path to parser directory
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
True if success, False if failed
|
|
27
|
+
"""
|
|
28
|
+
print(f"🚀 Installing {parser_name}")
|
|
29
|
+
print("=" * 40)
|
|
30
|
+
|
|
31
|
+
parser_dir = Path(parser_path).resolve()
|
|
32
|
+
os.chdir(parser_dir)
|
|
33
|
+
|
|
34
|
+
# 1. Apply platform fixes
|
|
35
|
+
print("🔧 Applying platform fixes...")
|
|
36
|
+
apply_platform_fixes()
|
|
37
|
+
print("✅ Platform fixes applied")
|
|
38
|
+
|
|
39
|
+
# 2. Check system
|
|
40
|
+
print("🔍 Checking system...")
|
|
41
|
+
requirements = check_system_requirements()
|
|
42
|
+
failed = [k for k, v in requirements.items() if not v]
|
|
43
|
+
if failed:
|
|
44
|
+
print(f"❌ Missing: {', '.join(failed)}")
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
print(f"✅ Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")
|
|
48
|
+
|
|
49
|
+
# 3. Install dependencies
|
|
50
|
+
if not _install_dependencies():
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
# 4. Install browsers
|
|
54
|
+
if not _install_browsers():
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
# 5. Create batch files (Windows only)
|
|
58
|
+
if platform.system() == "Windows":
|
|
59
|
+
_create_batch_files(parser_name)
|
|
60
|
+
|
|
61
|
+
print(f"\n✅ {parser_name} installed successfully!")
|
|
62
|
+
print("Run: python main.py 5 2")
|
|
63
|
+
if platform.system() == "Windows":
|
|
64
|
+
print("Or: START.bat")
|
|
65
|
+
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _install_dependencies() -> bool:
|
|
70
|
+
"""Install Python dependencies."""
|
|
71
|
+
print("📦 Installing dependencies...")
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
# Check if requirements.txt exists
|
|
75
|
+
if Path("requirements.txt").exists():
|
|
76
|
+
subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"],
|
|
77
|
+
check=True, capture_output=True)
|
|
78
|
+
else:
|
|
79
|
+
# Fallback to basic packages
|
|
80
|
+
subprocess.run([sys.executable, "-m", "pip", "install", "unrealon", "playwright"],
|
|
81
|
+
check=True, capture_output=True)
|
|
82
|
+
|
|
83
|
+
print("✅ Dependencies installed")
|
|
84
|
+
return True
|
|
85
|
+
except subprocess.CalledProcessError as e:
|
|
86
|
+
print(f"❌ Failed to install dependencies: {e}")
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _install_browsers() -> bool:
|
|
91
|
+
"""Install Playwright browsers with smart fixing."""
|
|
92
|
+
print("🌐 Installing browsers...")
|
|
93
|
+
|
|
94
|
+
# First, diagnose any issues
|
|
95
|
+
issues = diagnose_browsers()
|
|
96
|
+
|
|
97
|
+
# If there are issues, try to fix them
|
|
98
|
+
if not all(issues.values()):
|
|
99
|
+
print("🔧 Detected browser issues, applying fixes...")
|
|
100
|
+
if fix_browsers(force_reinstall=False):
|
|
101
|
+
print("✅ Browser issues fixed")
|
|
102
|
+
return True
|
|
103
|
+
else:
|
|
104
|
+
print("❌ Could not fix browser issues")
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
# If no issues, just install normally
|
|
108
|
+
try:
|
|
109
|
+
subprocess.run([sys.executable, "-m", "playwright", "install", "chromium"],
|
|
110
|
+
check=True, capture_output=True)
|
|
111
|
+
print("✅ Browsers installed")
|
|
112
|
+
return True
|
|
113
|
+
except subprocess.CalledProcessError as e:
|
|
114
|
+
print(f"⚠️ Normal install failed, trying fix: {e}")
|
|
115
|
+
# Try fixing as fallback
|
|
116
|
+
return fix_browsers(force_reinstall=True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _create_batch_files(parser_name: str):
|
|
120
|
+
"""Create Windows batch files using Jinja2 templates."""
|
|
121
|
+
print("🪟 Creating Windows batch files...")
|
|
122
|
+
|
|
123
|
+
# Detect parser config
|
|
124
|
+
config = _detect_parser_config()
|
|
125
|
+
|
|
126
|
+
# Create template engine
|
|
127
|
+
engine = TemplateEngine()
|
|
128
|
+
|
|
129
|
+
# Generate batch files
|
|
130
|
+
start_content = engine.render_start_bat(parser_name, config)
|
|
131
|
+
Path("START.bat").write_text(start_content, encoding="utf-8")
|
|
132
|
+
|
|
133
|
+
quick_content = engine.render_quick_run_bat(parser_name, config)
|
|
134
|
+
Path("QUICK_RUN.bat").write_text(quick_content, encoding="utf-8")
|
|
135
|
+
|
|
136
|
+
test_content = engine.render_test_bat(parser_name, config)
|
|
137
|
+
Path("TEST.bat").write_text(test_content, encoding="utf-8")
|
|
138
|
+
|
|
139
|
+
print("✅ Created START.bat, QUICK_RUN.bat, TEST.bat")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _detect_parser_config() -> Dict[str, Any]:
|
|
143
|
+
"""Detect parser configuration from files."""
|
|
144
|
+
config = {
|
|
145
|
+
'browsers_needed': ['chromium'],
|
|
146
|
+
'proxy_support': False,
|
|
147
|
+
'supports_persistent': False
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# Check main.py for persistent mode support
|
|
151
|
+
if Path("main.py").exists():
|
|
152
|
+
main_content = Path("main.py").read_text()
|
|
153
|
+
if "--persistent" in main_content:
|
|
154
|
+
config['supports_persistent'] = True
|
|
155
|
+
|
|
156
|
+
# Check for proxy config
|
|
157
|
+
if Path("src/proxy_config.py").exists() or Path("proxy_config.py").exists():
|
|
158
|
+
config['proxy_support'] = True
|
|
159
|
+
|
|
160
|
+
# Check pyproject.toml for browser requirements
|
|
161
|
+
if Path("pyproject.toml").exists():
|
|
162
|
+
toml_content = Path("pyproject.toml").read_text()
|
|
163
|
+
if "firefox" in toml_content.lower():
|
|
164
|
+
config['browsers_needed'].append('firefox')
|
|
165
|
+
|
|
166
|
+
return config
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Platform compatibility fixes. Clean and simple.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import sys
|
|
7
|
+
import platform
|
|
8
|
+
import warnings
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def apply_platform_fixes():
|
|
15
|
+
"""Apply all necessary platform fixes automatically."""
|
|
16
|
+
system = platform.system()
|
|
17
|
+
fixes = []
|
|
18
|
+
|
|
19
|
+
if system == "Windows":
|
|
20
|
+
fixes.extend(_apply_windows_fixes())
|
|
21
|
+
elif system == "Darwin": # macOS
|
|
22
|
+
fixes.extend(_apply_macos_fixes())
|
|
23
|
+
elif system == "Linux":
|
|
24
|
+
fixes.extend(_apply_linux_fixes())
|
|
25
|
+
|
|
26
|
+
if fixes:
|
|
27
|
+
logger.info(f"✅ Applied platform fixes: {', '.join(fixes)}")
|
|
28
|
+
|
|
29
|
+
return fixes
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _apply_windows_fixes():
|
|
33
|
+
"""Windows-specific fixes."""
|
|
34
|
+
fixes = []
|
|
35
|
+
|
|
36
|
+
# Asyncio event loop policy
|
|
37
|
+
if sys.version_info >= (3, 8):
|
|
38
|
+
try:
|
|
39
|
+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|
40
|
+
fixes.append("ProactorEventLoop")
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
# Console encoding
|
|
45
|
+
try:
|
|
46
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
47
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
48
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
49
|
+
fixes.append("UTF-8 encoding")
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
# Suppress warnings
|
|
54
|
+
warnings.filterwarnings("ignore", category=ResourceWarning)
|
|
55
|
+
fixes.append("ResourceWarning suppression")
|
|
56
|
+
|
|
57
|
+
return fixes
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _apply_macos_fixes():
|
|
61
|
+
"""macOS-specific fixes."""
|
|
62
|
+
fixes = []
|
|
63
|
+
|
|
64
|
+
# SSL context fix
|
|
65
|
+
try:
|
|
66
|
+
import ssl
|
|
67
|
+
ssl._create_default_https_context = ssl._create_unverified_context
|
|
68
|
+
fixes.append("SSL context")
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
# File descriptor limits
|
|
73
|
+
try:
|
|
74
|
+
import resource
|
|
75
|
+
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
76
|
+
if soft < 1024:
|
|
77
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, (min(1024, hard), hard))
|
|
78
|
+
fixes.append("File descriptor limit")
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
return fixes
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _apply_linux_fixes():
|
|
86
|
+
"""Linux-specific fixes."""
|
|
87
|
+
fixes = []
|
|
88
|
+
|
|
89
|
+
# File descriptor limits
|
|
90
|
+
try:
|
|
91
|
+
import resource
|
|
92
|
+
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
93
|
+
if soft < 2048:
|
|
94
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, (min(2048, hard), hard))
|
|
95
|
+
fixes.append("File descriptor limit")
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
# Headless display
|
|
100
|
+
import os
|
|
101
|
+
if not os.environ.get('DISPLAY'):
|
|
102
|
+
os.environ['DISPLAY'] = ':99'
|
|
103
|
+
fixes.append("Headless display")
|
|
104
|
+
|
|
105
|
+
return fixes
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def get_platform_info():
|
|
109
|
+
"""Get platform information."""
|
|
110
|
+
return {
|
|
111
|
+
'platform': platform.system(),
|
|
112
|
+
'architecture': platform.architecture()[0],
|
|
113
|
+
'python_version': f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def check_system_requirements():
|
|
118
|
+
"""Check if system meets requirements."""
|
|
119
|
+
return {
|
|
120
|
+
'python_version': sys.version_info >= (3, 9),
|
|
121
|
+
'platform_supported': platform.system() in ['Windows', 'Darwin', 'Linux']
|
|
122
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Jinja2 template engine for batch files.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
import json
|
|
8
|
+
from jinja2 import Environment, FileSystemLoader
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TemplateEngine:
|
|
12
|
+
"""Jinja2-based template engine for Windows batch files."""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
# Get batch templates directory
|
|
16
|
+
templates_dir = Path(__file__).parent / "batch_templates"
|
|
17
|
+
|
|
18
|
+
# Create Jinja2 environment
|
|
19
|
+
self.env = Environment(
|
|
20
|
+
loader=FileSystemLoader(str(templates_dir)),
|
|
21
|
+
trim_blocks=True,
|
|
22
|
+
lstrip_blocks=True
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def render_start_bat(self, parser_name: str, config: Dict[str, Any]) -> str:
|
|
26
|
+
"""Render START.bat file."""
|
|
27
|
+
template = self.env.get_template('start.bat.j2')
|
|
28
|
+
|
|
29
|
+
context = {
|
|
30
|
+
'parser_name': parser_name,
|
|
31
|
+
'config': config,
|
|
32
|
+
'config_json': json.dumps(config, indent=None),
|
|
33
|
+
'browsers_list': ', '.join(config.get('browsers_needed', ['chromium'])),
|
|
34
|
+
'has_proxy': config.get('proxy_support', False),
|
|
35
|
+
'has_persistent': config.get('supports_persistent', False)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return template.render(**context)
|
|
39
|
+
|
|
40
|
+
def render_quick_run_bat(self, parser_name: str, config: Dict[str, Any]) -> str:
|
|
41
|
+
"""Render QUICK_RUN.bat file."""
|
|
42
|
+
template = self.env.get_template('quick_run.bat.j2')
|
|
43
|
+
|
|
44
|
+
context = {
|
|
45
|
+
'parser_name': parser_name,
|
|
46
|
+
'config': config
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return template.render(**context)
|
|
50
|
+
|
|
51
|
+
def render_test_bat(self, parser_name: str, config: Dict[str, Any]) -> str:
|
|
52
|
+
"""Render TEST.bat file."""
|
|
53
|
+
template = self.env.get_template('test.bat.j2')
|
|
54
|
+
|
|
55
|
+
context = {
|
|
56
|
+
'parser_name': parser_name,
|
|
57
|
+
'config': config
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return template.render(**context)
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Cross-platform compatibility utilities for UnrealOn Driver.
|
|
3
|
-
|
|
4
|
-
Handles platform-specific configurations and fixes for Windows, macOS, and Linux.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
import sys
|
|
9
|
-
import platform
|
|
10
|
-
import warnings
|
|
11
|
-
from typing import Optional
|
|
12
|
-
import logging
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class PlatformCompatibility:
|
|
18
|
-
"""
|
|
19
|
-
Handles cross-platform compatibility for UnrealOn Driver.
|
|
20
|
-
|
|
21
|
-
Automatically applies platform-specific fixes and configurations
|
|
22
|
-
to ensure consistent behavior across Windows, macOS, and Linux.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def __init__(self):
|
|
26
|
-
"""Initialize platform compatibility."""
|
|
27
|
-
self.platform = platform.system()
|
|
28
|
-
self.python_version = sys.version_info
|
|
29
|
-
self._applied_fixes = []
|
|
30
|
-
|
|
31
|
-
def apply_all_fixes(self) -> None:
|
|
32
|
-
"""
|
|
33
|
-
Apply all platform-specific fixes automatically.
|
|
34
|
-
|
|
35
|
-
This method should be called once at the start of the application
|
|
36
|
-
to ensure optimal cross-platform compatibility.
|
|
37
|
-
"""
|
|
38
|
-
logger.info(f"🔧 Applying platform fixes for {self.platform}")
|
|
39
|
-
|
|
40
|
-
if self.platform == "Windows":
|
|
41
|
-
self._apply_windows_fixes()
|
|
42
|
-
elif self.platform == "Darwin": # macOS
|
|
43
|
-
self._apply_macos_fixes()
|
|
44
|
-
elif self.platform == "Linux":
|
|
45
|
-
self._apply_linux_fixes()
|
|
46
|
-
|
|
47
|
-
logger.info(f"✅ Applied {len(self._applied_fixes)} platform fixes: {', '.join(self._applied_fixes)}")
|
|
48
|
-
|
|
49
|
-
def _apply_windows_fixes(self) -> None:
|
|
50
|
-
"""Apply Windows-specific fixes."""
|
|
51
|
-
|
|
52
|
-
# Fix 1: Set ProactorEventLoopPolicy for better asyncio performance
|
|
53
|
-
if self.python_version >= (3, 8):
|
|
54
|
-
try:
|
|
55
|
-
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|
56
|
-
self._applied_fixes.append("WindowsProactorEventLoopPolicy")
|
|
57
|
-
logger.debug("✅ Set WindowsProactorEventLoopPolicy for better asyncio performance")
|
|
58
|
-
except Exception as e:
|
|
59
|
-
logger.warning(f"⚠️ Failed to set WindowsProactorEventLoopPolicy: {e}")
|
|
60
|
-
|
|
61
|
-
# Fix 2: Suppress ResourceWarning on Windows
|
|
62
|
-
try:
|
|
63
|
-
warnings.filterwarnings("ignore", category=ResourceWarning)
|
|
64
|
-
self._applied_fixes.append("ResourceWarning suppression")
|
|
65
|
-
logger.debug("✅ Suppressed ResourceWarning for cleaner output")
|
|
66
|
-
except Exception as e:
|
|
67
|
-
logger.warning(f"⚠️ Failed to suppress ResourceWarning: {e}")
|
|
68
|
-
|
|
69
|
-
# Fix 3: Set console encoding to UTF-8 if possible
|
|
70
|
-
try:
|
|
71
|
-
if hasattr(sys.stdout, 'reconfigure'):
|
|
72
|
-
sys.stdout.reconfigure(encoding='utf-8')
|
|
73
|
-
sys.stderr.reconfigure(encoding='utf-8')
|
|
74
|
-
self._applied_fixes.append("UTF-8 console encoding")
|
|
75
|
-
logger.debug("✅ Set console encoding to UTF-8")
|
|
76
|
-
except Exception as e:
|
|
77
|
-
logger.debug(f"Console encoding fix not needed or failed: {e}")
|
|
78
|
-
|
|
79
|
-
# Fix 4: Increase default socket timeout for Windows
|
|
80
|
-
try:
|
|
81
|
-
import socket
|
|
82
|
-
socket.setdefaulttimeout(30.0)
|
|
83
|
-
self._applied_fixes.append("Socket timeout increase")
|
|
84
|
-
logger.debug("✅ Increased default socket timeout to 30s")
|
|
85
|
-
except Exception as e:
|
|
86
|
-
logger.warning(f"⚠️ Failed to set socket timeout: {e}")
|
|
87
|
-
|
|
88
|
-
def _apply_macos_fixes(self) -> None:
|
|
89
|
-
"""Apply macOS-specific fixes."""
|
|
90
|
-
|
|
91
|
-
# Fix 1: Handle macOS SSL context issues
|
|
92
|
-
try:
|
|
93
|
-
import ssl
|
|
94
|
-
# Create unverified SSL context for development
|
|
95
|
-
ssl._create_default_https_context = ssl._create_unverified_context
|
|
96
|
-
self._applied_fixes.append("SSL context fix")
|
|
97
|
-
logger.debug("✅ Applied macOS SSL context fix")
|
|
98
|
-
except Exception as e:
|
|
99
|
-
logger.debug(f"SSL context fix not needed: {e}")
|
|
100
|
-
|
|
101
|
-
# Fix 2: Set optimal file descriptor limits
|
|
102
|
-
try:
|
|
103
|
-
import resource
|
|
104
|
-
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
105
|
-
if soft < 1024:
|
|
106
|
-
resource.setrlimit(resource.RLIMIT_NOFILE, (min(1024, hard), hard))
|
|
107
|
-
self._applied_fixes.append("File descriptor limit increase")
|
|
108
|
-
logger.debug("✅ Increased file descriptor limit")
|
|
109
|
-
except Exception as e:
|
|
110
|
-
logger.debug(f"File descriptor limit fix not needed: {e}")
|
|
111
|
-
|
|
112
|
-
def _apply_linux_fixes(self) -> None:
|
|
113
|
-
"""Apply Linux-specific fixes."""
|
|
114
|
-
|
|
115
|
-
# Fix 1: Set optimal file descriptor limits
|
|
116
|
-
try:
|
|
117
|
-
import resource
|
|
118
|
-
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
119
|
-
if soft < 2048:
|
|
120
|
-
resource.setrlimit(resource.RLIMIT_NOFILE, (min(2048, hard), hard))
|
|
121
|
-
self._applied_fixes.append("File descriptor limit increase")
|
|
122
|
-
logger.debug("✅ Increased file descriptor limit")
|
|
123
|
-
except Exception as e:
|
|
124
|
-
logger.debug(f"File descriptor limit fix not needed: {e}")
|
|
125
|
-
|
|
126
|
-
# Fix 2: Handle display issues for headless environments
|
|
127
|
-
try:
|
|
128
|
-
import os
|
|
129
|
-
if not os.environ.get('DISPLAY'):
|
|
130
|
-
os.environ['DISPLAY'] = ':99'
|
|
131
|
-
self._applied_fixes.append("Headless display fix")
|
|
132
|
-
logger.debug("✅ Set display for headless environment")
|
|
133
|
-
except Exception as e:
|
|
134
|
-
logger.debug(f"Display fix not needed: {e}")
|
|
135
|
-
|
|
136
|
-
def get_platform_info(self) -> dict:
|
|
137
|
-
"""
|
|
138
|
-
Get detailed platform information.
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
Dictionary with platform details
|
|
142
|
-
"""
|
|
143
|
-
return {
|
|
144
|
-
'platform': self.platform,
|
|
145
|
-
'platform_release': platform.release(),
|
|
146
|
-
'platform_version': platform.version(),
|
|
147
|
-
'architecture': platform.architecture(),
|
|
148
|
-
'machine': platform.machine(),
|
|
149
|
-
'processor': platform.processor(),
|
|
150
|
-
'python_version': f"{self.python_version.major}.{self.python_version.minor}.{self.python_version.micro}",
|
|
151
|
-
'python_implementation': platform.python_implementation(),
|
|
152
|
-
'applied_fixes': self._applied_fixes
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
@classmethod
|
|
156
|
-
def auto_configure(cls) -> 'PlatformCompatibility':
|
|
157
|
-
"""
|
|
158
|
-
Automatically configure platform compatibility.
|
|
159
|
-
|
|
160
|
-
This is the recommended way to use this class - it will
|
|
161
|
-
automatically detect the platform and apply all necessary fixes.
|
|
162
|
-
|
|
163
|
-
Returns:
|
|
164
|
-
Configured PlatformCompatibility instance
|
|
165
|
-
"""
|
|
166
|
-
compatibility = cls()
|
|
167
|
-
compatibility.apply_all_fixes()
|
|
168
|
-
return compatibility
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
# Global instance for easy access
|
|
172
|
-
_platform_compatibility: Optional[PlatformCompatibility] = None
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def ensure_platform_compatibility() -> PlatformCompatibility:
|
|
176
|
-
"""
|
|
177
|
-
Ensure platform compatibility is configured.
|
|
178
|
-
|
|
179
|
-
This function can be called multiple times safely - it will only
|
|
180
|
-
configure compatibility once per application run.
|
|
181
|
-
|
|
182
|
-
Returns:
|
|
183
|
-
PlatformCompatibility instance
|
|
184
|
-
"""
|
|
185
|
-
global _platform_compatibility
|
|
186
|
-
|
|
187
|
-
if _platform_compatibility is None:
|
|
188
|
-
_platform_compatibility = PlatformCompatibility.auto_configure()
|
|
189
|
-
|
|
190
|
-
return _platform_compatibility
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def get_platform_info() -> dict:
|
|
194
|
-
"""
|
|
195
|
-
Get platform information.
|
|
196
|
-
|
|
197
|
-
Returns:
|
|
198
|
-
Dictionary with platform details
|
|
199
|
-
"""
|
|
200
|
-
compatibility = ensure_platform_compatibility()
|
|
201
|
-
return compatibility.get_platform_info()
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
# Auto-configure on import for convenience
|
|
205
|
-
ensure_platform_compatibility()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|