# Source: https://github.com/lksky8/sign-ql/blob/main/PPark.py
# Raw: https://raw.githubusercontent.com/lksky8/sign-ql/main/PPark.py
# Repo: lksky8/sign-ql
# Path: PPark.py
# UploadedAt: 2025-09-22T11:01:17Z
# SHA256: 120e73308bc1ccfb9db8ebd87357364850257a25ba1bc039d67c2967fe6c8e19
# Category: web版/账密
# Evidence: web/H5关键词 + username/password/login

"""
PP停车签到

作者：https://github.com/lksky8/sign-ql
最后更新日期：2025-9-22
食用方法：变量输入export pp_token=账号1#token1&账号2#token2
支持多用户运行
多用户用&或者@隔开
例如账号1：13800000000#eyJhbGciOiJIUzI1NiJ9.eyj 账号2： 139000000000#eyJhbGciOiJIUzI1NiJ9.eyj
则变量为
export pp_token="13800000000#eyJhbGciOiJIUzI1NiJ9.eyj&13900000000#eyJhbGciOiJIUzI1NiJ9.eyj"
每天运行两次否则token会过期

cron: 0 40 0,12 * * *
"""
import requests
import base64
from urllib.parse import quote
import json
import hashlib
import time
import os
import re

KEY_ENCRYPT = "2363ECDFC54A5AF12477D3D45333A19F"  # key1 用于加密
KEY_DECRYPT = "466d67cf8f9810707404fae5ed172b8e"  # key2 用于解密
WX_ENCRYPT = "riegh^ee:w0fok5je5eeS{eecaes1nep"

send_msg = ''
one_msg = ''

def Log(cont=''):
    global send_msg, one_msg
    if cont:
        one_msg += f'\n{cont}'
        send_msg += f'\n{cont}'



def send_notification_message(title):
    try:
        from notify import send
        print("加载通知服务成功！")
        send(title, send_msg)
    except Exception as e:
        if e:
            print('发送通知消息失败！')

api_headers = {
    'Host': 'api.660pp.com',
    'accept': '*/*',
    'content-type': 'application/x-www-form-urlencoded',
    'rest_api_type': '1',
    'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Parking/4.3.2 (iOS 16.6.1; iPhone15,2; Build/1312) NetType/WIFI',
    'accept-language': 'zh_CN',
}



wx_headers = {
    'Host': 'user-api.4pyun.com',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF WindowsWechat(0x63090a13) UnifiedPCWindowsWechat(0xf2541022) XWEB/16467',
    'xweb_xhr': '1',
    'content-type': 'application/x-www-form-urlencoded',
    'accept': '*/*',
    'sec-fetch-site': 'cross-site',
    'sec-fetch-mode': 'cors',
    'sec-fetch-dest': 'empty',
    'referer': 'https://servicewechat.com/wxa204074068ad40ef/879/page-frame.html',
    'accept-language': 'zh-CN,zh;q=0.9',
    'priority': 'u=1, i',
}

def transform(bytes_data: bytearray, key: str) -> bytearray:
    len_key = len(key)
    len_b = len(bytes_data)
    for i in range(len_b):
        index = (len_b - i) % len_key
        key_byte = ord(key[index])
        in_byte = bytes_data[i]
        flipped = in_byte ^ 0xFF
        bytes_data[i] = key_byte ^ flipped
    return bytes_data

def encrypt(arg: str, key: str) -> str:
    """加密，对应提供的 encrypt 方法"""
    bytes_data = bytearray(arg.encode('utf-8'))
    transformed = transform(bytes_data, key)
    return base64.b64encode(transformed).decode('utf-8')

def decrypt(arg: str) -> str:
    """解密，对应提供的 decrypt 方法"""
    bytes_data = bytearray(base64.b64decode(arg))
    transformed = transform(bytes_data, KEY_DECRYPT)
    return transformed.decode('utf-8')

def save_or_update_phone_data(phone_key, new_data, file_path='PPark_data.json'):
    if os.path.exists(file_path):
        with open(file_path, 'r', encoding='utf-8') as f:
            existing_data = json.load(f)
    else:
        existing_data = {}

    if phone_key in existing_data:
        existing_data[phone_key].update(new_data)  # 合并新旧数据
        print(f"[{phone_key}] 数据已更新！")
    else:
        existing_data[phone_key] = new_data  # 新增数据
        print(f"[{phone_key}] 新增数据！")

    with open(file_path, 'w', encoding='utf-8') as f:
        json.dump(existing_data, f, ensure_ascii=False, indent=4)

def get_user_ids_and_tokens(file_path='PPark_data.json'):
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            phone_data = json.load(f)
        results = []
        for phone_key, data in phone_data.items():
            token = data.get('refresh_token')
            if token:
                results.append({
                    'phone_key': phone_key,
                    'refresh_token': token,
                    'user_name': data.get('user_name'),
                    'user_id': data.get('user_id')
                })
        return results
    except FileNotFoundError:
        print(f"文件 {file_path} 不存在！")
        return []
    except json.JSONDecodeError:
        print(f"文件 {file_path} 格式错误！")
        return []

def get_userdata(u):
    user_phone = u.split('#')[0]
    user_token = u.split('#')[1]

    try:
        with open('PPark_data.json', 'r', encoding='utf-8') as f:
            cache_data = json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        cache_data = {}

    if user_phone not in cache_data:
        print(f'[{user_phone}] 不存在于缓存中，尝试初始化数据')
        init_refresh_token(user_phone,user_token)
    else:
        print(f'[{user_phone}] 已存在于缓存中，直接读取数据')
        refresh_token(user_phone,user_token,cache_data[user_phone]['refresh_token'])
        old_user_token = cache_data[user_phone]['init_token']
        if old_user_token != user_token:
            print(f'[{user_phone}] token已更新，尝试刷新')
            init_refresh_token(user_phone,user_token)
        

def md5_encrypt(s: str):
    return hashlib.md5(s.encode('utf-8')).hexdigest()


def get_user_info(phone: str, token: str):
    headers = wx_headers.copy()
    headers['authorization'] = f'Bearer {token}'
    try:
        response = requests.get('https://user-api.4pyun.com/rest/2.0/user/whoami',headers=headers)
        response.raise_for_status()
        response_json = response.json()
        if response_json['code'] == '1001':
            print(f"[{response_json['payload']['nickname']}] 获取用户信息：成功")
            return response_json['payload']['nickname'], response_json['payload']['identity']
        else:
            print(f"[{phone}] 获取用户信息：失败：{response_json}")
            return None
    except requests.RequestException as e:
        print(f"[{phone}] 获取用户信息_请求异常：{e}")
    except json.JSONDecodeError:
        print(f"[{phone}] 获取用户信息_响应不是有效的JSON格式")
    except Exception as e:
        print(f"[{phone}] 获取用户信息_其他异常：{e}")

def refresh_token(mobile: str, i_token: str, r_token: str):
    headers = api_headers.copy()
    headers['rest_api_token'] = r_token
    try:
        response = requests.put('https://api.660pp.com/rest/1.6/user/token', headers=headers)
        response_json = response.json()
        # print(response_json)
        if response_json['code'] == '1001':
            result = json.loads(decrypt(response_json['result']))
            print(f"[{mobile}] 刷新token成功")
            save_or_update_phone_data(mobile,{'init_token': i_token,'refresh_token': result['token']})
        elif response_json['code'] == '401':
            print(f"[{mobile}] 刷新token失败：token已失效")
            Log(f"[{mobile}] 刷新token失败：token已失效")
        else:
            print(f"[{mobile}] 刷新token失败：{response_json}")
    except requests.RequestException as e:
        print(f"[{mobile}] 刷新token_请求异常：{e}")
    except json.JSONDecodeError:
        print(f"[{mobile}] 刷新token_响应不是有效的JSON格式")
    except Exception as e:
        print(f"[{mobile}] 刷新token_其他异常：{e}")


def init_refresh_token(mobile: str, token: str):
    headers = api_headers.copy()
    headers['rest_api_token'] = token
    try:
        response = requests.put('https://api.660pp.com/rest/1.6/user/token', headers=headers)
        response_json = response.json()
        # print(response_json)
        if response_json['code'] == '1001':
            result = json.loads(decrypt(response_json['result']))
            user_name, user_id = get_user_info(mobile, token)
            save_or_update_phone_data(mobile,{'user_name': user_name, 'user_id': user_id, 'init_token': token, 'refresh_token': result['token']})
            print(f"[{mobile}] 刷新token成功")
        elif response_json['code'] == '401':
            print(f"[{mobile}] 刷新token失败：token已失效")
            Log(f"[{mobile}] 刷新token失败：token已失效")
        else:
            print(f"[{mobile}] 刷新token失败：{response_json}")
    except requests.RequestException as e:
        print(f"[{mobile}] 刷新token_请求异常：{e}")
    except json.JSONDecodeError:
        print(f"[{mobile}] 刷新token_响应不是有效的JSON格式")
    except Exception as e:
        print(f"[{mobile}] 刷新token_其他异常：{e}")

def day_sign(nickname: str, token: str):
    headers = api_headers.copy()
    headers['rest_api_token'] = token
    try:
        response = requests.get('https://api.660pp.com/rest/1.6/reward/bonus?yc7OyqO6qQ%3D%3D=rL7JhL/NrriG2tPZ2aegog%3D%3D', headers=headers)
        response.raise_for_status()
        response_json = response.json()
        if response_json['code'] == '1001':
            result = json.loads(decrypt(response_json['result']))
            message = result['message'].replace('\r\n', '')
            if '签到获得' in result['message'] or '已连续签到'in result['message']:
                print(f"[{nickname}] 签到成功：{message} 已累计获得：{result['combo']} 积分")
                Log(f"[{nickname}] 签到成功：{message} 已累计获得：{result['combo']} 积分")
            elif message == '您已签到成功' :
                print(f"[{nickname}] 签到失败：已签到")
                Log(f"[{nickname}] 已签到")
            else:
                print(f"[{nickname}] 签到失败：{message}")
                Log(f"[{nickname}] 签到失败：{message}")
        else:
            print(f"[{nickname}] 获取签到数据失败：{response_json}")
    except requests.RequestException as e:
        print(f"[{nickname}] 签到_请求异常：{e}")
    except json.JSONDecodeError:
        print(f"[{nickname}] 签到_响应不是有效的JSON格式")
    except Exception as e:
        print(f"[{nickname}] 签到_其他异常：{e}")

def get_task(nickname: str, token: str):
    headers = wx_headers.copy()
    headers['authorization'] = f'Bearer {token}'
    try:
        response = requests.get('https://user-api.4pyun.com/rest/2.0/bonus/reward/task/list?%2B9Hnx%2FPy=7bL7p%2FqgoK77uPOi%2B8WjqP%2Fw', headers=headers)
        response.raise_for_status()
        response_json = response.json()
        if response_json['code'] == '1001':
            video_task = [task for task in response_json['payload']['row'] if task['name'] == '看视频得积分'][0]
            referer_url = video_task['referer_url']
            print(f'[{nickname}] 获取视频任务数据成功')
            return referer_url.split('&voucher=')[1]
        else:
            print(f"[{nickname}] 获取任务_失败：{response_json}")
            Log(f"[{nickname}] 获取视频任务数据失败：{response_json}")
            return None
    except requests.RequestException as e:
        print(f"[{nickname}] 获取任务_请求异常：{e}")
    except json.JSONDecodeError:
        print(f"[{nickname}] 获取任务_响应不是有效的JSON格式")
    except Exception as e:
        print(f"[{nickname}] 获取任务_其他异常：{e}")

def complete_task(nickname: str, token: str, task_id: str):
    headers = wx_headers.copy()
    headers['authorization'] = f'Bearer {token}'
    headers['content-type'] = 'application/json;charset=utf-8'
    try:
        data = '{"app_id":"wxa204074068ad40ef","purpose":"reward:motivate:advertising","voucher":"voucher_data"}'.replace('voucher_data', task_id)
        encrypt_data = encrypt(data,WX_ENCRYPT)
        response = requests.post('https://user-api.4pyun.com/rest/2.0/bonus/reward/task/complete', headers=headers, data=encrypt_data)
        response.raise_for_status()
        response_json = response.json()
        if response_json['code'] == '1001':
            print(f"[{nickname}] 看视频：任务完成")
            headers['content-type'] = 'application/x-www-form-urlencoded'
            response = requests.get('https://user-api.4pyun.com/rest/2.0/bonus/reward/acquire?%2B9Hnx%2FPy=7bL7p%2FqgoK77uPOi%2B8WjqP%2Fw&6u%2FT5%2Ffp8w%3D%3D=%2Fv%2Fp%2Fej%2BvsH17qPs9L7xqvir%2FqDo7sjk8fTx', headers=headers)
            response.raise_for_status()
            response_json = response.json()
            if response_json['code'] == '1001':
                print(f"[{nickname}] 看视频：任务奖励已领取，{response_json['payload']['message']}")
                Log(f"[{nickname}] 看视频：任务奖励已领取，{response_json['payload']['message']}")
            elif response_json['message'] == '已达积分领取次数上限':
                print(f"[{nickname}] 看视频：积分已领取")
            else:
                print(f"[{nickname}] 看视频：{response_json['message']}")
                Log(f"[{nickname}] 看视频：{response_json['message']}")
        else:
            print(f"[{nickname}] 看视频任务失败：{response_json}")
            Log(f"[{nickname}] 看视频任务失败：{response_json}")
    except requests.RequestException as e:
        print(f"[{nickname}] 看视频任务_请求异常：{e}")
    except json.JSONDecodeError:
        print(f"[{nickname}] 看视频任务_响应不是有效的JSON格式")
    except Exception as e:
        print(f"[{nickname}] 看视频任务_其他异常：{e}")

def reward_balance(nickname: str, token: str, user_id: str):
    headers = wx_headers.copy()
    headers['authorization'] = f'Bearer {token}'
    try:
        response = requests.get(f'https://user-api.4pyun.com/rest/2.0/reward/balance?rP7%2Fz%2BPx7u8%3D={quote(encrypt(user_id,WX_ENCRYPT))}&7%2BnE5cfz8g%3D%3D={quote(encrypt(user_id,WX_ENCRYPT))}&%2Fbb%2F6P7j4erz=pw%3D%3D', headers=headers)
        response.raise_for_status()
        response_json = response.json()
        if response_json['code'] == '1001':
            print(f"[{nickname}] 积分余额：{response_json['payload']['balance']}")
            Log(f"[{nickname}] 积分余额：{response_json['payload']['balance']}")
        else:
            print(f"[{nickname}] 获取积分余额_失败：{response_json}")
            Log(f"[{nickname}] 获取积分余额_失败：{response_json}")
    except requests.RequestException as e:
        print(f"[{nickname}] 获取积分余额_请求异常：{e}")
    except json.JSONDecodeError:
        print(f"[{nickname}] 获取积分余额_响应不是有效的JSON格式")
    except Exception as e:
        print(f"[{nickname}] 获取积分余额_其他异常：{e}")

if __name__ == '__main__':
    if 'pp_token' in os.environ:
        pp_token = re.split("@|&", os.environ.get("pp_token"))
        print(f'查找到{len(pp_token)}个账号')
    else:
        pp_token = []
        print('请填入pp_token变量')
        Log('请填入pp_token变量')

    try:
        if pp_token:
            for pp_token_item in pp_token:
                get_userdata(pp_token_item)
            user_data = get_user_ids_and_tokens()
            if user_data:
                print(f"配置缓存中读取到{len(user_data)}个账号")
                print('*' * 50)
                z = 1
                for item in user_data:
                    print('-' * 50)
                    print(f'登录第{z}个账号>>>>>>')
                    phone_key = item.get('phone_key')
                    new_token = item.get('refresh_token')
                    user_name = item.get('user_name')
                    user_id = item.get('user_id')
                    if new_token:
                        day_sign(user_name,new_token)
                        for _ in range(2):
                            voucher = get_task(user_name, new_token)
                            if voucher:
                                complete_task(user_name, new_token, voucher)
                            time.sleep(5)
                        reward_balance(user_name,new_token,user_id)
                        time.sleep(3)
                    Log('-'*30)
                    z += 1
            else:
                print("未读取到用户数据，程序退出")
                exit(0)
    except Exception as e:
        print(f'程序异常：{e}')
    try:
        send_notification_message(title='PP停车')  # 发送通知
    except Exception as e:
        print('推送失败:' + str(e))
