# Source: https://github.com/Small-tailqwq/ql_script/blob/master/kuro/ql_kuro.py
# Raw: https://raw.githubusercontent.com/Small-tailqwq/ql_script/master/kuro/ql_kuro.py
# Repo: Small-tailqwq/ql_script
# Path: kuro/ql_kuro.py
# UploadedAt: 2026-05-23T15:50:58Z
# SHA256: 46013225510794598730a4bad8387ebfbcee626a372ce9a1e605ff7acbf18bac
# Category: web版/抓包
# Evidence: web/H5关键词 + cookie/token/header

# -*- coding: utf-8 -*-
"""
cron: 30 8 * * *
new Env('库街区自动签到');

基于 mxyooR/Kuro-autosignin (GPL-3.0) 精简适配青龙单文件版
上游版本: a334796 (2026-04-21)
最后更新: 2026-05-23 09:00 (UTC+8)
"""

import datetime
import json
import logging
import os
import random
import socket
import time
import uuid
from typing import Dict, List, Optional
import requests

try:
    from sendNotify import send
except Exception:
    try:
        from notify import send
    except Exception:
        send = None

TIMEOUT = 30
GAME_WUWA_ID = "3"
GAME_PGR_ID = "2"
SERVER_WUWA = "76402e5b20be2c39f095a152090afddc"
SERVER_PGR = "1000"
MAX_RETRIES = 3


class API:
    USER_MINE = "https://api.kurobbs.com/user/mineV2"
    USER_SIGN_IN = "https://api.kurobbs.com/user/signIn"
    USER_ROLE_LIST = "https://api.kurobbs.com/user/role/findRoleList"
    FORUM_LIST = "https://api.kurobbs.com/forum/list"
    FORUM_POST_DETAIL = "https://api.kurobbs.com/forum/getPostDetail"
    FORUM_LIKE = "https://api.kurobbs.com/forum/like"
    TASK_PROCESS = "https://api.kurobbs.com/encourage/level/getTaskProcess"
    TASK_SHARE = "https://api.kurobbs.com/encourage/level/shareTask"
    GAME_SIGN_IN = "https://api.kurobbs.com/encourage/signIn/v2"
    GAME_SIGN_RECORD = "https://api.kurobbs.com/encourage/signIn/queryRecordV2"


def get_ip_address() -> str:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(("8.8.8.8", 80))
        return s.getsockname()[0]
    except socket.error:
        return "127.0.0.1"
    finally:
        s.close()


class KuroSigner:
    def __init__(self, token_data: Dict[str, str]):
        self.token = token_data['token']
        self.note = token_data.get('note', '未命名账号')

        self.ip = get_ip_address()
        self.devcode = str(uuid.uuid4())
        self.distinct_id = str(uuid.uuid4())

        self.session = requests.Session()
        self.user_id = None
        self.wuwa_role_id = None
        self.pgr_role_id = None
        self.messages = []
        self.success = True

    def _log(self, msg: str):
        logging.info(f"[{self.note}] {msg}")
        self.messages.append(msg)

    def _req(self, url: str, method: str = 'POST', data: Optional[dict] = None, req_type: str = 'bbs') -> dict:
        headers = {
            "Accept": "*/*",
            "Accept-Language": "zh-CN,zh-Hans;q=0.9",
            "Accept-Encoding": "gzip, deflate, br",
            "Connection": "keep-alive",
            "Host": "api.kurobbs.com",
            "source": "ios",
        }

        if req_type == 'bbs':
            headers.update({
                "lang": "zh-Hans",
                "User-Agent": "KuroGameBox/48 CFNetwork/1492.0.1 Darwin/23.3.0",
                "channelId": "1",
                "channel": "appstore",
                "version": "2.2.0",
                "model": "iPhone15,2",
                "osVersion": "17.3",
                "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
                "Cookie": f"user_token={self.token}",
                "Ip": self.ip,
                "distinct_id": self.distinct_id,
                "devCode": self.devcode,
                "token": self.token,
            })
        elif req_type == 'game':
            headers.update({
                "Accept": "application/json, text/plain, */*",
                "Sec-Fetch-Site": "same-site",
                "source": "ios",
                "Sec-Fetch-Mode": "cors",
                "Origin": "https://web-static.kurobbs.com",
                "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) KuroGameBox/2.2.0",
                "devCode": f"{self.ip}, Mozilla/5.0 (iPhone; CPU iPhone OS 17_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) KuroGameBox/2.2.0",
                "token": self.token,
            })
        elif req_type == 'user_info':
            headers.update({
                "osversion": "Android",
                "countrycode": "CN",
                "ip": self.ip,
                "model": "2211133C",
                "source": "android",
                "lang": "zh-Hans",
                "version": "1.0.9",
                "versioncode": "1090",
                "content-type": "application/x-www-form-urlencoded",
                "user-agent": "okhttp/3.10.0",
                "devcode": self.devcode,
                "distinct_id": self.distinct_id,
                "token": self.token,
            })

        try:
            if method == 'POST':
                resp = self.session.post(url, headers=headers, data=data, timeout=TIMEOUT).json()
            else:
                resp = self.session.get(url, headers=headers, timeout=TIMEOUT).json()
            return resp
        except Exception as e:
            return {"code": -1, "msg": f"请求异常: {str(e)}"}

    def init_user_data(self) -> bool:
        res = self._req(API.USER_MINE, req_type='user_info')
        if res.get('code') != 200:
            self._log(f"获取用户信息失败或Token已过期: {res.get('msg')}")
            self.success = False
            return False
        self.user_id = res['data']['mine']['userId']

        res_wuwa = self._req(API.USER_ROLE_LIST, data={"gameId": GAME_WUWA_ID}, req_type='user_info')
        if res_wuwa.get('code') == 200 and res_wuwa.get('data'):
            self.wuwa_role_id = res_wuwa['data'][0].get('roleId')

        res_pgr = self._req(API.USER_ROLE_LIST, data={"gameId": GAME_PGR_ID}, req_type='user_info')
        if res_pgr.get('code') == 200 and res_pgr.get('data'):
            self.pgr_role_id = res_pgr['data'][0].get('roleId')

        return True

    def _get_sign_reward(self, game_name: str, game_id: str, server_id: str, role_id: str) -> Optional[str]:
        try:
            data = {
                "gameId": game_id,
                "serverId": server_id,
                "roleId": role_id,
                "userId": self.user_id,
            }
            res = self._req(API.GAME_SIGN_RECORD, data=data, req_type='game')
            if res.get('code') == 200 and res.get('data'):
                if isinstance(res['data'], list) and len(res['data']) > 0:
                    return res['data'][0].get('goodsName')
            return None
        except Exception:
            return None

    def game_sign(self):
        month = datetime.datetime.now().strftime("%m")

        def _do_sign(game_name, game_id, server_id, role_id):
            if not role_id:
                self._log(f"[{game_name}] 未找到角色ID，跳过签到")
                return
            data = {
                "gameId": game_id,
                "serverId": server_id,
                "roleId": role_id,
                "userId": self.user_id,
                "reqMonth": month,
            }
            res = self._req(API.GAME_SIGN_IN, data=data, req_type='game')
            code = res.get('code')

            if code == 200:
                reward = self._get_sign_reward(game_name, game_id, server_id, role_id)
                msg = f"[{game_name}] 签到成功"
                if reward:
                    msg += f"，奖励: {reward}"
                self._log(msg)
            elif code == 1511:
                reward = self._get_sign_reward(game_name, game_id, server_id, role_id)
                msg = f"[{game_name}] 今日已签到"
                if reward:
                    msg += f"，奖励: {reward}"
                self._log(msg)
            elif code == 1513:
                self._log(f"[{game_name}] 签到失败：用户信息异常，即将禁用该账号")
                self.success = False
            elif code == 220:
                self._log(f"[{game_name}] 签到失败：登录已过期，请重新登录")
                self.success = False
            else:
                self._log(f"[{game_name}] 签到失败: {res.get('msg', '未知错误')} (code={code})")
                raise Exception(f"{game_name} 签到失败: {res.get('msg', '未知错误')}")

        _do_sign("鸣潮", GAME_WUWA_ID, SERVER_WUWA, self.wuwa_role_id)
        time.sleep(1)
        _do_sign("战双", GAME_PGR_ID, SERVER_PGR, self.pgr_role_id)

    def forum_tasks(self):
        res = self._req(API.USER_SIGN_IN, data={"gameId": "2"}, req_type='bbs')
        if res.get('code') == 200:
            self._log("论坛签到成功")

        time.sleep(1)

        res_tasks = self._req(API.TASK_PROCESS, data={"gameId": "0"}, req_type='bbs')
        if res_tasks.get('code') != 200:
            self._log("获取每日任务列表失败跳过")
            return

        tasks = res_tasks.get('data', {}).get('dailyTask', [])
        tasks_map = {t.get('remark'): t.get('process') for t in tasks}

        posts = []
        if tasks_map.get("浏览3篇帖子", 1) == 0 or tasks_map.get("点赞5次", 1) == 0:
            res_posts = self._req(API.FORUM_LIST, data={"forumId": "9", "gameId": "3", "pageIndex": "1", "pageSize": "20", "searchType": "3", "timeType": "0"}, req_type='bbs')
            if res_posts.get('code') == 200:
                posts = res_posts.get('data', {}).get('postList', [])

        if tasks_map.get("浏览3篇帖子", 1) == 0 and posts:
            view_count = 0
            for p in posts[:3]:
                self._req(API.FORUM_POST_DETAIL, data={"isOnlyPublisher": "0", "postId": p['postId'], "showOrderTyper": "2"}, req_type='bbs')
                view_count += 1
                time.sleep(random.uniform(1, 2))
            self._log(f"完成浏览帖子任务 ({view_count}/3)")

        if tasks_map.get("点赞5次", 1) == 0 and posts:
            like_count = 0
            for p in posts[:5]:
                self._req(API.FORUM_LIKE, data={"forumId": 11, "gameId": 3, "likeType": 1, "operateType": 1, "postCommentId": "", "postCommentReplyId": "", "postId": p['postId'], "postType": 1, "toUserId": p['userId']}, req_type='bbs')
                like_count += 1
                time.sleep(random.uniform(1, 2))
            self._log(f"完成点赞帖子任务 ({like_count}/5)")

        if tasks_map.get("分享1次帖子", 1) == 0:
            self._req(API.TASK_SHARE, data={"gameId": 3}, req_type='bbs')
            self._log("完成分享帖子任务 (1/1)")

    def run(self):
        for attempt in range(MAX_RETRIES):
            try:
                if attempt > 0:
                    delay = random.uniform(5, 15)
                    self._log(f"将在 {delay:.1f} 秒后进行第 {attempt + 1}/{MAX_RETRIES} 次重试...")
                    time.sleep(delay)

                if self.init_user_data():
                    self.game_sign()
                    self.forum_tasks()

                if not self.success:
                    raise Exception("签到执行失败")
                break

            except Exception as e:
                self._log(f"执行异常: {str(e)}")
                self.success = False
                if attempt < MAX_RETRIES - 1:
                    continue
                else:
                    self._log(f"重试 {MAX_RETRIES} 次后仍然失败，放弃签到")

        return "\n".join(self.messages)


def get_tokens() -> List[Dict[str, str]]:
    raw = os.environ.get("KURO_TOKEN", "").strip()
    if not raw:
        return []

    raw = raw.replace('&', '\n').replace(',', '\n')
    token_list = []

    for line in raw.split('\n'):
        line = line.strip()
        if not line:
            continue

        parts = line.split('#', 1)
        token_val = parts[0].strip()
        note = parts[1].strip() if len(parts) > 1 else f"账号{len(token_list) + 1}"

        if token_val:
            token_list.append({'token': token_val, 'note': note})

    return token_list


def main():
    logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')

    tokens = get_tokens()
    if not tokens:
        logging.error("未找到 Token，请在环境变量中设置 'KURO_TOKEN'。")
        return

    logging.info(f"✅ 检测到 {len(tokens)} 个账号，开始执行库街区签到...\n" + "-" * 30)

    notify_content = []

    for token_data in tokens:
        signer = KuroSigner(token_data)
        result_msg = signer.run()

        status_icon = "✅" if signer.success else "❌"
        header = f"【{signer.note}】{status_icon}"

        notify_content.append(header)
        notify_content.append(result_msg)
        notify_content.append("-" * 20)

    final_content = "\n".join(notify_content)
    logging.info(f"\n{final_content}")

    if send:
        send("库街区签到", final_content)


if __name__ == '__main__':
    main()
