1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
|
#!/usr/bin/env python3
"""
Extract the latest Twos AppImage version from twosapp.com/home
Uses Playwright with headless browser to wait for JavaScript to load the download link
Compares with current version in PKGBUILD and writes to build.env if different
"""
import re
import sys
import os
HEADLESS = True
try:
from playwright.sync_api import sync_playwright
except ImportError:
print("Error: playwright not installed. Install with: pip install playwright && playwright install chromium", file=sys.stderr)
sys.exit(1)
def find_version():
"""Extract version from twosapp.com/home using headless browser"""
global HEADLESS
pattern = r'https://twos\.s3\.us-west-2\.amazonaws\.com/mac/Twos-(\d+\.\d+\.\d+)\.AppImage'
pattern2 = r'Twos-(\d+\.\d+\.\d+)\.AppImage'
browser = None
context = None
page = None
headless_mode = HEADLESS # Use local variable to avoid issues
with sync_playwright() as p:
try:
# Launch browser (Playwright automatically downloads it if needed)
launch_options = {
'headless': headless_mode,
}
if not headless_mode:
# Add options for non-headless mode
launch_options['args'] = ['--disable-blink-features=AutomationControlled']
import time
try:
browser = p.chromium.launch(**launch_options)
time.sleep(0.2) # Small delay to let browser stabilize
# Check if browser is actually connected
if not browser.is_connected():
raise Exception("Browser launched but not connected - may have crashed")
except Exception as e:
print(f"Error launching browser in {'non-headless' if not headless_mode else 'headless'} mode: {e}", file=sys.stderr)
# If non-headless fails, try headless as fallback
if not headless_mode:
print("Falling back to headless mode...", file=sys.stderr)
headless_mode = True
try:
browser = p.chromium.launch(headless=True)
time.sleep(0.2)
if not browser.is_connected():
print("Browser still not connected in headless mode", file=sys.stderr)
return None
except Exception as e2:
print(f"Error launching headless browser: {e2}", file=sys.stderr)
return None
else:
return None
# Double-check browser is connected right before creating context
if not browser.is_connected():
print("Browser disconnected before context creation", file=sys.stderr)
if not headless_mode:
print("Falling back to headless mode...", file=sys.stderr)
headless_mode = True
browser = p.chromium.launch(headless=True)
time.sleep(0.2)
if not browser.is_connected():
print("Browser still not connected in headless mode", file=sys.stderr)
return None
else:
return None
try:
context = browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
)
except Exception as e:
print(f"Error creating context: {e}", file=sys.stderr)
# If non-headless failed, try headless
if not headless_mode and "closed" in str(e).lower():
print("Falling back to headless mode...", file=sys.stderr)
headless_mode = True
if browser:
try:
browser.close()
except:
pass
browser = p.chromium.launch(headless=True)
time.sleep(0.2)
context = browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
)
else:
if browser:
try:
browser.close()
except:
pass
return None
try:
page = context.new_page()
except Exception as e:
print(f"Error creating page: {e}", file=sys.stderr)
print(f"Browser connected: {browser.is_connected() if browser else 'N/A'}", file=sys.stderr)
# If non-headless failed, try headless as fallback
if not headless_mode and "closed" in str(e).lower():
print("Falling back to headless mode...", file=sys.stderr)
headless_mode = True
try:
if context:
context.close()
if browser:
browser.close()
except:
pass
# Restart with headless
browser = p.chromium.launch(headless=True)
time.sleep(0.2)
context = browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
)
page = context.new_page()
else:
if context:
context.close()
if browser:
browser.close()
return None
# Navigate to the page
page.goto('https://www.twosapp.com/home', wait_until='networkidle')
# Wait for JavaScript to fully load
page.wait_for_timeout(5000)
# Try to click on "home" or any download-related button if it exists
try:
# Look for download buttons or links
download_buttons = page.query_selector_all('a[href*="download"], button:has-text("Download"), a:has-text("Download"), a:has-text("Get"), button:has-text("Get")')
if download_buttons:
download_buttons[0].click()
page.wait_for_timeout(2000)
except Exception:
pass
# Scroll to trigger lazy loading
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
page.wait_for_timeout(2000)
page.evaluate("window.scrollTo(0, 0)")
page.wait_for_timeout(2000)
# Scroll through the page more thoroughly
for i in range(3):
page.evaluate(f"window.scrollTo(0, {i * 500})")
page.wait_for_timeout(1000)
# Get page content
page_source = page.content()
# Look for AppImage URL pattern in page source
match = re.search(pattern, page_source)
if match:
version = match.group(1)
if not headless_mode:
page.wait_for_timeout(2000) # Keep browser open briefly in non-headless mode
return version
# Try to find download links
try:
# Look for links containing AppImage or twos.s3
links = page.query_selector_all('a[href*="AppImage"], a[href*="twos.s3"]')
for link in links:
href = link.get_attribute('href')
if href:
match = re.search(pattern, href)
if match:
version = match.group(1)
if not headless_mode:
page.wait_for_timeout(2000) # Keep browser open briefly in non-headless mode
return version
except Exception as e:
pass
# Try alternative pattern
match = re.search(pattern2, page_source)
if match:
version = match.group(1)
if not headless_mode:
page.wait_for_timeout(2000) # Keep browser open briefly in non-headless mode
return version
print("Version not found in page", file=sys.stderr)
if not headless_mode:
page.wait_for_timeout(2000) # Keep browser open briefly in non-headless mode
return None
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
import traceback
traceback.print_exc()
return None
finally:
# Clean up browser resources
if browser and browser.is_connected():
try:
if context:
context.close()
browser.close()
except Exception as e:
pass
def get_current_version():
"""Read current version from PKGBUILD"""
try:
with open('PKGBUILD', 'r') as f:
content = f.read()
match = re.search(r'^pkgver=(\S+)', content, re.MULTILINE)
if match:
return match.group(1)
except FileNotFoundError:
print("Error: PKGBUILD not found", file=sys.stderr)
except Exception as e:
print(f"Error reading PKGBUILD: {e}", file=sys.stderr)
return None
def main():
"""Main function that compares versions and writes build.env if needed"""
current_version = get_current_version()
if current_version is None:
print("Could not read current version from PKGBUILD", file=sys.stderr)
return 1
# Find the latest version from the website
found_version = find_version()
if found_version is None:
print("Could not find version from website", file=sys.stderr)
return 1
print(f"current version is {current_version}")
print(f"found version {found_version}")
# Compare versions
if current_version != found_version:
print("Build the thing")
# Write to build.env
try:
with open('build.env', 'w') as f:
f.write(f"pkgver={found_version}\n")
return 0
except Exception as e:
print(f"Error writing build.env: {e}", file=sys.stderr)
return 1
else:
print("No new version")
return 0
if __name__ == '__main__':
# If called with --version-only flag, just print version (for backward compatibility)
if len(sys.argv) > 1 and sys.argv[1] == '--version-only':
version = find_version()
if version:
print(version)
sys.exit(0)
else:
sys.exit(1)
else:
# Full mode: compare versions and write build.env
sys.exit(main())
|