# plugins: 调用 alist 实现跨网盘转存
# author: https://github.com/jenfonro

import re
import json
import requests


class Alist_sync:

    default_config = {
        "url": "",  # Alist服务器URL
        "token": "",  # Alist服务器Token
        "quark_storage_id": "",  # Alist 服务器夸克存储 ID
        "save_storage_id": "",  # Alist 服务器同步的存储 ID
        "tv_mode": "",  # TV库模式，填入非0值开启
        # TV库模式说明：
        # 1.开启后，会验证文件名是否包含S01E01的正则，格式目前仅包含mp4及mkv，
        # 2.会对比保存目录下是否存在该名称的mp4、mkv文件，如果不存在才会进行同步
        # 3.夸克目录及同步目录均会提取为S01E01的正则进行匹配，不受其它字符影响
    }
    is_active = False
    # 缓存参数

    default_task_config = {
        "enable": False,  # 当前任务开关，
        "save_path": "",  # 需要同步目录，默认空时路径则会与夸克的保存路径一致，不开启完整路径模式时，默认根目录为保存驱动的根目录
        "verify_path": "",  # 验证目录，主要用于影视库避免重复文件，一般配合alist的别名功能及full_path_mode使用，用于多个网盘的源合并成一个目录
        "full_path_mode": False,  # 完整路径模式
        # 完整路径模式开启后不再限制保存目录的存储驱动，将根据填入的路径进行保存，需要填写完整的alist目录
    }

    def __init__(self, **kwargs):
        if kwargs:
            for key, _ in self.default_config.items():
                if key in kwargs:
                    setattr(self, key, kwargs[key])
                else:
                    print(f"{self.__class__.__name__} 模块缺少必要参数: {key}")
            if self.url and self.token:
                if self.verify_server():
                    self.is_active = True

    def _send_request(self, method, url, **kwargs):
        headers = {"Authorization": self.token, "Content-Type": "application/json"}
        if "headers" in kwargs:
            headers = kwargs["headers"]
            del kwargs["headers"]
        try:
            response = requests.request(method, url, headers=headers, **kwargs)
            # print(f"{response.text}")
            # response.raise_for_status()  # 检查请求是否成功，但返回非200也会抛出异常
            return response
        except Exception as e:
            print(f"_send_request error:\n{e}")
            fake_response = requests.Response()
            fake_response.status_code = 500
            fake_response._content = b'{"status": 500, "message": "request error"}'
            return fake_response

    def verify_server(self):
        url = f"{self.url}/api/me"
        querystring = ""
        headers = {"Authorization": self.token, "Content-Type": "application/json"}
        try:
            response = requests.request("GET", url, headers=headers, params=querystring)
            response.raise_for_status()
            response = response.json()
            if response.get("code") == 200:
                if response.get("data").get("username") == "guest":
                    print(f"Alist登陆失败，请检查token")
                else:
                    print(
                        f"Alist登陆成功，当前用户: {response.get('data').get('username')}"
                    )
                    return True
            else:
                print(f"Alist同步: 连接服务器失败❌ {response.get('message')}")
        except requests.exceptions.RequestException as e:
            print(f"获取Alist信息出错: {e}")
        return False

    def run(self, task, **kwargs):
        if not task["addition"]["alist_sync"]["enable"]:
            return 0
        print(f"开始进行alist同步")
        # 这一块注释的是获取任务的参数，在web界面可以看
        # print("所有任务参数：")
        # print(task)
        # print(task['addition'])
        # print(task['addition']['alist_sync'])
        # print(task['addition']['alist_sync']['target_path'])

        # 获取夸克挂载根目录
        data = self.get_storage_path(self.quark_storage_id)
        if data["driver"] != "Quark":
            print(
                f"Alist同步: 存储{self.quark_storage_id}非夸克存储❌ {data['driver']}"
            )
            return 0
        quark_mount_root_path = re.sub(r".*root_folder_id\":\"", "", data["addition"])
        quark_mount_root_path = re.sub(r"\",.*", "", quark_mount_root_path)
        if quark_mount_root_path != "0" and quark_mount_root_path != "":
            print(f"Alist同步: 存储{self.quark_storage_id}挂载的目录非夸克根目录❌")
            return 0
        self.quark_mount_path = data["mount_path"]

        # 获取保存路径的挂载根目录
        if self.save_storage_id != 0:
            data = self.get_storage_path(self.save_storage_id)
            self.save_mount_path = data["mount_path"]

        # 保存的目录初始化
        if task["addition"]["alist_sync"]["save_path"] == "":
            self.save_path = f"{self.save_mount_path}/{task['savepath']}"
        else:
            self.save_path = task["addition"]["alist_sync"]["save_path"]
            if not task["addition"]["alist_sync"]["full_path_mode"]:
                if self.save_path.startswith("/"):
                    self.save_path = self.save_path[1:]
                if self.save_path.endswith("/"):
                    self.save_path = self.save_path[:-1]
                self.save_path = f"{self.save_mount_path}/{self.save_path}"
            else:
                # print('完整路径模式')
                if not self.save_path.startswith("/"):
                    self.save_path = "/" + self.save_path
                if self.save_path.endswith("/"):
                    self.save_path = self.save_path[:-1]

        # 获取保存目录是否存在
        if not self.get_path(self.save_path):
            dir_exists = False
            # 如果目录不存在判断两边路径是否一致，一致时直接创建复制目录任务即可

        else:
            dir_exists = True
            copy_dir = False

        # 初始化验证目录
        # 如果没有填验证目录，则验证目录与保存目录一致

        if task["addition"]["alist_sync"]["verify_path"]:
            self.verify_path = task["addition"]["alist_sync"]["verify_path"]
            if not task["addition"]["alist_sync"]["full_path_mode"]:
                if self.verify_path.startswith("/"):
                    self.verify_path = self.save_path[1:]
                if self.verify_path.endswith("/"):
                    self.verify_path = self.save_path[:-1]
                self.verify_path = f"{self.save_mount_path}/{self.verify_path}"
            else:
                # print('完整路径模式')
                if not self.verify_path.startswith("/"):
                    self.verify_path = "/" + self.save_path
                if self.verify_path.endswith("/"):
                    self.verify_path = self.save_path[:-1]
        else:
            self.verify_path = self.save_path

        # 初始化夸克目录
        self.source_path = f"{self.quark_mount_path}/{task['savepath']}"
        # 初始化任务名
        self.taskname = f"{task['taskname']}"

        # 获取网盘已有文件
        source_dir_list = self.get_path_list(self.source_path)
        if not source_dir_list:
            print("获取夸克文件列表失败，请检查网络或手动刷新alist中的夸克目录")
            return 0
        if self.tv_mode == 0 or self.tv_mode == "":
            self.tv_mode = False
        else:
            self.tv_mode = True

        # 如果是新建的目录则将所有文件直接复制
        if not dir_exists:
            self.get_save_file([], source_dir_list)
        else:
            verify_dir_list = self.get_path_list(self.verify_path)
            if verify_dir_list:
                self.get_save_file(verify_dir_list, source_dir_list)
            else:
                self.get_save_file([], source_dir_list)

        if self.save_file_data:
            self.save_start(self.save_file_data)
            print("同步的文件列表：")
            for save_file in self.save_file_data:
                print(f"└── 🎞️{save_file}")
        else:
            print("没有需要同步的文件")

    def save_start(self, save_file_data):
        url = f"{self.url}/api/fs/copy"
        payload = json.dumps(
            {
                "src_dir": self.source_path,
                "dst_dir": self.save_path,
                "names": save_file_data,
            }
        )
        response = self._send_request("POST", url, data=payload)
        if response.status_code != 200:
            print("未能进行Alist同步，请手动同步")
        else:
            print("Alist创建任务成功")
        self.copy_task = response.json()

    def get_save_file(self, target_dir_list, source_dir_list):
        self.save_file_data = []
        if target_dir_list == []:
            for source_list in source_dir_list:
                if self.tv_mode:
                    if re.search(
                        self.taskname + r"\.s\d{1,3}e\d{1,3}\.(mkv|mp4)",
                        source_list["name"],
                        re.IGNORECASE,
                    ):
                        self.save_file_data.append(source_list["name"])
                else:
                    self.save_file_data.append(source_list["name"])
        else:
            for source_list in source_dir_list:
                skip = False
                source_list_filename = (
                    source_list["name"]
                    .replace(".mp4", "")
                    .replace(".mkv", "")
                    .replace(self.taskname + ".", "")
                    .lower()
                )
                for target_list in target_dir_list:
                    if source_list["is_dir"]:
                        # print(f"跳过目录同步")
                        skip = True
                        break
                    if self.tv_mode:
                        target_list_filename = (
                            target_list["name"]
                            .replace(".mp4", "")
                            .replace(".mkv", "")
                            .replace(self.taskname + ".", "")
                            .lower()
                        )
                        if source_list_filename == target_list_filename:
                            # print(f"文件存在，名称为：{target_list['name']}")
                            skip = True
                            break
                    else:
                        if source_list["name"] == target_list["name"]:
                            # print(f"文件存在，名称为：{target_dir['name']}")
                            skip = True
                            break
                    if self.tv_mode:
                        if re.search(
                            self.taskname + r"\.s\d{1,3}e\d{1,3}\.(mkv|mp4)",
                            source_list["name"],
                            re.IGNORECASE,
                        ):
                            # 添加一句验证，如果有MKV，MP4存在时，则只保存某一个格式
                            if re.search(
                                self.taskname + r"\.s\d{1,3}e\d{1,3}\.mp4",
                                source_list["name"],
                                re.IGNORECASE,
                            ):
                                for all_file in source_dir_list:
                                    if (
                                        source_list["name"].replace(".mp4", ".mkv")
                                        == all_file["name"]
                                    ):
                                        print(
                                            f"{source_list['name']}拥有相同版本的MKV文件，跳过复制"
                                        )
                                        skip = True
                if not skip:
                    self.save_file_data.append(source_list["name"])

    def get_path_list(self, path):
        url = f"{self.url}/api/fs/list"
        payload = json.dumps(
            {"path": path, "password": "", "page": 1, "per_page": 0, "refresh": True}
        )
        response = self._send_request("POST", url, data=payload)
        if response.status_code != 200:
            print(f"获取Alist目录出错: {response}")
            return False
        else:
            return response.json()["data"]["content"]

    def get_path(self, path):
        url = f"{self.url}/api/fs/list"
        payload = json.dumps({"path": path, "password": "", "force_root": False})
        response = self._send_request("POST", url, data=payload)
        if response.status_code != 200 or response.json()["message"] != "success":
            return False
        else:
            return True

    def get_storage_path(self, storage_id):
        url = f"{self.url}/api/admin/storage/get"
        headers = {"Authorization": self.token}
        querystring = {"id": storage_id}
        try:
            response = requests.request("GET", url, headers=headers, params=querystring)
            response.raise_for_status()
            data = response.json()
            if data.get("code") == 200:
                return data.get("data", [])
            else:
                print(f"Alist同步: 存储{storage_id}连接失败❌ {data.get('message')}")
        except Exception as e:
            print(f"Alist同步: 获取Alist存储出错 {e}")
        return []
