Update renewer.py
Browse files- renewer.py +96 -53
renewer.py
CHANGED
|
@@ -11,19 +11,93 @@ logging.basicConfig(
|
|
| 11 |
)
|
| 12 |
logger = logging.getLogger(__name__)
|
| 13 |
|
| 14 |
-
|
| 15 |
|
| 16 |
|
| 17 |
-
def
|
| 18 |
-
if os.path.exists(
|
| 19 |
-
with open(
|
| 20 |
return json.load(f)
|
| 21 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
def save_accounts(accounts):
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
def get_host(username, token):
|
|
@@ -45,7 +119,6 @@ def extend_webapp(username, password, host="www.pythonanywhere.com"):
|
|
| 45 |
session = requests.Session()
|
| 46 |
results = []
|
| 47 |
|
| 48 |
-
# headers تحاكي المتصفح تماماً
|
| 49 |
session.headers.update({
|
| 50 |
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
| 51 |
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
@@ -53,44 +126,33 @@ def extend_webapp(username, password, host="www.pythonanywhere.com"):
|
|
| 53 |
})
|
| 54 |
|
| 55 |
try:
|
| 56 |
-
#
|
| 57 |
login_url = f"https://{host}/login/"
|
| 58 |
login_page = session.get(login_url, timeout=15)
|
| 59 |
logger.info(f"[{username}] صفحة الدخول: {login_page.status_code}")
|
| 60 |
|
| 61 |
soup = BeautifulSoup(login_page.text, "html.parser")
|
| 62 |
-
|
| 63 |
-
# اجلب كل حقول الـ form تلقائياً
|
| 64 |
form = soup.find("form")
|
| 65 |
if not form:
|
| 66 |
-
logger.error(f"[{username}] لم يتم العثور على form
|
| 67 |
return []
|
| 68 |
|
| 69 |
-
# ابدأ بكل الحقول الموجودة في الـ form (hidden fields)
|
| 70 |
form_data = {}
|
| 71 |
for inp in form.find_all("input"):
|
| 72 |
-
name
|
| 73 |
-
value = inp.get("value", "")
|
| 74 |
if name:
|
| 75 |
-
form_data[name] = value
|
| 76 |
-
|
| 77 |
-
logger.info(f"[{username}] حقول الـ form: {list(form_data.keys())}")
|
| 78 |
|
| 79 |
-
# أضف ب��انات الدخول
|
| 80 |
form_data["auth-username"] = username
|
| 81 |
form_data["auth-password"] = password
|
| 82 |
-
|
| 83 |
-
# بعض إصدارات الموقع تحتاج هذا الحقل
|
| 84 |
if "login_view-current_step" not in form_data:
|
| 85 |
form_data["login_view-current_step"] = "auth"
|
| 86 |
|
| 87 |
-
#
|
| 88 |
form_action = form.get("action") or login_url
|
| 89 |
if not form_action.startswith("http"):
|
| 90 |
form_action = f"https://{host}{form_action}"
|
| 91 |
|
| 92 |
-
logger.info(f"[{username}] إرسال الدخول إلى: {form_action}")
|
| 93 |
-
|
| 94 |
login_resp = session.post(
|
| 95 |
form_action,
|
| 96 |
data=form_data,
|
|
@@ -102,41 +164,26 @@ def extend_webapp(username, password, host="www.pythonanywhere.com"):
|
|
| 102 |
timeout=15,
|
| 103 |
allow_redirects=True
|
| 104 |
)
|
| 105 |
-
logger.info(f"[{username}] نتيجة الدخول: {login_resp.status_code} |
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
logger.info(f"[{username}] الكوكيز: {list(cookies.keys())}")
|
| 110 |
-
|
| 111 |
-
if "sessionid" not in cookies:
|
| 112 |
-
logger.error(f"[{username}] لم يتم إنشاء sessionid — فشل تسجيل الدخول")
|
| 113 |
return []
|
| 114 |
|
| 115 |
-
|
| 116 |
-
logger.error(f"[{username}] ما زال على صفحة login — فشل")
|
| 117 |
-
return []
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
# ── الخطوة 3: فتح صفحة الويب آب وجلب CSRF جديد ──
|
| 122 |
webapps_url = f"https://{host}/user/{username}/webapps/"
|
| 123 |
page = session.get(webapps_url, timeout=15)
|
| 124 |
-
logger.info(f"[{username}] صفحة الويب آب: {page.status_code}")
|
| 125 |
|
| 126 |
soup = BeautifulSoup(page.text, "html.parser")
|
| 127 |
csrf_input = soup.find("input", {"name": "csrfmiddlewaretoken"})
|
| 128 |
-
|
| 129 |
-
meta = soup.find("meta", {"name": "csrf-token"})
|
| 130 |
-
csrf = meta.get("content", "") if meta else ""
|
| 131 |
-
else:
|
| 132 |
-
csrf = csrf_input["value"]
|
| 133 |
-
|
| 134 |
if not csrf:
|
| 135 |
-
logger.error(f"[{username}] لم يتم العثور على CSRF
|
| 136 |
return []
|
| 137 |
-
logger.info(f"[{username}] CSRF الويب آب: {csrf[:10]}...")
|
| 138 |
|
| 139 |
-
#
|
| 140 |
domains = []
|
| 141 |
for link in soup.find_all("a", href=True):
|
| 142 |
href = link["href"]
|
|
@@ -147,11 +194,8 @@ def extend_webapp(username, password, host="www.pythonanywhere.com"):
|
|
| 147 |
|
| 148 |
if not domains:
|
| 149 |
domains = [f"{username}.pythonanywhere.com"]
|
| 150 |
-
logger.warning(f"[{username}] النطاق الافتراضي: {domains[0]}")
|
| 151 |
-
else:
|
| 152 |
-
logger.info(f"[{username}] النطاقات: {domains}")
|
| 153 |
|
| 154 |
-
#
|
| 155 |
for domain in domains:
|
| 156 |
extend_url = f"https://{host}/user/{username}/webapps/{domain}/extend"
|
| 157 |
resp = session.post(
|
|
@@ -165,7 +209,6 @@ def extend_webapp(username, password, host="www.pythonanywhere.com"):
|
|
| 165 |
timeout=15
|
| 166 |
)
|
| 167 |
logger.info(f"[{username}] Extend {domain}: HTTP {resp.status_code}")
|
| 168 |
-
|
| 169 |
status = "✅ تم التمديد" if resp.status_code in (200, 302) else f"❌ فشل ({resp.status_code})"
|
| 170 |
results.append({"domain": domain, "status": status})
|
| 171 |
|
|
@@ -198,7 +241,7 @@ def renew_account(account):
|
|
| 198 |
def run_renewals():
|
| 199 |
accounts = load_accounts()
|
| 200 |
if not accounts:
|
| 201 |
-
logger.info("لا توجد حسابات.")
|
| 202 |
return
|
| 203 |
|
| 204 |
now = datetime.utcnow()
|
|
|
|
| 11 |
)
|
| 12 |
logger = logging.getLogger(__name__)
|
| 13 |
|
| 14 |
+
STATE_FILE = "renewal_state.json"
|
| 15 |
|
| 16 |
|
| 17 |
+
def load_state():
|
| 18 |
+
if os.path.exists(STATE_FILE):
|
| 19 |
+
with open(STATE_FILE, "r") as f:
|
| 20 |
return json.load(f)
|
| 21 |
+
return {}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def save_state(state):
|
| 25 |
+
with open(STATE_FILE, "w") as f:
|
| 26 |
+
json.dump(state, f, indent=2)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def load_accounts():
|
| 30 |
+
"""
|
| 31 |
+
يقرأ الحسابات من Environment Variables بأي من الصيغ التالية:
|
| 32 |
+
|
| 33 |
+
الصيغة 1 — متغيرات مرقّمة (الموصى بها):
|
| 34 |
+
ACCOUNTS_1 = username1:password1
|
| 35 |
+
ACCOUNTS_2 = username2:password2
|
| 36 |
+
ACCOUNTS_3 = username3:password3
|
| 37 |
+
|
| 38 |
+
الصيغة 2 — متغير واحد يحتوي الكل (مفصولة بفاصلة):
|
| 39 |
+
ACCOUNTS = username1:password1,username2:password2
|
| 40 |
+
|
| 41 |
+
يمكن الجمع بين الصيغتين — سيتم دمج الكل بدون تكرار.
|
| 42 |
+
"""
|
| 43 |
+
state = load_state()
|
| 44 |
+
accounts = []
|
| 45 |
+
seen = set()
|
| 46 |
+
|
| 47 |
+
def add_account(username, password):
|
| 48 |
+
if not username or not password or username in seen:
|
| 49 |
+
return
|
| 50 |
+
seen.add(username)
|
| 51 |
+
user_state = state.get(username, {})
|
| 52 |
+
accounts.append({
|
| 53 |
+
"username": username,
|
| 54 |
+
"password": password,
|
| 55 |
+
"host": user_state.get("host", "www.pythonanywhere.com"),
|
| 56 |
+
"last_renewed": user_state.get("last_renewed"),
|
| 57 |
+
"next_renewal": user_state.get("next_renewal"),
|
| 58 |
+
"last_results": user_state.get("last_results", []),
|
| 59 |
+
})
|
| 60 |
+
|
| 61 |
+
def parse_entry(entry):
|
| 62 |
+
entry = entry.strip()
|
| 63 |
+
if ":" in entry:
|
| 64 |
+
username, password = entry.split(":", 1)
|
| 65 |
+
add_account(username.strip(), password.strip())
|
| 66 |
+
|
| 67 |
+
# ── الصيغة 1: ACCOUNTS_1, ACCOUNTS_2, ACCOUNTS_3 ... ──
|
| 68 |
+
i = 1
|
| 69 |
+
while True:
|
| 70 |
+
entry = os.environ.get(f"ACCOUNTS_{i}", "").strip()
|
| 71 |
+
if not entry:
|
| 72 |
+
break
|
| 73 |
+
parse_entry(entry)
|
| 74 |
+
i += 1
|
| 75 |
+
|
| 76 |
+
# ── الصيغة 2: ACCOUNTS = user1:pass1,user2:pass2 ──
|
| 77 |
+
accounts_env = os.environ.get("ACCOUNTS", "").strip()
|
| 78 |
+
if accounts_env:
|
| 79 |
+
for entry in accounts_env.split(","):
|
| 80 |
+
parse_entry(entry)
|
| 81 |
+
|
| 82 |
+
if accounts:
|
| 83 |
+
logger.info(f"تم تحميل {len(accounts)} حساب: {[a['username'] for a in accounts]}")
|
| 84 |
+
else:
|
| 85 |
+
logger.warning("لم يتم العثور على حسابات — أضف ACCOUNTS_1 أو ACCOUNTS في Secrets")
|
| 86 |
+
|
| 87 |
+
return accounts
|
| 88 |
|
| 89 |
|
| 90 |
def save_accounts(accounts):
|
| 91 |
+
"""يحفظ حالة التجديد فقط — كلمات المرور تبقى في Secrets"""
|
| 92 |
+
state = {}
|
| 93 |
+
for a in accounts:
|
| 94 |
+
state[a["username"]] = {
|
| 95 |
+
"host": a.get("host", "www.pythonanywhere.com"),
|
| 96 |
+
"last_renewed": a.get("last_renewed"),
|
| 97 |
+
"next_renewal": a.get("next_renewal"),
|
| 98 |
+
"last_results": a.get("last_results", []),
|
| 99 |
+
}
|
| 100 |
+
save_state(state)
|
| 101 |
|
| 102 |
|
| 103 |
def get_host(username, token):
|
|
|
|
| 119 |
session = requests.Session()
|
| 120 |
results = []
|
| 121 |
|
|
|
|
| 122 |
session.headers.update({
|
| 123 |
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
| 124 |
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
|
|
| 126 |
})
|
| 127 |
|
| 128 |
try:
|
| 129 |
+
# الخطوة 1: صفحة الدخول
|
| 130 |
login_url = f"https://{host}/login/"
|
| 131 |
login_page = session.get(login_url, timeout=15)
|
| 132 |
logger.info(f"[{username}] صفحة الدخول: {login_page.status_code}")
|
| 133 |
|
| 134 |
soup = BeautifulSoup(login_page.text, "html.parser")
|
|
|
|
|
|
|
| 135 |
form = soup.find("form")
|
| 136 |
if not form:
|
| 137 |
+
logger.error(f"[{username}] لم يتم العثور على form")
|
| 138 |
return []
|
| 139 |
|
|
|
|
| 140 |
form_data = {}
|
| 141 |
for inp in form.find_all("input"):
|
| 142 |
+
name = inp.get("name")
|
|
|
|
| 143 |
if name:
|
| 144 |
+
form_data[name] = inp.get("value", "")
|
|
|
|
|
|
|
| 145 |
|
|
|
|
| 146 |
form_data["auth-username"] = username
|
| 147 |
form_data["auth-password"] = password
|
|
|
|
|
|
|
| 148 |
if "login_view-current_step" not in form_data:
|
| 149 |
form_data["login_view-current_step"] = "auth"
|
| 150 |
|
| 151 |
+
# الخطوة 2: تسجيل الدخول
|
| 152 |
form_action = form.get("action") or login_url
|
| 153 |
if not form_action.startswith("http"):
|
| 154 |
form_action = f"https://{host}{form_action}"
|
| 155 |
|
|
|
|
|
|
|
| 156 |
login_resp = session.post(
|
| 157 |
form_action,
|
| 158 |
data=form_data,
|
|
|
|
| 164 |
timeout=15,
|
| 165 |
allow_redirects=True
|
| 166 |
)
|
| 167 |
+
logger.info(f"[{username}] نتيجة الدخول: {login_resp.status_code} | {login_resp.url}")
|
| 168 |
|
| 169 |
+
if "sessionid" not in session.cookies.get_dict():
|
| 170 |
+
logger.error(f"[{username}] فشل تسجيل الدخول — لا يوجد sessionid")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
return []
|
| 172 |
|
| 173 |
+
logger.info(f"[{username}] ✅ تم تسجيل الدخول")
|
|
|
|
|
|
|
| 174 |
|
| 175 |
+
# الخطوة 3: صفحة الويب آب
|
|
|
|
|
|
|
| 176 |
webapps_url = f"https://{host}/user/{username}/webapps/"
|
| 177 |
page = session.get(webapps_url, timeout=15)
|
|
|
|
| 178 |
|
| 179 |
soup = BeautifulSoup(page.text, "html.parser")
|
| 180 |
csrf_input = soup.find("input", {"name": "csrfmiddlewaretoken"})
|
| 181 |
+
csrf = csrf_input["value"] if csrf_input else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
if not csrf:
|
| 183 |
+
logger.error(f"[{username}] لم يتم العثور على CSRF")
|
| 184 |
return []
|
|
|
|
| 185 |
|
| 186 |
+
# الخطوة 4: اكتشاف النطاقات
|
| 187 |
domains = []
|
| 188 |
for link in soup.find_all("a", href=True):
|
| 189 |
href = link["href"]
|
|
|
|
| 194 |
|
| 195 |
if not domains:
|
| 196 |
domains = [f"{username}.pythonanywhere.com"]
|
|
|
|
|
|
|
|
|
|
| 197 |
|
| 198 |
+
# الخطوة 5: التمديد
|
| 199 |
for domain in domains:
|
| 200 |
extend_url = f"https://{host}/user/{username}/webapps/{domain}/extend"
|
| 201 |
resp = session.post(
|
|
|
|
| 209 |
timeout=15
|
| 210 |
)
|
| 211 |
logger.info(f"[{username}] Extend {domain}: HTTP {resp.status_code}")
|
|
|
|
| 212 |
status = "✅ تم التمديد" if resp.status_code in (200, 302) else f"❌ فشل ({resp.status_code})"
|
| 213 |
results.append({"domain": domain, "status": status})
|
| 214 |
|
|
|
|
| 241 |
def run_renewals():
|
| 242 |
accounts = load_accounts()
|
| 243 |
if not accounts:
|
| 244 |
+
logger.info("لا توجد حسابات — أضف ACCOUNTS_1 أو ACCOUNTS في Secrets.")
|
| 245 |
return
|
| 246 |
|
| 247 |
now = datetime.utcnow()
|