Continue seeded Qinglong script collection
This commit is contained in:
462
脚本库/web版/抓包/次元姬小说/2025-08-27_次元姬小说_d9d5f110.py
Normal file
462
脚本库/web版/抓包/次元姬小说/2025-08-27_次元姬小说_d9d5f110.py
Normal file
@@ -0,0 +1,462 @@
|
||||
# Source: https://github.com/LinYuanovo/AutoTaskScripts/blob/main/app/%E6%AC%A1%E5%85%83%E5%A7%AC%E5%B0%8F%E8%AF%B4.py
|
||||
# Raw: https://raw.githubusercontent.com/LinYuanovo/AutoTaskScripts/main/app/%E6%AC%A1%E5%85%83%E5%A7%AC%E5%B0%8F%E8%AF%B4.py
|
||||
# Repo: LinYuanovo/AutoTaskScripts
|
||||
# Path: app/次元姬小说.py
|
||||
# UploadedAt: 2025-08-27T15:31:08+08:00
|
||||
# SHA256: d9d5f110790abe6c16dad9d7b17c22b51314611f9430b5eb988f21cd45158305
|
||||
# Category: web版/抓包
|
||||
# Evidence: web/H5关键词 + cookie/token/header
|
||||
|
||||
"""
|
||||
作者: 临渊
|
||||
日期: 2025/6/27
|
||||
name: 次元姬小说
|
||||
入口: APP
|
||||
功能: 签到、阅读、领取奖励 (阅读需要模拟真实时间,所以脚本会运行比较长时间,预计一个小时)
|
||||
变量: cyj_account = 'deviceno&token' (api.hwnovel.com下请求的deviceno和token,用&分割)
|
||||
定时: 一天一到两次
|
||||
cron: 10 8,15 * * *
|
||||
------------更新日志------------
|
||||
2025/6/27 V1.0 初始化脚本
|
||||
2025/6/28 V1.1 优化阅读逻辑 增加阅读时间判断,如果已阅读时间超过50分钟,则跳过模拟阅读
|
||||
2025/6/28 V1.2 增加自动订阅 需要填写bookId和从第多少章开始订阅
|
||||
2025/7/28 V1.3 修改头部注释,以便拉库
|
||||
"""
|
||||
|
||||
MULTI_ACCOUNT_SPLIT = ["\n", "@"] # 多账户分隔符列表
|
||||
NOTIFY = False # 是否推送日志,默认不推送,True则推送
|
||||
AUTO_ORDER_BOOK_ID = "" # 自动订阅的小说bookId,填了则自动订阅,否则不订阅,只有第一个号会进行订阅
|
||||
AUTO_ORDER_BOOK_CHAPTER = 1 # 从第多少章开始自动订阅,默认使用所有代币往后进行订阅
|
||||
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
import requests
|
||||
import base64
|
||||
import hashlib
|
||||
import threading
|
||||
import uuid as uuidlib
|
||||
from Crypto.Cipher import DES
|
||||
from Crypto.Util.Padding import pad
|
||||
|
||||
class AutoTask:
|
||||
def __init__(self, script_name):
|
||||
self.script_name = script_name
|
||||
self.log_msgs = []
|
||||
self.local = threading.local()
|
||||
self.base_url = "https://api.hwnovel.com/api/ciyuanji/client/"
|
||||
self.setup_logging()
|
||||
|
||||
def setup_logging(self):
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s\t- %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S',
|
||||
handlers=[logging.StreamHandler()]
|
||||
)
|
||||
|
||||
def log(self, msg, level="info", push=True):
|
||||
account_prefix = getattr(self.local, "account_prefix", "")
|
||||
msg = f"{account_prefix}{msg}"
|
||||
if level == "info":
|
||||
logging.info(msg)
|
||||
elif level == "error":
|
||||
logging.error(msg)
|
||||
elif level == "warning":
|
||||
logging.warning(msg)
|
||||
if push:
|
||||
self.log_msgs.append(msg)
|
||||
|
||||
def check_env(self):
|
||||
"""
|
||||
检查环境变量
|
||||
:return: 环境变量字符串
|
||||
"""
|
||||
try:
|
||||
# 从环境变量获取cookie
|
||||
cyj_account = os.getenv(f"cyj_account")
|
||||
if not cyj_account:
|
||||
self.log("[检查环境变量]没有找到环境变量cyj_account,请检查环境变量", level="error")
|
||||
return None
|
||||
|
||||
# 自动检测分隔符
|
||||
split_char = None
|
||||
for sep in MULTI_ACCOUNT_SPLIT:
|
||||
if sep in cyj_account:
|
||||
split_char = sep
|
||||
break
|
||||
if not split_char:
|
||||
# 如果都没有分隔符,默认当作单账号
|
||||
cyj_accounts = [cyj_account]
|
||||
else:
|
||||
cyj_accounts = cyj_account.split(split_char)
|
||||
|
||||
for cyj_account in cyj_accounts:
|
||||
if "=" in cyj_account:
|
||||
cyj_account = cyj_account.split("=")[1]
|
||||
deviceno, token = cyj_account.split("&")
|
||||
yield deviceno, token
|
||||
else:
|
||||
deviceno, token = cyj_account.split("&")
|
||||
yield deviceno, token
|
||||
except Exception as e:
|
||||
self.log(f"[检查环境变量]发生错误: {str(e)}\n{traceback.format_exc()}", level="error")
|
||||
raise
|
||||
|
||||
def encrypt(self, data):
|
||||
key = b"ZUreQN0E"
|
||||
cipher = DES.new(key, DES.MODE_ECB)
|
||||
padded_data = pad(data.encode("utf-8"), DES.block_size)
|
||||
encrypted = cipher.encrypt(padded_data)
|
||||
return base64.b64encode(encrypted).decode("utf-8")
|
||||
|
||||
def base64_encode(self, s):
|
||||
return base64.b64encode(s.encode("utf-8")).decode("utf-8")
|
||||
|
||||
def digest(self, s, method="md5"):
|
||||
if method == "md5":
|
||||
return hashlib.md5(s.encode("utf-8")).hexdigest()
|
||||
raise NotImplementedError("只支持md5")
|
||||
|
||||
def uuid(self):
|
||||
return str(uuidlib.uuid4())
|
||||
|
||||
def request(self, method, url, headers, data=None):
|
||||
timestamp = int(time.time() * 1000)
|
||||
request_id = self.uuid()
|
||||
param = self.encrypt(json.dumps({**(data or {}), "timestamp": timestamp}))
|
||||
sign_str = f"param={param}&requestId={request_id}×tamp={timestamp}&key=NpkTYvpvhJjEog8Y051gQDHmReY54z5t3F0zSd9QEFuxWGqfC8g8Y4GPuabq0KPdxArlji4dSnnHCARHnkqYBLu7iIw55ibTo18"
|
||||
sign = self.digest(self.base64_encode(sign_str), "md5").upper()
|
||||
try:
|
||||
if method == "GET":
|
||||
resp = requests.get(
|
||||
self.base_url + url,
|
||||
params={
|
||||
"param": param,
|
||||
"requestId": request_id,
|
||||
"sign": sign,
|
||||
"timestamp": timestamp
|
||||
},
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
else:
|
||||
resp = requests.post(
|
||||
self.base_url + url,
|
||||
json={
|
||||
"param": param,
|
||||
"requestId": request_id,
|
||||
"sign": sign,
|
||||
"timestamp": timestamp
|
||||
},
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
except Exception as e:
|
||||
self.log(f"请求异常: {url} {str(e)}\n{traceback.format_exc()}", level="error")
|
||||
return None
|
||||
|
||||
def signin(self, headers):
|
||||
"""
|
||||
每日签到
|
||||
"""
|
||||
response = self.request("POST", "sign/sign", headers, {})
|
||||
if response["code"] == "200":
|
||||
self.log(f"签到: {response['msg']}")
|
||||
return response
|
||||
else:
|
||||
self.log(f"签到: {response['msg']}", level="warning")
|
||||
return False
|
||||
|
||||
def get_task_list(self, headers):
|
||||
"""
|
||||
获取任务列表
|
||||
:return: 任务列表
|
||||
"""
|
||||
response = self.request("GET", "task/getTaskList", headers, {})
|
||||
if response["code"] == "200":
|
||||
task_list = response["data"]["daliyTask"]
|
||||
return task_list
|
||||
else:
|
||||
self.log(f"获取任务列表: {response['msg']}", level="warning")
|
||||
return False
|
||||
|
||||
def read_start(self, headers):
|
||||
"""
|
||||
开始阅读
|
||||
"""
|
||||
data = {
|
||||
"startChapter": "3632603",
|
||||
"startChapterName": "第1章 邪恶app",
|
||||
"bookName": "人渣男主,为所欲为",
|
||||
"bookId": "14827",
|
||||
"timestamp": int(time.time() * 1000)
|
||||
}
|
||||
response = self.request("POST", "read/start", headers, data)
|
||||
if response["code"] == "200":
|
||||
readId = response["data"]["readId"]
|
||||
# self.log(f"开始阅读: readId={readId}", level="info")
|
||||
return readId
|
||||
else:
|
||||
self.log(f"开始阅读: {response['msg']}", level="error")
|
||||
return False
|
||||
|
||||
def read_start_chapter(self, headers, readId):
|
||||
"""
|
||||
阅读章节
|
||||
:param readId: 阅读ID
|
||||
"""
|
||||
data = {
|
||||
"isBuy": "0",
|
||||
"isFee": "0",
|
||||
"readId": readId,
|
||||
"chapterId": "3632603",
|
||||
"chapterName": "第1章 邪恶app",
|
||||
"bookName": "人渣男主,为所欲为",
|
||||
"bookId": "14827",
|
||||
"timestamp": int(time.time() * 1000)
|
||||
}
|
||||
response = self.request("POST", "read/startChapter", headers, data)
|
||||
if response["code"] == "200":
|
||||
readChapterId = response["data"]["readChapterId"]
|
||||
# self.log(f"阅读章节: readChapterId={readChapterId}", level="info")
|
||||
return readChapterId
|
||||
else:
|
||||
self.log(f"阅读章节: {response['msg']}", level="error")
|
||||
return False
|
||||
|
||||
def read_end(self, headers, readId):
|
||||
"""
|
||||
结束阅读
|
||||
:param readId: 阅读ID
|
||||
"""
|
||||
data = {
|
||||
"endChapterName": "第1章 邪恶app",
|
||||
"readId": readId,
|
||||
"chapterCount": "1",
|
||||
"endChapter": "3632603",
|
||||
"bookId": "14827",
|
||||
"timestamp": int(time.time() * 1000)
|
||||
}
|
||||
response = self.request("POST", "read/end", headers, data)
|
||||
if response["code"] == "200":
|
||||
# self.log(f"结束阅读: {response['msg']}", level="info")
|
||||
return response
|
||||
else:
|
||||
self.log(f"结束阅读: {response['msg']}", level="error")
|
||||
return False
|
||||
|
||||
def read_end_chapter(self, headers,readChapterId):
|
||||
"""
|
||||
结束阅读章节
|
||||
:param readChapterId: 阅读章节ID
|
||||
"""
|
||||
data = {
|
||||
"isBuy": "0",
|
||||
"isFee": "0",
|
||||
"isUserMember": "0",
|
||||
"isBookMember": "0",
|
||||
"readChapterId": readChapterId,
|
||||
"timestamp": int(time.time() * 1000)
|
||||
}
|
||||
response = self.request("POST", "read/endChapter", headers, data)
|
||||
if response["code"] == "200":
|
||||
# self.log(f"结束阅读章节: {response['msg']}", level="info")
|
||||
return response
|
||||
else:
|
||||
self.log(f"结束阅读章节: {response['msg']}", level="error")
|
||||
return False
|
||||
|
||||
def receive_task_reward(self, headers, task_id, reward_id):
|
||||
"""
|
||||
领取任务奖励
|
||||
:param task_id: 任务ID
|
||||
:param reward_id: 奖励ID
|
||||
"""
|
||||
data = {
|
||||
"taskId": task_id,
|
||||
"rewardId": reward_id
|
||||
}
|
||||
response = self.request("POST", "task/receiveTaskReward", headers, data)
|
||||
if response["code"] == "200":
|
||||
self.log(f"领取奖励: {response['msg']}", push=False)
|
||||
return response
|
||||
else:
|
||||
self.log(f"领取奖励: {response['msg']}", level="error")
|
||||
return False
|
||||
|
||||
def get_user_info(self, headers):
|
||||
"""
|
||||
获取用户信息
|
||||
"""
|
||||
response = self.request("GET", "account/getAccountByUser", headers, {})
|
||||
if response["code"] == "200":
|
||||
nikeName = response["data"]["accountInfo"]["nickName"]
|
||||
# 代币
|
||||
couponBalance = float(response["data"]["accountInfo"]["couponBalance"])
|
||||
# 推荐票
|
||||
dayCount = int(response["data"]["accountInfo"]["dayCount"])
|
||||
self.log(f"{nikeName} 代币:{couponBalance} 推荐票:{dayCount}")
|
||||
return couponBalance
|
||||
else:
|
||||
self.log(f"获取用户信息: {response['msg']}", level="error")
|
||||
return False
|
||||
|
||||
def get_book_chapter_info(self, headers, bookId):
|
||||
"""
|
||||
获取小说章节信息
|
||||
:param bookId: 小说ID
|
||||
:return: 小说名称, 章节列表
|
||||
"""
|
||||
data = {
|
||||
"sortType": "1",
|
||||
"pageNo": "1",
|
||||
"pageSize": "9999",
|
||||
"bookId": bookId,
|
||||
"timestamp": int(time.time() * 1000)
|
||||
}
|
||||
response = self.request("GET", "chapter/getChapterListByBookId", headers, data)
|
||||
if response["code"] == "200":
|
||||
book_name = response["data"]["bookChapter"]["bookName"]
|
||||
chapter_list = response["data"]["bookChapter"]["chapterList"]
|
||||
return book_name, chapter_list
|
||||
else:
|
||||
self.log(f"获取小说章节信息: {response['msg']}", level="error")
|
||||
return False
|
||||
|
||||
def order_book_chapter(self, headers, bookId, chapterId):
|
||||
"""
|
||||
订阅章节
|
||||
:param bookId: 小说ID
|
||||
:param chapterId: 章节ID
|
||||
"""
|
||||
data = {
|
||||
"buyCount": "1",
|
||||
"productId": chapterId,
|
||||
"viewType": "2",
|
||||
"consumeType": "1",
|
||||
"bookId": bookId,
|
||||
"timestamp": int(time.time() * 1000)
|
||||
}
|
||||
response = self.request("POST", "order/consume", headers, data)
|
||||
if response["code"] == "200":
|
||||
return True
|
||||
else:
|
||||
self.log(f"订阅章节: {response['msg']}", level="error")
|
||||
return False
|
||||
|
||||
def run_for_account(self, index, token, deviceno):
|
||||
self.local.account_prefix = f"[账号{index}] "
|
||||
# 防止串号
|
||||
headers = {
|
||||
"channel": "25",
|
||||
"deviceno": deviceno,
|
||||
"platform": "1",
|
||||
"imei": "",
|
||||
"targetmodel": "M2012K11AC",
|
||||
"oaid": "",
|
||||
"version": "3.4.3",
|
||||
"token": token,
|
||||
"user-agent": "Mozilla/5.0 (Linux; Android 11; Pixel 4 XL Build/RP1A.200720.009; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/92.0.4515.115 Mobile Safari/537.36"
|
||||
}
|
||||
# 签到
|
||||
self.signin(headers)
|
||||
# 获取任务列表
|
||||
task_list = self.get_task_list(headers)
|
||||
reader_time = 0
|
||||
for task in task_list:
|
||||
if task['actionName'] == "阅读" and task['taskState'] != "0":
|
||||
reader_time = int(task['rewardList'][0]['actionRequire'])
|
||||
if reader_time >= 0:
|
||||
need_read_time = 50 - reader_time
|
||||
self.log(f"还需要阅读{need_read_time}分钟")
|
||||
if need_read_time > 0:
|
||||
# 5分钟一次,太长时间会失效
|
||||
for i in range(need_read_time // 5):
|
||||
# 开始阅读
|
||||
readId = self.read_start(headers)
|
||||
time.sleep(5)
|
||||
if readId:
|
||||
# 阅读章节
|
||||
readChapterId = self.read_start_chapter(headers, readId)
|
||||
self.log(f"模拟第{i+1}次阅读中,请勿停止运行脚本", push=False)
|
||||
time.sleep(random.randint(300, 320))
|
||||
if readChapterId:
|
||||
# 结束阅读章节
|
||||
self.read_end_chapter(headers, readChapterId)
|
||||
time.sleep(5)
|
||||
# 结束阅读
|
||||
self.read_end(headers, readId)
|
||||
else:
|
||||
self.log(f"已阅读{reader_time}分钟,跳过模拟阅读")
|
||||
# 获取任务列表
|
||||
task_list = self.get_task_list(headers)
|
||||
if task_list:
|
||||
# 领取奖励
|
||||
for task in task_list:
|
||||
if task["taskState"] == "1":
|
||||
self.receive_task_reward(headers, task["taskId"], task["rewardId"])
|
||||
# 再次获取任务列表
|
||||
task_list = self.get_task_list(headers)
|
||||
if task_list:
|
||||
for task in task_list:
|
||||
taskname = task["taskName"]
|
||||
if "阅读" in taskname:
|
||||
status = "已完成" if task["taskState"] == "2" else "未完成"
|
||||
self.log(f"任务-{taskname}-{status}", level="info")
|
||||
# 获取用户信息
|
||||
coupon_balance = self.get_user_info(headers)
|
||||
# 自动订阅小说
|
||||
if AUTO_ORDER_BOOK_ID and index == 1:
|
||||
book_name, chapter_list = self.get_book_chapter_info(headers, AUTO_ORDER_BOOK_ID)
|
||||
if chapter_list:
|
||||
for index, chapter in enumerate(chapter_list, 1):
|
||||
# 大于等于指定章节开始订阅
|
||||
if index >= AUTO_ORDER_BOOK_CHAPTER and int(chapter['isFee']) == 1 and int(chapter['isBuy']) == 0:
|
||||
if coupon_balance >= float(chapter['price']):
|
||||
# 订阅
|
||||
if self.order_book_chapter(headers, AUTO_ORDER_BOOK_ID, chapter["chapterId"]):
|
||||
self.log(f"订阅 [{book_name}] - {chapter['chapterName']} 成功")
|
||||
coupon_balance -= float(chapter['price'])
|
||||
else:
|
||||
break
|
||||
|
||||
def run(self):
|
||||
self.log(f"【{self.script_name}】开始执行任务")
|
||||
|
||||
threads = []
|
||||
for index, (deviceno, token) in enumerate(self.check_env(), 1):
|
||||
t = threading.Thread(
|
||||
target=self.run_for_account,
|
||||
args=(index, token, deviceno)
|
||||
)
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
if NOTIFY:
|
||||
# 如果notify模块不存在,从远程下载至本地
|
||||
if not os.path.exists("notify.py"):
|
||||
url = "https://raw.githubusercontent.com/whyour/qinglong/refs/heads/develop/sample/notify.py"
|
||||
response = requests.get(url)
|
||||
with open("notify.py", "w", encoding="utf-8") as f:
|
||||
f.write(response.text)
|
||||
import notify
|
||||
else:
|
||||
import notify
|
||||
# 任务结束后推送日志
|
||||
title = f"{self.script_name} 运行日志"
|
||||
header = "作者:临渊\n\n"
|
||||
content = header + "\n" +"\n".join(self.log_msgs)
|
||||
notify.send(title, content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
auto_task = AutoTask("次元姬小说")
|
||||
auto_task.run()
|
||||
Reference in New Issue
Block a user