[Python Automation] Freecloud Automatic Login, Renewal, and Telegram Notification Script

大家好! :wave:

今天给大家分享一个我编写的 Python 脚本,它可以自动化处理 Freecloud 网站(https://freecloud.ltd/)的日常任务,包括 自动登录、检查订阅剩余天数、在即将到期时自动续费,并将结果通过 Telegram Bot 推送给你。

如果你有多个 Freecloud 账号需要管理,或者只是想“一劳永逸” :rocket:,这个脚本可以为你节省大量时间和精力。


:sparkles: 功能特点

  • :busts_in_silhouette: 多账号支持:轻松管理多个 Freecloud 账号。
  • :key: 自动登录:脚本会自动处理登录过程,包括填写用户名、密码和计算验证码。
  • :cloud: 智能Cloudflare验证:自动检测并尝试通过 Cloudflare 的“真人验证”步骤。
  • :mag: 账户信息提取:登录后自动导航到服务器页面,提取订阅到期时间和剩余天数。
  • :arrows_counterclockwise: 自动续费:当检测到订阅剩余天数少于或等于3天时,脚本会自动尝试进行续费。
  • :speech_balloon: Telegram实时通知:无论是成功续费、续费失败,还是普通的账户状态检查,都会通过 Telegram Bot 发送格式清晰的通知。
  • :camera: 错误处理与快照:在遇到登录失败或元素找不到等问题时,会自动保存当前页面的快照(HTML文件),方便你进行调试。
  • :link: 会话保持:通过连接到本地已打开的浏览器实例(端口 9222),可以避免重复扫描二维码登录,并保持登录状态。

:wrench: 准备工作

在运行脚本之前,你需要进行一些准备工作。

1. 安装必要的 Python 库

本脚本依赖 DrissionPage 和 requests 库。打开你的终端或命令行工具,运行以下命令来安装它们:

pip install DrissionPage requests
2. 配置浏览器 (重要!)

为了让脚本能够接管你的浏览器,你需要以“调试模式”启动一个浏览器实例(推荐使用 Chrome 或 Edge)。

Windows 用户:
找到你的 Chrome 浏览器快捷方式,右键点击“属性”,在“目标”字段的末尾添加以下参数(注意在exe路径后先加一个空格):

--remote-debugging-port=9222

修改后的目标路径看起来像这样:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222

macOS 用户:
打开“终端”并运行以下命令:

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222

运行后,会打开一个新的浏览器窗口。请不要关闭这个窗口,后续的自动化操作将在这个窗口中执行。

3. 配置 Telegram Bot

你需要一个属于自己的 Telegram Bot 来接收通知。

  1. 在 Telegram 中搜索 BotFather。
  2. 发送 /newbot 命令创建一个新的机器人,你将获得一个 Token。
  3. 搜索你刚刚创建的机器人的名字,并给它发送一条消息。
  4. 你需要获取你的 Chat ID。最简单的方法是把你收到的机器人 Token 填入以下 URL,并在浏览器中打开它:
    https://api.telegram.org/bot<你的TOKEN>/getUpdates
    在返回的 JSON 数据中,找到 "chat":{"id": <你的CHAT_ID>}。

:scroll: 代码详解

这是脚本的核心逻辑分解。

  • Telegram 通知函数 (send_telegram_message): 这个函数负责将格式化的消息发送到你指定的 Telegram 聊天。
  • 登出函数 (logout_if_needed): 在每次处理新账号之前,脚本会检查当前是否已登录。如果检测到登录状态(通过寻找“退出”按钮),它会先执行登出操作,确保一个干净的登录环境。
  • 核心处理函数 (login_and_process): 这是脚本最关键的部分:
    • Cloudflare 验证:循环检测页面上是否有“确认您是真人”的按钮,如果有就点击,直到获取到 cf_clearance cookie 为止。
    • 表单填写:自动输入用户名和密码。
    • 验证码计算:脚本会自动从页面提取如 (5 + 3) 这样的数学表达式,并计算出结果填入验证码输入框。
    • 信息提取:登录成功后,导航到服务器列表页面,并精确地提取出包含到期时间和剩余天数的信息。
    • 续费逻辑:检查剩余天数。如果小于等于3天,它会自动寻找并点击“续费”和“确认续费”按钮。
    • 发送通知:根据操作结果(常规检查、续费成功、续费失败),构建不同内容的消息并发送。
  • 主函数 (main):
    • 连接浏览器:使用 ChromiumOptions().set_local_port(9222) 来接管你之前在调试模式下打开的浏览器。
    • 账号列表:在这里你需要填入你的 Freecloud 用户名和密码。
    • 循环处理:遍历账号列表,对每个账号依次执行登出检查和登录处理流程。

:rocket: 如何使用

  1. 将下面的完整代码保存为一个 Python 文件,例如 login.py。

  2. 修改配置:

    • 在脚本中找到 TELEGRAM_BOT_TOKEN 和 TELEGRAM_CHAT_ID,替换成你在准备工作中获取到的值。
    • 找到 accounts 列表,将示例中的用户名和密码修改为你自己的 Freecloud 账号信息。你可以添加任意多个账号。
    # Telegram Bot Configuration
    TELEGRAM_BOT_TOKEN = '你的_BOT_TOKEN'
    TELEGRAM_CHAT_ID = '你的_CHAT_ID'
    
    # ...
    
    accounts = [
        {'username': '你的用户名1', 'password': '你的密码1'},
        {'username': '你的用户名2', 'password': '你的密码2'}
    ]
    
  3. 运行脚本:

    • 确保你已经在调试模式下打开了一个浏览器窗口。
    • 打开终端,进入到你保存脚本的目录,运行脚本:
    python login.py
    

现在,脚本就会自动开始执行所有操作了。你可以在终端看到实时的日志输出,同时你的 Telegram 也会收到相应的通知。


:snake: 完整代码

点击展开/折叠代码
from DrissionPage import Chromium, ChromiumOptions
from DrissionPage.errors import ElementNotFoundError
import re
import platform
import time
import requests
from datetime import datetime

# Telegram Bot Configuration
# 在这里替换为你自己的 Bot Token 和 Chat ID
TELEGRAM_BOT_TOKEN = '你的_BOT_TOKEN'
TELEGRAM_CHAT_ID = '你的_CHAT_ID'

def send_telegram_message(message):
    """Sends a message to a Telegram user or group."""
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
    payload = {
        'chat_id': TELEGRAM_CHAT_ID,
        'text': message,
        'parse_mode': 'Markdown'
    }
    try:
        response = requests.post(url, json=payload)
        response.raise_for_status()
        print("Telegram message sent successfully!")
    except requests.exceptions.RequestException as e:
        print(f"Failed to send Telegram message: {e}")

def logout_if_needed(tab):
    """Navigates to the login page and logs out if a session is active."""
    print("导航至登录页面并检查/执行登出操作...")
    tab.get('https://freecloud.ltd/login')
    tab.wait.load_start() # Wait for the page to load or redirect

    try:
        # Check if the logout button exists. If it does, we are logged in.
        tab.ele('xpath:/html/body/header/div/div[1]/a[2]', timeout=5)
        
        print("检测到已登录,正在执行退出操作...")
        
        # Navigate to the homepage first before logging out.
        try:
            homepage_button = tab.ele('xpath://*[@id="header"]/ul[2]/li[1]/a')
            homepage_button.click()
            tab.wait.load_start()
            time.sleep(2)  # Add delay
            print("已返回官网。")
        except ElementNotFoundError:
            print("无法找到官网按钮,将直接尝试退出。")
        
        # Re-find and click the logout button.
        tab.ele('xpath:/html/body/header/div/div[1]/a[2]', timeout=10).click()
        print("已退出登录。")
        tab.wait.load_start()
        time.sleep(2)  # Add delay
        
        # After logging out, explicitly navigate back to the login page for a clean start.
        tab.get('https://freecloud.ltd/login')
        print("已返回登录页面。")
        
    except ElementNotFoundError:
        # If the logout button is not found, it means we are already logged out.
        print("未检测到登录状态,可以安全地继续登录。")
        # The page should be the login page at this point.
        pass

def login_and_process(tab, username, password):
    """Logs in with the given credentials and processes the account information."""
    try:
        # --- 填写登录表单 ---
        print(f"--- 开始处理账号: {username} ---")

        # --- 处理Cloudflare验证 ---
        print("正在检查Cloudflare验证...")
        value = '确认您是真人' if platform.system().lower() == 'windows' else 'Verify you are human'
        for i in range(20):
            if tab.ele(f'x://input[@value="{value}"]', timeout=3):
                print(f"第 {i+1} 次尝试, 点击 '确认您是真人'...")
                tab.ele(f'x://input[@value="{value}"]').click()
                tab.wait(2)
            
            cookies = tab.cookies()
            if any(cookie['name'] == 'cf_clearance' for cookie in cookies):
                print("成功获取 cf_clearance cookie。")
                break
        else:
            print("无法获取 cf_clearance cookie,继续尝试登录。")

        print("正在填写登录表单...")
        # 清除并输入用户名
        user_input = tab.ele('xpath://*[@id="PASS"]/div[1]/div/input', timeout=60)
        user_input.clear()
        user_input.input(username)
        
        # 清除并输入密码
        pass_input = tab.ele('xpath://*[@id="PASS"]/div[2]/div/input')
        pass_input.clear()
        pass_input.input(password)

        # 提取并计算验证码
        captcha_ele = tab.ele('xpath=//*[@id="content"]/div/div/section/form/div[1]/div[2]/div/input')
        captcha_text = captcha_ele.attr('placeholder')
        match_captcha = re.search(r'(\d+\s*[\+\-\*]\s*\d+)', captcha_text)
        if not match_captcha:
            raise ValueError("无法从文本中解析出验证码。")
        math_expr = match_captcha.group(1)
        captcha_result = eval(math_expr)
        captcha_ele.input(str(captcha_result))

        # 点击登录按钮
        print("正在点击登录按钮...")
        tab.ele('xpath://*[@id="content"]/div/div/section/form/div[3]/button').click()
        time.sleep(2)  # Add delay
        print("登录成功!")

        # --- 选择服务器并提取信息 ---
        print("正在选择服务器...")
        tab.ele('xpath://*[@id="nav"]/ul/li[3]/a/span', timeout=20).click()
        
        print("正在提取信息...")
        tab.wait.load_start()
        info_element = tab.ele('xpath:/html/body/section/section/section/form[2]/div/div/table/tbody/tr/td[9]', timeout=20)
        info_text = info_element.text
        print(f"提取的信息: {info_text}")

        # Parse the expiry date and remaining days
        match = re.search(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s*((\d+)天后)', info_text)
        if match:
            expiry_date_str = match.group(1)
            remaining_days = int(match.group(2))

            # Format the base message
            message = f"*Freecloud Account: {username}*\n\n" \
                      f"Expiry Date: `{expiry_date_str}`\n" \
                      f"Remaining Days: `{remaining_days}`"

            # Check for renewal condition
            if remaining_days <= 3:
                try:
                    print("Subscription expiring soon. Attempting to auto-renew...")
                    
                    # 查找并点击续订按钮
                    table_row = info_element.parent()
                    renew_button = table_row.ele('text:续费', timeout=10)
                    renew_button.click()
                    print("已点击 '续费' 按钮。")

                    # 等待并点击确认续订按钮
                    print("正在等待续订确认框...")
                    submit_button = tab.ele('#submitRenew', timeout=20)
                    submit_button.click()
                    print("已点击 '点击续费' 按钮。")
                    time.sleep(3)  # 等待操作完成
                    
                    # If successful, create a success message
                    message = f"*Freecloud Auto-Renewal SUCCESS: {username}*\n\n" \
                              f"Successfully renewed subscription.\n\n" \
                              f"Previous Expiry Date: `{expiry_date_str}`\n" \
                              f"Previous Remaining Days: `{remaining_days}`"

                except ElementNotFoundError as e:
                    print(f"Auto-renewal failed for {username}: {e}")
                    # If renewal fails, append a warning to the original message
                    message += f"\n\n*警告: 账号 {username} 的订阅即将过期。*"
                    message += f"\n*自动续订尝试失败*: {e}"

            send_telegram_message(message)
        else:
            error_message = f"Could not parse expiry date and remaining days for {username} from the extracted text: `{info_text}`"
            print(error_message)
            send_telegram_message(error_message)
        
        print(f"--- 账号: {username} 处理完毕 ---")

    except ElementNotFoundError as e:
        print(f"错误 (账号: {username}): {e}")
        print("可能原因:Cloudflare 验证失败、网络超时或页面结构已更改。")
        snapshot_path = f'login_snapshot_{username}.html'
        tab.save(path=snapshot_path)
        print(f"已保存页面快照至 {snapshot_path}")
        print(f"当前页面 URL: {tab.url}")

def main():
    """Main function to run the automation for all accounts."""
    # 启动或接管浏览器
    co = ChromiumOptions().set_local_port(9222)
    page = Chromium(co)
    tab = page.get_tab()

    # 在这里替换为你自己的账号信息
    accounts = [
        {'username': '你的用户名1', 'password': '你的密码1'},
        {'username': '你的用户名2', 'password': '你的密码2'}
    ]

    for account in accounts:
        logout_if_needed(tab)
        login_and_process(tab, account['username'], account['password'])
        
    print("所有账号处理完毕。")
    page.quit()
    print("浏览器已关闭。")

if __name__ == "__main__":
    main()

:warning: 注意事项

  • 页面结构变化:如果 Freecloud 网站的页面结构(HTML)发生改变,可能会导致脚本中的元素选择器(如 XPath)失效。届时需要根据新的页面结构更新代码。如果脚本出错,检查保存的 login_snapshot_...html 文件会很有帮助。
  • 网络问题:不稳定的网络可能会导致超时错误。脚本内置了一些等待时间,但你可能需要根据自己的网络情况进行调整。
  • 安全性:请妥善保管你的 TELEGRAM_BOT_TOKEN 和 Freecloud 账号密码,不要泄露给他人。

希望这个脚本能对大家有所帮助!如果你有任何问题或建议,欢迎在下方留言讨论。 :raised_hands:

29 Likes

懂技术就是好!为大佬点:+1:

大佬厉害啊,能再出个通用的模板就更加造福小白了!

厉害了
https://nat.freecloud.ltd 大佬这个有脚本吗?

感谢技术大佬
是不是用headless模式更好

2 Likes

太强了

可以自动化处理 Freecloud ?

给大佬打call :xhj006:

老板看到了马上不能用

技术大佬就是牛

这个不错,可以拿来参考搞下woiden

1 Like

感谢大佬分享~ :xhj003:

看看66 :xhj003:

出个青龙面板的教程就完美了

刚刚用你的脚本测试了一下,我反馈一下我使用的结果:
1、会卡打开freecloud的cloudflare验证的界面,我手动刷新了一下,并手动点击了人工验证
2、自动登陆成功后,执行到 “正在提取信息…” 之后, 返回的信息是:
错误 (账号: ******我的账号):
没有找到元素。
3、之后就在终端中提示异常:
可能原因:Cloudflare 验证失败、网络超时或页面结构已更改。
已保存页面快照至 login_snapshot_我的账号.html
4、以上执行"失败"后,最后提示:
所有账号处理完毕。
浏览器已关闭。
但是:TG机器人没有收到失败的提示(准确来说,没有收到任何信息)

再次更新一下:
我把延迟加大了,tab.ele(‘xpath://*[@id=“nav”]/ul/li[3]/a/span’, timeout=5000).click()
原来是20,我把时间变长了
这个地方我也把延迟加大了,submit_button = tab.ele(‘#submitRenew’, timeout=5000)
可能是因为我自己网络的原因哈,其他人看着情况进行调整就行
经过延迟的调整,现在我已经跑通了,分享一下自己的情况给大家

2 Likes

技术大牛,佩服 :xhj16:

支持一下

大佬厉害

1 Like

哈哈,技术都懂点,大佬特别懂

感谢大佬