nopoh22 commited on
Commit
5cc76be
·
verified ·
1 Parent(s): a4a9d59

Update renewer.py

Browse files
Files changed (1) hide show
  1. renewer.py +54 -144
renewer.py CHANGED
@@ -3,11 +3,7 @@ import json
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,7 +27,7 @@ def save_accounts(accounts):
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,228 +42,142 @@ def get_host(username, token):
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():
247
  accounts = load_accounts()
248
  if not accounts:
249
- logger.info("No accounts configured yet.")
250
  return
251
 
252
  now = datetime.utcnow()
253
  renewed_count = 0
254
 
255
  for account in accounts:
256
- username = account["username"]
257
  next_renewal_str = account.get("next_renewal")
258
 
259
  if next_renewal_str:
260
  next_renewal = datetime.fromisoformat(next_renewal_str)
261
  if now < next_renewal:
262
  remaining = (next_renewal - now).days
263
- logger.info(f"[{username}] Skipping - next renewal in {remaining} days")
264
  continue
265
 
266
- logger.info(f"[{username}] Starting renewal...")
267
  success, results = renew_account(account)
268
  if success:
269
  renewed_count += 1
270
 
271
  save_accounts(accounts)
272
- logger.info(f"Done. Renewed {renewed_count}/{len(accounts)} accounts.")
273
  return accounts
 
3
  import os
4
  import logging
5
  from datetime import datetime, timedelta
6
+ from bs4 import BeautifulSoup
 
 
 
 
7
 
8
  logging.basicConfig(
9
  level=logging.INFO,
 
27
 
28
 
29
  def get_host(username, token):
30
+ """اكتشاف الخادم (US أو EU) عبر الـ Token"""
31
  for host in ["www.pythonanywhere.com", "eu.pythonanywhere.com"]:
32
  try:
33
  r = requests.get(
 
42
  return None
43
 
44
 
45
+ def extend_webapp(username, password, host="www.pythonanywhere.com"):
46
+ """
47
+ التمديد الحقيقي عبر session + CSRF
48
+ يضغط زر Extend تماماً كما لو فعلته يدوياً
49
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  session = requests.Session()
51
+ results = []
52
 
53
  try:
54
+ # الخطوة 1: فتح صفحة تسجيل الدخول وجلب CSRF
55
  login_url = f"https://{host}/login/"
56
+ login_page = session.get(login_url, timeout=15)
57
+
58
  soup = BeautifulSoup(login_page.text, "html.parser")
59
  csrf_input = soup.find("input", {"name": "csrfmiddlewaretoken"})
60
  if not csrf_input:
61
+ logger.error(f"[{username}] لم يتم العثور على CSRF في صفحة تسجيل الدخول")
62
+ return []
63
  csrf = csrf_input["value"]
64
 
65
+ # الخطوة 2: تسجيل الدخول
66
  login_data = {
67
  "auth-username": username,
68
  "auth-password": password,
69
  "csrfmiddlewaretoken": csrf,
70
  "next": "/"
71
  }
72
+ session.post(
73
  login_url,
74
  data=login_data,
75
  headers={"Referer": login_url},
76
+ timeout=15,
77
  allow_redirects=True
78
  )
 
 
 
79
 
80
+ # التحقق من نجاح تسجيل الدخول
81
+ check = session.get(f"https://{host}/dashboard/", timeout=10)
82
+ if username.lower() not in check.text.lower():
83
+ logger.error(f"[{username}] فشل تسجيل الدخول — تحقق من كلمة المرور")
84
+ return []
85
 
86
+ logger.info(f"[{username}] تم تسجيل الدخول بنجاح على {host}")
87
 
88
+ # الخطوة 3: فتح صفحة الويب آب وجلب CSRF جديد
89
  webapps_url = f"https://{host}/user/{username}/webapps/"
90
+ page = session.get(webapps_url, timeout=15)
91
  soup = BeautifulSoup(page.text, "html.parser")
92
+
93
  csrf_input = soup.find("input", {"name": "csrfmiddlewaretoken"})
94
  if not csrf_input:
95
+ logger.error(f"[{username}] لم يتم العثور على CSRF في صفحة الويب آب")
96
+ return []
97
+ csrf = csrf_input["value"]
 
 
 
 
 
 
98
 
99
+ # الخطوة 4: اكتشاف النطاقات المتاحة
100
  domains = []
101
  for link in soup.find_all("a", href=True):
102
  href = link["href"]
103
+ if f"/user/{username}/webapps/" in href:
104
+ part = href.split(f"/user/{username}/webapps/")[-1].strip("/")
105
+ if part and "." in part and part not in domains:
106
+ domains.append(part)
107
 
108
+ # إذا لم يجد نطاقات، يجرب النطاق الافتراضي
109
  if not domains:
110
+ domains = [f"{username}.pythonanywhere.com"]
111
+ logger.warning(f"[{username}] لم يتم اكتشاف نطاقات، سيجرب: {domains[0]}")
 
112
 
113
+ # الخطوة 5: إرسال طلب التمديد لكل نطاق
114
  for domain in domains:
115
  extend_url = f"https://{host}/user/{username}/webapps/{domain}/extend"
116
  resp = session.post(
117
  extend_url,
118
  data={"csrfmiddlewaretoken": csrf},
119
  headers={"Referer": webapps_url},
120
+ timeout=15
121
  )
122
  if resp.status_code in (200, 302):
123
+ status = "✅ تم التمديد"
124
  else:
125
+ status = f"❌ فشل ({resp.status_code})"
126
+
127
  logger.info(f"[{username}] Extend {domain}: {status}")
128
  results.append({"domain": domain, "status": status})
129
 
130
  except Exception as e:
131
+ logger.error(f"[{username}] خطأ أثناء التمديد: {e}")
132
 
133
  return results
134
 
135
 
 
 
 
 
136
  def renew_account(account):
137
  username = account["username"]
 
138
  password = account.get("password", "").strip()
139
+ host = account.get("host", "www.pythonanywhere.com")
140
 
141
+ if not password:
142
+ logger.error(f"[{username}] لا توجد كلمة مرور — لا يمكن التمديد")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  return False, []
144
 
145
+ results = extend_webapp(username, password, host)
146
+ success = any("✅" in r.get("status", "") for r in results) if results else False
147
+
148
  account["host"] = host
149
  account["last_renewed"] = datetime.utcnow().isoformat()
150
  account["next_renewal"] = (datetime.utcnow() + timedelta(days=10)).isoformat()
151
  account["last_results"] = results
152
+
153
  return success, results
154
 
155
 
156
  def run_renewals():
157
  accounts = load_accounts()
158
  if not accounts:
159
+ logger.info("لا توجد حسابات.")
160
  return
161
 
162
  now = datetime.utcnow()
163
  renewed_count = 0
164
 
165
  for account in accounts:
166
+ username = account["username"]
167
  next_renewal_str = account.get("next_renewal")
168
 
169
  if next_renewal_str:
170
  next_renewal = datetime.fromisoformat(next_renewal_str)
171
  if now < next_renewal:
172
  remaining = (next_renewal - now).days
173
+ logger.info(f"[{username}] لا حاجة للتجديد — بعد {remaining} يوم")
174
  continue
175
 
176
+ logger.info(f"[{username}] جاري التمديد...")
177
  success, results = renew_account(account)
178
  if success:
179
  renewed_count += 1
180
 
181
  save_accounts(accounts)
182
+ logger.info(f"اكتمل. تم تمديد {renewed_count}/{len(accounts)} حساب.")
183
  return accounts