新知:

输出:

给 kakaopage 的等就免费的漫画,做了个自动检测等待券和使用脚本。

import requests
import time
import datetime
import pytz
import json
 
# --- 配置区 ---
# 监控漫画ID
CONTENT_ID = '3577'
 
COOKIE = ''
 
# 全局请求头
HEADERS = {
    'accept': 'application/json, text/plain, */*',
    'accept-language': 'ko',
    'origin': 'https://webtoon.kakao.com',
    'referer': 'https://webtoon.kakao.com/',
    'sec-ch-ua': '"Chromium";v="140", "Not=A?Brand";v="24", "Google Chrome";v="140"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-site',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
    'cookie': COOKIE
}
 
# API 模块
TICKET_STATUS_URL = f'https://gateway-kw.kakao.com/ticket/v2/views/content-home/available-tickets?contentId={CONTENT_ID}'
EPISODES_LIST_URL = f'https://gateway-kw.kakao.com/episode/v2/views/content-home/contents/{CONTENT_ID}/episodes?sort=-NO&offset=0&limit=1000' 
PURCHASE_URL_TEMPLATE = 'https://gateway-kw.kakao.com/episode/v3/episodes/{episode_id}/pass'
 
def get_current_time_str():
    """获取格式化的当前时间字符串"""
    return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
 
def check_ticket_status():
    """查询免费券状态"""
    try:
        print(f"[{get_current_time_str()}] 正在检查免费券状态...")
        response = requests.get(TICKET_STATUS_URL, headers=HEADERS)
        response.raise_for_status()  # 如果请求失败 (例如 401, 403), 会抛出异常
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"  [错误] 检查免费券状态失败: {e}")
        if "401" in str(e) or "403" in str(e):
            print("  [提示] 可能是Cookie失效了,请更新脚本中的COOKIE变量。")
        return None
 
def get_episodes():
    """获取所有章节列表"""
    try:
        print(f"[{get_current_time_str()}] 正在获取章节列表...")
        response = requests.get(EPISODES_LIST_URL, headers=HEADERS)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"  [错误] 获取章节列表失败: {e}")
        return None
 
def purchase_episode(episode_id):
    """使用免费券购买指定章节"""
    purchase_url = PURCHASE_URL_TEMPLATE.format(episode_id=episode_id)
    payload = {"ticketType": "wait_for_free"}
    purchase_headers = HEADERS.copy()
    purchase_headers['content-type'] = 'application/json;charset=UTF-8'
 
    try:
        print(f"[{get_current_time_str()}] 正在尝试使用免费券解锁章节 (ID: {episode_id})...")
        response = requests.post(purchase_url, headers=purchase_headers, data=json.dumps(payload))
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"  [错误] 解锁章节失败: {e}")
        return None
 
def find_next_episode_to_unlock(episodes_data):
    """从章节列表中找到下一个需要解锁的章节"""
    if not episodes_data or 'data' not in episodes_data or 'episodes' not in episodes_data['data']:
        return None
 
    # 按章节号 'no' 从小到大排序
    sorted_episodes = sorted(
        episodes_data['data']['episodes'],
        key=lambda x: int(x['no']) if str(x['no']).isdigit() else x['no']
    )
 
    for episode in sorted_episodes:
        # 已经能读的章节直接跳过
        if episode.get('readable', False):
            continue
 
        if episode.get('useType') == 'WAIT_FOR_FREE':
            return episode
        
    return None
 
 
def main():
    """主函数"""
    print("="*40)
    print("漫画自动解锁脚本已启动")
    print(f"目标漫画ID: {CONTENT_ID}")
    print("="*40)
 
    while True:
        status_data = check_ticket_status()
        if not status_data:
            print(f"  将在10分钟后重试...")
            time.sleep(600)  # 如果请求失败,等待10分钟再试
            continue
 
        ticket_count = status_data.get('data', {}).get('ticketCount', 0)
 
        if ticket_count > 0:
            print(f"  [成功] 发现 {ticket_count} 张可用免费券!")
            
            episodes_data = get_episodes()
            if episodes_data:
                next_episode = find_next_episode_to_unlock(episodes_data)
                
                if next_episode:
                    episode_id = next_episode['id']
                    episode_no = next_episode['no']
                    episode_title = next_episode['title']
                    print(f"  [发现] 下一个要解锁的章节是: 第 {episode_no} 话 - {episode_title}")
                    
                    purchase_result = purchase_episode(episode_id)
                    if purchase_result and purchase_result.get('data', {}).get('pass') is True:
                        print(f"  [成功] 章节 '{episode_title}' 解锁成功!")
                    else:
                        print(f"  [失败] 章节 '{episode_title}' 解锁失败。服务器返回: {purchase_result}")
                else:
                    print("  [完成] 所有可免费解锁的章节均已解锁,脚本将退出。")
                    break # 退出循环
 
            # 无论成功与否,都等待一小段时间再检查,避免过快消耗掉所有券(如果有多张)
            print("  等待10秒后进行下一次检查...")
            time.sleep(10)
        
        else:
            # 没有可用的券,计算等待时间
            wait_for_free_info = status_data.get('data', {}).get('waitForFree')
            if wait_for_free_info and 'rechargesDateTime' in wait_for_free_info:
                recharge_time_str = wait_for_free_info['rechargesDateTime']
                # 将ISO 8601格式的UTC时间字符串转换为datetime对象
                recharge_time_utc = datetime.datetime.fromisoformat(recharge_time_str.replace('Z', '+00:00'))
 
                # 转换为北京时间 (UTC+8)
                tz_beijing = pytz.timezone("Asia/Shanghai")
                recharge_time_local = recharge_time_utc.astimezone(tz_beijing)
 
                # 获取当前的UTC时间
                now_utc = datetime.datetime.now(pytz.utc)
                wait_seconds = (recharge_time_utc - now_utc).total_seconds()
                if wait_seconds > 0:
                    sleep_duration = wait_seconds + 60
                    wait_minutes = sleep_duration / 60
                    print(f"  [等待] 当前没有免费券。下一张券将在 {recharge_time_local.strftime('%Y-%m-%d %H:%M:%S %Z')} 发放。")
                    print(f"  脚本将休眠约 {int(wait_minutes)} 分钟...")
                    time.sleep(sleep_duration)
 
                else:
                    # 如果计算出的时间在过去,说明可能刚发放,等待1分钟再检查
                    print("  [提示] 下一张券的刷新时间已过,可能正在发放。等待1分钟后重新检查。")
                    time.sleep(60)
            else:
                print("  [错误] 无法获取下一张券的时间信息。将在10分钟后重试。")
                time.sleep(600)
        
        print("-" * 20)
 
 
if __name__ == "__main__":
    main()

杂谈:

听说 731 很难看,看完之后发现是喜剧片。