#!/usr/bin/env python
"""
@File    :   autoaya.py
@Time    :   2024/02/11 16:58:23
@Author  :   Ayatale 
@Version :   1.5
@Contact :   [email protected]
@Github  :   https://github.com/brx86/
@Desc    :   自动更新订阅,选择最快的节点连接
"""

import httpx
import json


class AutoAya:
    def __init__(
        self,
        user: str,
        passwd: str,
        port: int = 2017,
        sub: int = 0,
        limit: int = 800,
    ):
        """初始化参数

        Args:
            user (str): v2raya 的用户名
            passwd (str): v2raya 的密码
            port (int, optional): v2raya 的端口,默认为 2017
            sub (int, optional): 要自动更新的订阅,默认为 0(即第一个)
            limit (int, optional): 限制最大延迟,不会选中超过这个延迟的节点,默认为 800ms
        """
        self.api = f"http://127.0.0.1:{port}/api"
        self.sub, self.limit = sub, limit
        self.sublist = []
        r = httpx.post(
            f"{self.api}/login",
            json={
                "username": user,
                "password": passwd,
            },
        )
        r.raise_for_status
        token = r.json()["data"]["token"]
        self.client = httpx.Client(headers={"Authorization": token}, timeout=30)

    def get_sublist(self) -> set[int]:
        """获取订阅的节点列表"""
        result = self.client.get(f"{self.api}/touch").json()
        raw_list = result["data"]["touch"]["subscriptions"][self.sub]["servers"]
        self.sublist = {i["id"]: i["name"] for i in raw_list}
        connected_list = {_["id"] for _ in result["data"]["touch"]["connectedServer"]}
        return connected_list

    def sort_list(self, http_list: list) -> list[tuple[int, int]]:
        """对测速后的节点列表进行排序

        Args:
            http_list (list): 测速后的节点列表

        Raises:
            Exception: 所有节点都超出设定延迟则报错

        Returns:
            list: 测速后延迟前五的节点列表
        """
        test_result = {}
        for p in http_list:
            if p["pingLatency"].endswith("ms"):
                latency = int(p["pingLatency"][:-2])
                if (latency < self.limit) and ("GPT" not in self.sublist[p["id"]]):
                    test_result[p["id"]] = latency
        sorted_result = sorted(test_result.items(), key=lambda x: x[1])
        if not sorted_result:
            raise Exception(f"Limit: {self.limit}ms, no server available.")
        if len(sorted_result) < 5:
            return sorted_result
        return sorted_result[:5]

    def test_list(self) -> list[tuple[int, int]]:
        """节点测速

        Args:
            num (int): 节点数量

        Returns:
            list: 测速后延迟前五的节点列表
        """
        print("HTTP testing...")
        plist = [
            {"id": i + 1, "_type": "subscriptionServer", "sub": self.sub}
            for i in range(len(self.sublist))
        ]
        result = self.client.get(
            f"{self.api}/httpLatency",
            params={"whiches": json.dumps(plist)},
        ).json()
        try:
            http_list = result["data"]["whiches"]
            print("Finished!")
            return self.sort_list(http_list)
        except Exception as e:
            print(repr(e), result)
            exit(1)

    def v2raya(self, action: str, pid: int = 1):
        """对 v2raya 的操作

        Args:
            action (str): 要执行的操作名称
            pid (int, optional): 节点序号,默认为 1
        """
        match action:
            case "stop":
                self.client.delete(f"{self.api}/v2ray")
                print("V2ray stopped.")
            case "start":
                self.client.post(f"{self.api}/v2ray")
                print("V2ray started.")
            case "update":
                self.client.put(
                    f"{self.api}/subscription",
                    json={
                        "id": self.sub + 1,
                        "_type": "subscription",
                    },
                )
                print(f"Subscription {self.sub} updated.")
            case "connect":
                self.client.post(
                    f"{self.api}/connection",
                    json={
                        "id": pid,
                        "_type": "subscriptionServer",
                        "sub": self.sub,
                        "outbound": "proxy",
                    },
                )
            case "disconnect":
                self.client.request(
                    method="DELETE",
                    url=f"{self.api}/connection",
                    json={
                        "id": pid,
                        "_type": "subscriptionServer",
                        "sub": self.sub,
                        "outbound": "proxy",
                    },
                )
            case _:
                print("Unknown Action!")

    def run(self):
        """开始运行"""
        self.v2raya("update")  # 更新订阅
        connected_list = self.get_sublist()  # 获取订阅节点列表
        sorted_list = self.test_list()  # 对节点进行测速排序
        self.v2raya("stop")  # 停止 v2raya
        for pid, delay in sorted_list:
            print(f"Server {pid}\tDelay: {delay}ms\t{self.sublist[pid]}")
            self.v2raya("connect", pid)  # 连接新节点
        if disconnect_list := connected_list.difference({_[0] for _ in sorted_list}):
            for pid in disconnect_list:
                print(f"Disconnect server {pid}\t{self.sublist[pid]}")
                self.v2raya("disconnect", pid)  # 断开之前的节点
        self.v2raya("start")  # 启动 v2raya


if __name__ == "__main__":
    user = "aya"  # 此处填写 v2raya 的用户名
    passwd = "aya"  # 此处填写 v2raya 的密码
    autoaya = AutoAya(user, passwd, sub=0)
    autoaya.run()