Download renewer.py from nopoh22/pythonany: direct link, hf CLI and curl.
- Browser
- Download file 16.5 kB
-
https://huggingface.co/spaces/nopoh22/pythonany/resolve/main/renewer.py
- Command line
-
hf download hf://spaces/nopoh22/pythonany/renewer.py
-
curl -L -o renewer.py https://huggingface.co/spaces/nopoh22/pythonany/resolve/main/renewer.py
16.5 kB
| import requests | |
| import json | |
| import os | |
| import logging | |
| import time | |
| from datetime import datetime, timedelta | |
| from bs4 import BeautifulSoup | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s [%(levelname)s] %(message)s' | |
| ) | |
| logger = logging.getLogger(__name__) | |
| STATE_FILE = "renewal_state.json" | |
| HOSTS = ["www.pythonanywhere.com", "eu.pythonanywhere.com"] | |
| MAX_RETRIES = 3 | |
| RETRY_DELAY = 5 # ุซูุงูู ุจูู ุงูู ุญุงููุงุช | |
| REQUEST_TIMEOUT = 20 | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # State helpers | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def load_state() -> dict: | |
| if os.path.exists(STATE_FILE): | |
| try: | |
| with open(STATE_FILE, "r") as f: | |
| return json.load(f) | |
| except (json.JSONDecodeError, OSError) as e: | |
| logger.warning(f"ุชุนุฐูุฑ ูุฑุงุกุฉ ู ูู ุงูุญุงูุฉ: {e} โ ุณูุจุฏุฃ ู ู ุงูุตูุฑ") | |
| return {} | |
| def save_state(state: dict): | |
| try: | |
| with open(STATE_FILE, "w") as f: | |
| json.dump(state, f, indent=2, ensure_ascii=False) | |
| except OSError as e: | |
| logger.error(f"ุชุนุฐูุฑ ุญูุธ ุงูุญุงูุฉ: {e}") | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Account loading | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def load_accounts() -> list[dict]: | |
| """ | |
| ููุฑุฃ ุงูุญุณุงุจุงุช ู ู ู ุชุบูุฑุงุช ุงูุจูุฆุฉ ุจุฃู ู ู ุงูุตูุบุชูู: | |
| ุงูุตูุบุฉ 1 โ ู ุฑููู ุฉ (ู ูุตู ุจูุง): | |
| ACCOUNTS_1 = username1:password1 | |
| ACCOUNTS_2 = username2:password2 | |
| ุงูุตูุบุฉ 2 โ ู ุชุบูุฑ ูุงุญุฏ ู ูุตูู ุจูุงุตูุฉ: | |
| ACCOUNTS = username1:password1,username2:password2 | |
| """ | |
| state = load_state() | |
| accounts = [] | |
| seen = set() | |
| def add_account(username: str, password: str): | |
| username = username.strip() | |
| password = password.strip() | |
| if not username or not password or username in seen: | |
| return | |
| seen.add(username) | |
| s = state.get(username, {}) | |
| accounts.append({ | |
| "username": username, | |
| "password": password, | |
| "host": s.get("host", "www.pythonanywhere.com"), | |
| "last_renewed": s.get("last_renewed"), | |
| "next_renewal": s.get("next_renewal"), | |
| "last_results": s.get("last_results", []), | |
| }) | |
| def parse_entry(entry: str): | |
| entry = entry.strip() | |
| if ":" in entry: | |
| username, password = entry.split(":", 1) | |
| add_account(username, password) | |
| # ุงูุตูุบุฉ 1 | |
| i = 1 | |
| while True: | |
| entry = os.environ.get(f"ACCOUNTS_{i}", "").strip() | |
| if not entry: | |
| break | |
| parse_entry(entry) | |
| i += 1 | |
| # ุงูุตูุบุฉ 2 | |
| bulk = os.environ.get("ACCOUNTS", "").strip() | |
| if bulk: | |
| for entry in bulk.split(","): | |
| parse_entry(entry) | |
| if accounts: | |
| logger.info(f"ุชู ุชุญู ูู {len(accounts)} ุญุณุงุจ: {[a['username'] for a in accounts]}") | |
| else: | |
| logger.warning("ูู ูุชู ุงูุนุซูุฑ ุนูู ุญุณุงุจุงุช โ ุฃุถู ACCOUNTS_1 ุฃู ACCOUNTS ูู Secrets") | |
| return accounts | |
| def save_accounts(accounts: list[dict]): | |
| """ูุญูุธ ุงูุญุงูุฉ ููุท โ ููู ุงุช ุงูู ุฑูุฑ ูุง ุชููุชุจ ุนูู ุงููุฑุต.""" | |
| state = { | |
| a["username"]: { | |
| "host": a.get("host", "www.pythonanywhere.com"), | |
| "last_renewed": a.get("last_renewed"), | |
| "next_renewal": a.get("next_renewal"), | |
| "last_results": a.get("last_results", []), | |
| } | |
| for a in accounts | |
| } | |
| save_state(state) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Session builder | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def _build_session() -> requests.Session: | |
| session = requests.Session() | |
| session.headers.update({ | |
| "User-Agent": ( | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " | |
| "AppleWebKit/537.36 (KHTML, like Gecko) " | |
| "Chrome/124.0.0.0 Safari/537.36" | |
| ), | |
| "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", | |
| "Accept-Language": "en-US,en;q=0.9", | |
| "Accept-Encoding": "gzip, deflate, br", | |
| "Connection": "keep-alive", | |
| }) | |
| return session | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Login | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def _login(session: requests.Session, username: str, password: str, host: str) -> bool: | |
| """ | |
| ูุณุฌูู ุงูุฏุฎูู ุจุทุฑููุฉ ู ูุซููุฉ: | |
| 1. ูุฌูุจ ุงูู CSRF ู ู ุตูุญุฉ ุงูููุฌู | |
| 2. ูุฑุณู ุงูุจูุงูุงุช | |
| 3. ูุชุญูู ู ู ูุฌูุฏ sessionid ูู ุงูููููุฒ | |
| 4. ูุชุญูู ู ู ูุฌูุฏ "logout" ูู ูุต ุงูุตูุญุฉ | |
| """ | |
| login_url = f"https://{host}/login/" | |
| for attempt in range(1, MAX_RETRIES + 1): | |
| try: | |
| login_page = session.get(login_url, timeout=REQUEST_TIMEOUT) | |
| login_page.raise_for_status() | |
| soup = BeautifulSoup(login_page.text, "html.parser") | |
| form = soup.find("form") | |
| if not form: | |
| logger.error(f"[{username}] ูู ูุชู ุงูุนุซูุฑ ุนูู form ูู ุตูุญุฉ ุงูุฏุฎูู") | |
| return False | |
| form_data: dict = {} | |
| for inp in form.find_all("input"): | |
| name = inp.get("name") | |
| if name: | |
| form_data[name] = inp.get("value", "") | |
| form_data["auth-username"] = username | |
| form_data["auth-password"] = password | |
| form_data["login_view-current_step"] = "auth" | |
| action = form.get("action") or login_url | |
| if not action.startswith("http"): | |
| action = f"https://{host}{action}" | |
| resp = session.post( | |
| action, | |
| data=form_data, | |
| headers={ | |
| "Referer": login_url, | |
| "Origin": f"https://{host}", | |
| "Content-Type": "application/x-www-form-urlencoded", | |
| }, | |
| timeout=REQUEST_TIMEOUT, | |
| allow_redirects=True, | |
| ) | |
| cookies = session.cookies.get_dict() | |
| # ุงูุชุญูู ุงูู ุฒุฏูุฌ: ูููู sessionid + ูุต ูุญุชูู logout | |
| has_session = "sessionid" in cookies | |
| has_logout = ( | |
| "log out" in resp.text.lower() | |
| or "logout" in resp.text.lower() | |
| ) | |
| if has_session and has_logout: | |
| logger.info(f"[{username}] โ ุชู ุชุณุฌูู ุงูุฏุฎูู ุนูู {host}") | |
| return True | |
| if not has_session: | |
| logger.warning(f"[{username}] ู ุญุงููุฉ {attempt}/{MAX_RETRIES} โ ูุง ููุฌุฏ sessionid") | |
| elif not has_logout: | |
| logger.warning(f"[{username}] ู ุญุงููุฉ {attempt}/{MAX_RETRIES} โ 'logout' ุบูุฑ ู ูุฌูุฏ ูู ุงูุตูุญุฉ") | |
| except requests.Timeout: | |
| logger.warning(f"[{username}] ู ุญุงููุฉ {attempt}/{MAX_RETRIES} โ ุงูุชูุช ุงูู ููุฉ") | |
| except requests.RequestException as e: | |
| logger.warning(f"[{username}] ู ุญุงููุฉ {attempt}/{MAX_RETRIES} โ ุฎุทุฃ ุดุจูุฉ: {e}") | |
| if attempt < MAX_RETRIES: | |
| time.sleep(RETRY_DELAY) | |
| logger.error(f"[{username}] โ ูุดู ุชุณุฌูู ุงูุฏุฎูู ุจุนุฏ {MAX_RETRIES} ู ุญุงููุงุช") | |
| return False | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Host discovery | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def _detect_host(session: requests.Session, username: str) -> str | None: | |
| """ููุชุดู ุงูุฎุงุฏู ุงูุตุญูุญ (www ุฃู eu) ุจุนุฏ ุชุณุฌูู ุงูุฏุฎูู.""" | |
| for host in HOSTS: | |
| try: | |
| r = session.get( | |
| f"https://{host}/user/{username}/webapps/", | |
| timeout=REQUEST_TIMEOUT, | |
| allow_redirects=True, | |
| ) | |
| if r.status_code == 200 and username.lower() in r.url.lower(): | |
| logger.info(f"[{username}] ุงูุฎุงุฏู : {host}") | |
| return host | |
| except requests.RequestException: | |
| continue | |
| return None | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Webapp extension | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def _get_domains(soup: BeautifulSoup, username: str, host: str) -> list[str]: | |
| """ | |
| ููุชุดู ุงููุทุงูุงุช ุจุทุฑููุชูู: | |
| 1. ุฑูุงุจุท <a> ุชุญุชูู /webapps/ (ุงูุฃูุซุฑ ุฏูุฉ) | |
| 2. ูู ุงุฐุฌ <form> ุชุญุชูู /extend ูู action (ุงุญุชูุงุทู) | |
| """ | |
| domains: list[str] = [] | |
| # ุงูุทุฑููุฉ 1 โ ุฑูุงุจุท ุงูู webapps | |
| for tag in soup.find_all("a", href=True): | |
| href: str = tag["href"] | |
| marker = f"/user/{username}/webapps/" | |
| if marker in href: | |
| part = href.split(marker)[-1].strip("/") | |
| if part and "." in part and part not in domains: | |
| domains.append(part) | |
| # ุงูุทุฑููุฉ 2 โ ูู ุงุฐุฌ extend (ุงุญุชูุงุทู) | |
| if not domains: | |
| for form in soup.find_all("form", action=True): | |
| action: str = form.get("action", "") | |
| if "/extend" in action.lower(): | |
| domain = action.split("/webapps/")[-1].split("/extend")[0].strip("/") | |
| if domain and domain not in domains: | |
| domains.append(domain) | |
| # ุงูุงูุชุฑุงุถู | |
| if not domains: | |
| domains = [f"{username}.pythonanywhere.com"] | |
| logger.warning(f"[{username}] ูู ูุชู ุงูุชุดุงู ุฃู ูุทุงู โ ุณูุณุชุฎุฏู ุงูุงูุชุฑุงุถู: {domains[0]}") | |
| return domains | |
| def extend_webapp(username: str, password: str, host: str = "www.pythonanywhere.com") -> list[dict]: | |
| session = _build_session() | |
| results: list[dict] = [] | |
| # โโ ุชุณุฌูู ุงูุฏุฎูู โโ | |
| if not _login(session, username, password, host): | |
| return [] | |
| # โโ ุตูุญุฉ ุงูู webapps โโ | |
| webapps_url = f"https://{host}/user/{username}/webapps/" | |
| try: | |
| page = session.get(webapps_url, timeout=REQUEST_TIMEOUT) | |
| page.raise_for_status() | |
| except requests.RequestException as e: | |
| logger.error(f"[{username}] ุชุนุฐูุฑ ุงููุตูู ูุตูุญุฉ webapps: {e}") | |
| return [] | |
| soup = BeautifulSoup(page.text, "html.parser") | |
| # โโ CSRF โโ | |
| csrf_input = soup.find("input", {"name": "csrfmiddlewaretoken"}) | |
| csrf = csrf_input["value"] if csrf_input else session.cookies.get("csrftoken", "") | |
| if not csrf: | |
| logger.error(f"[{username}] ูู ูุชู ุงูุนุซูุฑ ุนูู CSRF token") | |
| return [] | |
| # โโ ุงููุทุงูุงุช โโ | |
| domains = _get_domains(soup, username, host) | |
| logger.info(f"[{username}] ุงููุทุงูุงุช ุงูู ูุชุดูุฉ: {domains}") | |
| # โโ ุงูุชู ุฏูุฏ โโ | |
| for domain in domains: | |
| extend_url = f"https://{host}/user/{username}/webapps/{domain}/extend" | |
| success = False | |
| for attempt in range(1, MAX_RETRIES + 1): | |
| try: | |
| resp = session.post( | |
| extend_url, | |
| data={"csrfmiddlewaretoken": csrf}, | |
| headers={ | |
| "Referer": webapps_url, | |
| "Origin": f"https://{host}", | |
| "Content-Type": "application/x-www-form-urlencoded", | |
| }, | |
| timeout=REQUEST_TIMEOUT, | |
| allow_redirects=True, | |
| ) | |
| logger.info(f"[{username}] Extend {domain}: HTTP {resp.status_code} โ {resp.url}") | |
| if resp.status_code in (200, 302): | |
| success = True | |
| break | |
| else: | |
| logger.warning( | |
| f"[{username}] ู ุญุงููุฉ {attempt}/{MAX_RETRIES} โ " | |
| f"extend {domain} ูุดู ({resp.status_code})" | |
| ) | |
| except requests.Timeout: | |
| logger.warning(f"[{username}] ู ุญุงููุฉ {attempt}/{MAX_RETRIES} โ ุงูุชูุช ุงูู ููุฉ ุฃุซูุงุก extend {domain}") | |
| except requests.RequestException as e: | |
| logger.warning(f"[{username}] ู ุญุงููุฉ {attempt}/{MAX_RETRIES} โ ุฎุทุฃ: {e}") | |
| if attempt < MAX_RETRIES: | |
| time.sleep(RETRY_DELAY) | |
| status = "โ ุชู ุงูุชู ุฏูุฏ" if success else f"โ ูุดู ุงูุชู ุฏูุฏ ุจุนุฏ {MAX_RETRIES} ู ุญุงููุงุช" | |
| results.append({"domain": domain, "status": status}) | |
| return results | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Renew single account | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def renew_account(account: dict) -> tuple[bool, list[dict]]: | |
| username = account["username"] | |
| password = account.get("password", "").strip() | |
| host = account.get("host") or "www.pythonanywhere.com" | |
| if not password: | |
| logger.error(f"[{username}] ูุง ุชูุฌุฏ ููู ุฉ ู ุฑูุฑ") | |
| return False, [] | |
| # ู ุญุงููุฉ ุงูุงุชุตุงู โ ุงูุชุดุงู ุงูุฎุงุฏู ุฅุฐุง ูุฒู | |
| results = extend_webapp(username, password, host) | |
| # ุฅุฐุง ูุดู ุงูุฎุงุฏู ุงูู ุญููุธุ ุฌุฑูุจ ุงูุขุฎุฑ | |
| if not results: | |
| other_host = next((h for h in HOSTS if h != host), None) | |
| if other_host: | |
| logger.info(f"[{username}] ุฌุฑุจ ุงูุฎุงุฏู ุงูุขุฎุฑ: {other_host}") | |
| results = extend_webapp(username, password, other_host) | |
| if results: | |
| host = other_host | |
| success = any("โ " in r.get("status", "") for r in results) if results else False | |
| account["host"] = host | |
| account["last_renewed"] = datetime.utcnow().isoformat() | |
| account["next_renewal"] = (datetime.utcnow() + timedelta(days=10)).isoformat() | |
| account["last_results"] = results | |
| return success, results | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Batch renewal | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def run_renewals() -> list[dict] | None: | |
| accounts = load_accounts() | |
| if not accounts: | |
| logger.info("ูุง ุชูุฌุฏ ุญุณุงุจุงุช.") | |
| return None | |
| now = datetime.utcnow() | |
| renewed_count = 0 | |
| for account in accounts: | |
| username = account["username"] | |
| next_renewal = account.get("next_renewal") | |
| if next_renewal: | |
| try: | |
| nxt = datetime.fromisoformat(next_renewal) | |
| if now < nxt: | |
| remaining = (nxt - now).days | |
| logger.info(f"[{username}] ุงูุชุฌุฏูุฏ ุงููุงุฏู ุจุนุฏ {remaining} ููู โ ูุง ุญุงุฌุฉ ุงูุขู") | |
| continue | |
| except ValueError: | |
| logger.warning(f"[{username}] next_renewal ุบูุฑ ุตุงูุญ โ ุณููุฌุฏููุฏ ุงูุขู") | |
| logger.info(f"[{username}] ุฌุงุฑู ุงูุชุฌุฏูุฏ...") | |
| success, _ = renew_account(account) | |
| if success: | |
| renewed_count += 1 | |
| else: | |
| logger.error(f"[{username}] โ ูุดู ุงูุชุฌุฏูุฏ") | |
| save_accounts(accounts) | |
| logger.info(f"ุงูุชู ู: ุชู ุชุฌุฏูุฏ {renewed_count}/{len(accounts)} ุญุณุงุจ.") | |
| return accounts |