nopoh22 commited on
Commit
1406c4a
·
verified ·
1 Parent(s): addce27

Update renewer.py

Browse files
Files changed (1) hide show
  1. renewer.py +168 -45
renewer.py CHANGED
@@ -3,6 +3,11 @@ import json
3
  import os
4
  import logging
5
  from datetime import datetime, timedelta
 
 
 
 
 
6
 
7
  logging.basicConfig(
8
  level=logging.INFO,
@@ -26,6 +31,7 @@ def save_accounts(accounts):
26
 
27
 
28
  def get_host(username, token):
 
29
  for host in ["www.pythonanywhere.com", "eu.pythonanywhere.com"]:
30
  try:
31
  r = requests.get(
@@ -40,84 +46,201 @@ def get_host(username, token):
40
  return None
41
 
42
 
43
- def extend_webapp(username, token, host):
44
- """
45
- الوظيفة الحقيقية لزر 'Run until 1 month from today':
46
- 1. POST /enable/ → يُفعّل الـ webapp ويمدده شهر (= الزر في الواجهة)
47
- 2. POST /reload/ → يُعيد تحميل الكود فقط (لا علاقة له بالتمديد)
48
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  results = []
50
  try:
51
- # الخطوة 1: جلب قائمة الـ webapps
52
  r = requests.get(
53
  f"https://{host}/api/v0/user/{username}/webapps/",
54
  headers={"Authorization": f"Token {token}"},
55
  timeout=10
56
  )
57
  if r.status_code != 200:
58
- logger.warning(f"[{username}] Failed to list webapps: {r.status_code} - {r.text}")
59
  return results
60
 
61
  webapps = r.json()
62
- if not webapps:
63
- logger.warning(f"[{username}] No webapps found.")
64
- return results
65
-
66
  for app in webapps:
67
  domain = app.get("domain_name")
68
- logger.info(f"[{username}] Processing webapp: {domain}")
69
-
70
- # الخطوة 2: POST /enable/ = "Run until 1 month from today"
71
- # هذا هو الـ endpoint الصحيح الذي يضغط الزر
72
- enable_r = requests.post(
73
- f"https://{host}/api/v0/user/{username}/webapps/{domain}/enable/",
74
  headers={"Authorization": f"Token {token}"},
75
  timeout=10
76
  )
 
 
 
 
 
 
77
 
78
- if enable_r.status_code == 200:
79
- status = "✅ ممدود (enabled)"
80
- logger.info(f"[{username}] {domain}: تم التمديد بنجاح ✅")
81
- else:
82
- # إذا كان مفعّلاً مسبقاً status=409 أو غيره، نجرب reload
83
- logger.warning(f"[{username}] enable/ returned {enable_r.status_code}: {enable_r.text}")
84
-
85
- # الخطوة 3: POST /reload/ كـ fallback للـ webapps المفعّلة أصلاً
86
- reload_r = requests.post(
87
- f"https://{host}/api/v0/user/{username}/webapps/{domain}/reload/",
88
- headers={"Authorization": f"Token {token}"},
89
- timeout=10
90
- )
91
- if reload_r.status_code == 200:
92
- status = "✅ Reloaded (كان مفعّلاً)"
93
- else:
94
- status = f"❌ فشل ({enable_r.status_code})"
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  results.append({"domain": domain, "status": status})
97
 
98
  except Exception as e:
99
- logger.error(f"[{username}] Error: {e}")
100
 
101
  return results
102
 
103
 
 
 
 
 
104
  def renew_account(account):
105
  username = account["username"]
106
- token = account["token"]
107
- host = account.get("host") or get_host(username, token)
 
108
 
109
- if not host:
110
- logger.error(f"[{username}] Cannot connect - invalid credentials?")
111
- return False, "Cannot connect"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
- account["host"] = host
114
- logger.info(f"[{username}] Connected to {host}")
 
 
115
 
116
- results = extend_webapp(username, token, host)
117
  account["last_renewed"] = datetime.utcnow().isoformat()
118
  account["next_renewal"] = (datetime.utcnow() + timedelta(days=10)).isoformat()
119
  account["last_results"] = results
120
- return True, results
121
 
122
 
123
  def run_renewals():
 
3
  import os
4
  import logging
5
  from datetime import datetime, timedelta
6
+ try:
7
+ from bs4 import BeautifulSoup
8
+ BS4_AVAILABLE = True
9
+ except ImportError:
10
+ BS4_AVAILABLE = False
11
 
12
  logging.basicConfig(
13
  level=logging.INFO,
 
31
 
32
 
33
  def get_host(username, token):
34
+ """Detect if account is on US or EU server using API token."""
35
  for host in ["www.pythonanywhere.com", "eu.pythonanywhere.com"]:
36
  try:
37
  r = requests.get(
 
46
  return None
47
 
48
 
49
+ def get_host_by_password(username, password):
50
+ """Detect server by trying to login."""
51
+ for host in ["www.pythonanywhere.com", "eu.pythonanywhere.com"]:
52
+ try:
53
+ session = requests.Session()
54
+ r = session.get(f"https://{host}/login/", timeout=10)
55
+ if r.status_code == 200:
56
+ return host, session
57
+ except Exception:
58
+ continue
59
+ return None, None
60
+
61
+
62
+ # ─────────────────────────────────────────────
63
+ # Method 1: API Token → /webapps/{domain}/reload/
64
+ # ─────────────────────────────────────────────
65
+
66
+ def extend_webapp_token(username, token, host):
67
+ """Extend all webapps via API reload endpoint (token-based)."""
68
  results = []
69
  try:
 
70
  r = requests.get(
71
  f"https://{host}/api/v0/user/{username}/webapps/",
72
  headers={"Authorization": f"Token {token}"},
73
  timeout=10
74
  )
75
  if r.status_code != 200:
76
+ logger.warning(f"[{username}] Failed to list webapps: {r.status_code}")
77
  return results
78
 
79
  webapps = r.json()
 
 
 
 
80
  for app in webapps:
81
  domain = app.get("domain_name")
82
+ reload_r = requests.post(
83
+ f"https://{host}/api/v0/user/{username}/webapps/{domain}/reload/",
 
 
 
 
84
  headers={"Authorization": f"Token {token}"},
85
  timeout=10
86
  )
87
+ status = "✅ OK" if reload_r.status_code == 200 else f"❌ {reload_r.status_code}"
88
+ logger.info(f"[{username}] Reload {domain}: {status}")
89
+ results.append({"domain": domain, "status": status})
90
+ except Exception as e:
91
+ logger.error(f"[{username}] Error (token method): {e}")
92
+ return results
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ # ─────────────────────────────────────────────
96
+ # Method 2: Password → session login → /extend
97
+ # ─────────────────────────────────────────────
98
+
99
+ def extend_webapp_password(username, password, host=None):
100
+ """Extend webapps via session login + CSRF extend endpoint (password-based)."""
101
+ if not BS4_AVAILABLE:
102
+ logger.error("beautifulsoup4 is not installed. Cannot use password method.")
103
+ return []
104
+
105
+ if not host:
106
+ host = "www.pythonanywhere.com"
107
+
108
+ results = []
109
+ session = requests.Session()
110
+
111
+ try:
112
+ # Step 1: Get login page and extract CSRF token
113
+ login_url = f"https://{host}/login/"
114
+ login_page = session.get(login_url, timeout=10)
115
+ soup = BeautifulSoup(login_page.text, "html.parser")
116
+ csrf_input = soup.find("input", {"name": "csrfmiddlewaretoken"})
117
+ if not csrf_input:
118
+ logger.error(f"[{username}] Could not find CSRF token on login page.")
119
+ return results
120
+ csrf = csrf_input["value"]
121
+
122
+ # Step 2: POST login credentials
123
+ login_data = {
124
+ "auth-username": username,
125
+ "auth-password": password,
126
+ "csrfmiddlewaretoken": csrf,
127
+ "next": "/"
128
+ }
129
+ login_resp = session.post(
130
+ login_url,
131
+ data=login_data,
132
+ headers={"Referer": login_url},
133
+ timeout=10,
134
+ allow_redirects=True
135
+ )
136
+ if login_resp.status_code not in (200, 302):
137
+ logger.error(f"[{username}] Login failed: HTTP {login_resp.status_code}")
138
+ return results
139
+
140
+ # Verify we are actually logged in
141
+ dashboard = session.get(f"https://{host}/dashboard/", timeout=10)
142
+ if "login" in dashboard.url.lower() or username.lower() not in dashboard.text.lower():
143
+ logger.error(f"[{username}] Login appears to have failed (redirected to login page).")
144
+ return results
145
+
146
+ logger.info(f"[{username}] Logged in successfully via password on {host}")
147
+
148
+ # Step 3: Open webapps page to get a fresh CSRF token
149
+ webapps_url = f"https://{host}/user/{username}/webapps/"
150
+ page = session.get(webapps_url, timeout=10)
151
+ soup = BeautifulSoup(page.text, "html.parser")
152
+ csrf_input = soup.find("input", {"name": "csrfmiddlewaretoken"})
153
+ if not csrf_input:
154
+ # Try meta tag fallback
155
+ meta = soup.find("meta", {"name": "csrf-token"})
156
+ csrf = meta["content"] if meta else None
157
+ else:
158
+ csrf = csrf_input["value"]
159
+
160
+ if not csrf:
161
+ logger.error(f"[{username}] Could not find CSRF token on webapps page.")
162
+ return results
163
+
164
+ # Step 4: Find all webapp domains on this page
165
+ domains = []
166
+ for link in soup.find_all("a", href=True):
167
+ href = link["href"]
168
+ if f"/user/{username}/webapps/" in href and "extend" not in href:
169
+ domain_part = href.split(f"/user/{username}/webapps/")[-1].strip("/")
170
+ if domain_part and "." in domain_part and domain_part not in domains:
171
+ domains.append(domain_part)
172
+
173
+ # Fallback: try the default domain if none found
174
+ if not domains:
175
+ default_domain = f"{username}.pythonanywhere.com"
176
+ domains = [default_domain]
177
+ logger.warning(f"[{username}] Could not detect domains, trying default: {default_domain}")
178
+
179
+ # Step 5: POST extend for each domain
180
+ for domain in domains:
181
+ extend_url = f"https://{host}/user/{username}/webapps/{domain}/extend"
182
+ resp = session.post(
183
+ extend_url,
184
+ data={"csrfmiddlewaretoken": csrf},
185
+ headers={"Referer": webapps_url},
186
+ timeout=10
187
+ )
188
+ if resp.status_code in (200, 302):
189
+ status = "✅ OK"
190
+ else:
191
+ status = f"❌ {resp.status_code}"
192
+ logger.info(f"[{username}] Extend {domain}: {status}")
193
  results.append({"domain": domain, "status": status})
194
 
195
  except Exception as e:
196
+ logger.error(f"[{username}] Error (password method): {e}")
197
 
198
  return results
199
 
200
 
201
+ # ─────────────────────────────────────────────
202
+ # Unified renew_account
203
+ # ─────────────────────────────────────────────
204
+
205
  def renew_account(account):
206
  username = account["username"]
207
+ token = account.get("token", "").strip()
208
+ password = account.get("password", "").strip()
209
+ host = account.get("host")
210
 
211
+ results = []
212
+ success = False
213
+
214
+ # ── Priority 1: password method (uses /extend endpoint) ──
215
+ if password and BS4_AVAILABLE:
216
+ if not host:
217
+ host = "www.pythonanywhere.com" # default; extend_webapp_password will auto-login
218
+ logger.info(f"[{username}] Using password method on {host}")
219
+ results = extend_webapp_password(username, password, host)
220
+ success = any("✅" in r.get("status", "") for r in results) if results else False
221
+
222
+ # ── Priority 2: token method (API reload) ──
223
+ elif token:
224
+ if not host:
225
+ host = get_host(username, token)
226
+ if not host:
227
+ logger.error(f"[{username}] Cannot connect - invalid token?")
228
+ return False, []
229
+ account["host"] = host
230
+ logger.info(f"[{username}] Using token method on {host}")
231
+ results = extend_webapp_token(username, token, host)
232
+ success = any("✅" in r.get("status", "") for r in results) if results else False
233
 
234
+ # ── Fallback: try password method even without bs4 ──
235
+ else:
236
+ logger.error(f"[{username}] No token or password provided.")
237
+ return False, []
238
 
239
+ account["host"] = host
240
  account["last_renewed"] = datetime.utcnow().isoformat()
241
  account["next_renewal"] = (datetime.utcnow() + timedelta(days=10)).isoformat()
242
  account["last_results"] = results
243
+ return success, results
244
 
245
 
246
  def run_renewals():