



import os
os.environ['NO_PROXY'] = 'stackoverflow.com'
import logging
logging.captureWarnings(True)
from DrissionPage import ChromiumPage,ChromiumOptions
import time
from datetime import datetime, timedelta
from time import sleep
from random import randint
import requests
import math
import pandas as pd
import redis
import json
from pathlib import Path
import re



class TkOrderExport():
    def __init__(self):
        self.page = ChromiumPage()
        # 修改请求头
        self.headers = {
            'accept': '*/*',
            'accept-language': 'en-US,en;q=0.9', # 'en-US,en;q=0.9'
            'cache-control': 'no-cache',
            'content-type': 'application/json; charset=UTF-8',
            'origin': 'https://www.tiktok.com',
            'pragma': 'no-cache',
            'priority': 'u=1, i',
            'referer': 'https://www.tiktok.com/',
            'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Windows"',
            'sec-fetch-dest': 'empty',
            'sec-fetch-mode': 'cors',
            'sec-fetch-site': 'cross-site',
            'sec-fetch-storage-access': 'active',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
        }
        self.shop_code_ceshi = "CNUSCBS2EL7D"

        # 店铺账号
        self.account = 'qiuaner.tiktok@outlook.com'
        # 下载文件路径
        self.download_folder = r"D:\Downloads"
        self.file_suffix = ".xlsx"  # 设置你期望的文件名后缀（包括点）
        self.receiver_name = 'wangjing5'

        # Redis 配置信息
        self.REDIS_CONFIG =  {
            'host': '120.79.147.190',
            'port': 6379,
            'password': 'fG7#vT6kQ1pX',
            'db': 13,
            'decode_responses': True
        }

        # chrome_options = ChromiumOptions()
        # chrome_options.set_browser_path(r'C:\Program Files\Google\Chrome\Application\chrome.exe')
        # chrome_options.set_local_port(9333)  # 设置 Chrome 的调试端口
        #
        # self.page = ChromiumPage(addr_or_opts=chrome_options)
        # print(f"Chrome 浏览器运行在端口: {9333}")

        self.url = "https://affiliate.tiktokglobalshop.com/product/order?shop_region=US"
        self.cookies = self.get_cookies()

    def login_with_cookies(self):
        # 检查是否登录状态
        try:
            self.page.get(self.url)
            self.page.set.window.max()
            sleep(randint(1, 3))

            tou_xiang = self.page.ele('xpath://span[@class="m4b-avatar-image"]', timeout=10)
            tou_xiang.click()
            sleep(randint(1, 3))

            self.shop_code = self.page.ele('xpath://div[@class="text-body-s text-neutral-text4"]/span',
                                           timeout=10).text
            self.shop_code = self.shop_code.split(': ')[1]
            print('店铺编号：', self.shop_code)
            sleep(randint(1, 3))

            if self.shop_code and str(self.shop_code) == self.shop_code_ceshi:
                print("在登录状态")

            else:
                print("不一样")
                raise
        except:
            print("需要登录")
            try:
                # 打开一个空白页，否则无法设置 cookie
                self.page.get('https://www.tiktok.com')

                # 设置 cookies
                for ck in self.cookies:
                    self.page.set.cookies(ck)

                # 重新打开目标网址，此时应为已登录状态
                self.page.get(self.url)
                time.sleep(5)

            except:
                print("登录失败")

    def save_cookie(self, cookies):
        """将整个 cookies 列表一次性存入 Redis，作为一条 JSON 数据"""
        key = f"tk_shop_cookie:{self.shop_code}:order:list"

        # 将整个 cookies 列表转换为 JSON 字符串
        value = json.dumps(cookies, ensure_ascii=False)

        # 存储到 Redis（覆盖旧数据）
        self.r.set(key, value)
        print(f"💾 已将 {len(cookies)} 个 cookie 存入 Redis，键为: {key}")

    def get_cookies(self):
        self.r = redis.StrictRedis(**self.REDIS_CONFIG)
        """从 Redis 中获取并解析 cookies"""
        key = f"tk_shop_cookie:{self.shop_code_ceshi}:order:list"
        cookie_json = self.r.get(key)

        if cookie_json:
            cookies = json.loads(cookie_json)
            print(f"🔄 从 Redis 成功读取 {len(cookies)} 个 cookie")
            return cookies
        else:
            print("❌ 未找到对应的 cookie 数据")
            return None

    def change_language(self):
        self.login_with_cookies()
        retry_num = 0
        max_retry = 2  # 最多重试次数
        while retry_num <= max_retry:
            try:
                self.page.get(self.url)
                self.page.set.window.max()
                sleep(randint(1, 3))

                tou_xiang = self.page.ele('xpath://span[@class="m4b-avatar-image"]', timeout=10)
                tou_xiang.click()
                sleep(randint(1, 3))

                self.store_name = self.page.ele('xpath://div[@class="text-body-m-medium text-neutral-text1"]',
                                                timeout=10).text
                print('店铺名：', self.store_name)
                sleep(randint(1, 3))

                self.shop_code = self.page.ele('xpath://div[@class="text-body-s text-neutral-text4"]/span',
                                               timeout=10).text
                self.shop_code = self.shop_code.split(': ')[1]
                print('店铺编号：', self.shop_code)
                sleep(randint(1, 3))

                language_element = self.page.ele(
                    'xpath=(//span[@class="text-neutral-text1 text-body-m-regular"])[3]', timeout=10)
                language = language_element.text
                print('目前的语言是：', language)
                sleep(randint(1, 3))

                if language != 'English':
                    print('需要转换成英文')
                    self.page.actions.move_to(ele_or_loc=language_element)
                    sleep(randint(1, 3))
                    self.page.ele('xpath://div[text()="English"]', timeout=10).click()
                    sleep(randint(1, 3))
                    language_element = self.page.ele('xpath=(//span[@class="text-neutral-text1 text-body-m-regular"])[3]', timeout=10)
                    language = language_element.text
                    if language == 'English':
                        print('已转换成英文')

                        # 调用 get_tk_order 并判断是否成功获取数据
                        success = self.get_tk_order()
                        if not success:
                            print("没有订单数据，终止流程。")
                            return  # 不再继续执行后续操作

                        cookies = self.page.cookies()
                        self.save_cookie(cookies)
                        self.send_success_message_via_wechat()

                    else:
                        print('转换英文失败,暂停...')

                else:
                    print('是英文')
                    # 同样判断是否成功获取数据
                    success = self.get_tk_order()
                    if not success:
                        print("没有订单数据，终止流程。")
                        return

                    cookies = self.page.cookies()
                    self.save_cookie(cookies)
                    self.send_success_message_via_wechat()
                break

            except Exception as e:
                print(f"change_language出现错误: {e}")
                self.send_error_notification_via_wechat(e)
                if "没有" in str(e):
                    retry_num += 1
                    sleep(5)
                else:
                    raise

    def get_tk_order(self):
        export_orders = self.page.ele('xpath://span[text()="Export orders"]', timeout=10)
        export_orders.click()
        print('点击Export orders')
        sleep(randint(2, 5))

        # 获取开始时间
        start_time_input = self.page.ele(f'xpath://input[@placeholder="Start date"]')
        self.start_time = start_time_input.attr("value")
        print(self.start_time)

        # 获取结束时间
        end_time_input = self.page.ele(f'xpath://input[@placeholder="End date"]')
        self.end_time = end_time_input.attr("value")
        print(self.end_time)
        time.sleep(2)

        sleep(randint(3, 6))
        print('点击Excel')
        excel = self.page.ele('xpath://div[text()="Excel"]', timeout=10).click()

        print('点击Export')
        export = self.page.ele('xpath://span[text()="Export"]', timeout=10).click()
        print('等待导出...')
        sleep(randint(60, 80))

        try:
            # 判断是否存在无订单提示
            no_orders_element = self.page.ele(
                'xpath://div[text()="There are no orders within the date range you selected"]',
                timeout=10
            )
            if no_orders_element:
                print("未找到订单数据，发送无数据通知。")
                self.send_no_data_message_via_wechat()
                return False  # 表示失败或无数据

            # 如果有数据，则继续下载文件等操作
            file_name = self.page.ele(
                'xpath:(//div[@class="arco-list-item-content"]//span[@data-e2e="16edc1ab-98cd-1ef8"])[1]',
                timeout=10
            ).text
            print('file_name: ', file_name)

            download = self.page.ele(
                'xpath:(//div[@class="arco-list-content arco-list-virtual"]//span[text()="Download"])[1]',
                timeout=10
            ).click()
            print('已点击下载')
            sleep(randint(10, 20))
            self.save_to_redis(file_name)

            return True  # 成功获取并处理了订单数据

        except Exception as e:
            print(f"get_tk_order出现错误: {e}")

    def connect_redis(self):
        """建立 Redis 连接"""
        self.r = redis.StrictRedis(**self.REDIS_CONFIG)
        try:
            self.r.ping()  # 测试连接
            print("✅ 成功连接到 Redis")
        except redis.exceptions.ConnectionError as e:
            print(f"❌ 无法连接到 Redis: {e}")
            raise

    def read_excel(self, file_path):
        """读取 Excel 文件内容，并防止数值被转为科学计数法"""
        print(f"📄 正在读取文件：{file_path}")

        # 假设 'Order ID'、'Product ID' 这类字段容易被转成科学计数法
        columns_to_keep_as_string = ['Order ID', 'Product ID']  # 替换为你实际的列名

        # 构造 dtype 字典，强制这些列为字符串类型
        dtype_mapping = {col: str for col in columns_to_keep_as_string}

        # 读取 Excel，指定某些列为字符串
        df = pd.read_excel(file_path, dtype=dtype_mapping)

        data = df.to_dict(orient='records')  # 转换为字典列表
        print(f"📊 已读取 {len(data)} 条记录")
        return data

    def store_data_in_redis(self, r, data, shop_code):
        """将数据存储到 Redis 中，所有相同 shop_code 的记录使用同一个列表结构存储"""
        key = f"tk_shop_order:{shop_code}:order:list"

        for idx, record in enumerate(data):
            # 替换 NaN 为 空字符串
            cleaned_record = {
                key: ("" if isinstance(value, float) and math.isnan(value) else value)
                for key, value in record.items()
            }

            # 序列化前确保没有 NaN
            value = json.dumps(cleaned_record, ensure_ascii=False)

            r.rpush(key, value)
            print(f"💾 存储键: {key}，新增记录")

    def find_latest_file(self, file_name, max_retries=1, retry_interval=10):
        """
        查找最新生成的 .xlsx 文件，若未找到则重试一次。

        :param file_name: 文件名前缀
        :param max_retries: 最大重试次数
        :param retry_interval: 每次重试间隔时间（秒）
        :return: 最新文件路径（字符串）
        """
        retry_count = 0

        while retry_count <= max_retries:
            download_path = Path(self.download_folder)

            # 查找所有匹配的 .xlsx 文件
            matching_files = [
                f for f in download_path.iterdir()
                if f.is_file() and f.name.startswith(file_name) and f.suffix == '.xlsx'
            ]

            if matching_files:
                # 定义一个从文件名提取时间戳的函数（假设时间戳是14位数字）
                def extract_timestamp(file_path):
                    match = re.search(r'\d{14}', file_path.name)
                    return match.group(0) if match else None

                # 根据文件名中的时间戳排序，取最新的文件
                latest_file = max(matching_files, key=lambda f: extract_timestamp(f) or '0')
                return str(latest_file)

            # 没有找到文件时的处理
            if retry_count < max_retries:
                print(f"未找到以 '{file_name}' 开头的 .xlsx 文件，第 {retry_count + 1} 次重试...")
                time.sleep(retry_interval)
            retry_count += 1

        # 超出最大重试次数仍未找到
        raise FileNotFoundError(
            f"在 {self.download_folder} 中经过 {max_retries} 次重试，"
            f"仍未找到以 '{file_name}' 开头的 .xlsx 文件"
        )

    def save_to_redis(self, file_name):
        EXCEL_FILE = self.find_latest_file(file_name)
        print(f'保存文件：{EXCEL_FILE}')
        data = self.read_excel(EXCEL_FILE)
        self.store_data_in_redis(self.r, data, self.shop_code)
        print(f"完成保存到redis")

    def send_success_message_via_wechat(self):
        webhook_url = 'http://47.112.96.71:8082/selection/sendMessage'  # 替换为你的企业微信机器人的Webhook URL
        data = {
            "account": self.receiver_name,
            "title": '【TK订单导出成功提醒】',
            "content": f'账号: {self.account}, 店铺: {self.store_name},下载文件范围时间：{self.start_time} - {self.end_time}, 程序运行时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
        }

        response = requests.post(url=webhook_url, data=data, timeout=15)
        if response.status_code == 200:
            print("已成功发送通知到企业微信")
        else:
            print(f"发送通知失败: {response.text}")

    def send_error_notification_via_wechat(self, error_message):
        webhook_url = 'http://47.112.96.71:8082/selection/sendMessage'  # 替换为你的企业微信机器人的Webhook URL
        data = {
            "account": self.receiver_name,
            'title': '【TK订单导出异常提醒】',
            'content': f'账号：{self.account}，店铺：{self.store_name},错误信息：{error_message}, 时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
        }

        response = requests.post(url=webhook_url, data=data, timeout=15)
        if response.status_code == 200:
            print("已成功发送错误通知到企业微信")
        else:
            print(f"发送错误通知失败: {response.text}")

    def send_no_data_message_via_wechat(self):
        webhook_url = 'http://47.112.96.71:8082/selection/sendMessage'  # 替换为你的企业微信机器人的Webhook URL
        data = {
            "account": self.receiver_name,
            'title': '【TK订单无数据提醒】',
            'content': f'账号：{self.account}，店铺：{self.store_name},在此时间内无订单数据：{self.start_time} - {self.end_time},, 时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
        }

        response = requests.post(url=webhook_url, data=data, timeout=15)
        if response.status_code == 200:
            print("已成功发送错误通知到企业微信")
        else:
            print(f"发送错误通知失败: {response.text}")

    def run(self):
        self.connect_redis()
        self.change_language()
        self.page.quit()

if __name__ == '__main__':
    TkOrderExport().run()









































