Skip to content

Commit 54ed479

Browse files
committed
Add daily/shyp.py for SHYP tasks
Add a new script daily/shyp.py to automate Shanghai YP media points tasks. The script provides ShypAPI and ShypRunner classes to perform reading, video watching, favoriting, commenting and sharing tasks via the app API, includes logging, randomized delays, and simple error handling. Usage is driven by the SHYP_ACCOUNTS environment variable (format: token#device_id#name; multiple accounts separated by & or newline). Note: the script requires the requests library and includes a cron example in the header.
1 parent 6965ba3 commit 54ed479

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

daily/shyp.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
new Env('上海云媒体积分任务');
5+
cron: 10 8 * * *
6+
7+
【使用说明】
8+
1. 环境变量名: SHYP_ACCOUNTS
9+
2. 格式要求:
10+
- 多个账号之间用 & 或者 换行 分隔。
11+
- 账号内部参数用 # 分隔,顺序为:token#device_id#备注名
12+
13+
例如:
14+
export SHYP_ACCOUNTS="token1#device1#张三 & token2#device2#李四"
15+
"""
16+
17+
import os
18+
import re
19+
import time
20+
import random
21+
import logging
22+
import requests
23+
from typing import Dict, Any, Optional
24+
25+
# ==================== 配置与日志 ====================
26+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
27+
logger = logging.getLogger("SHYP")
28+
29+
# 随机延迟设置
30+
DELAY_ACCOUNTS = (5, 12)
31+
DELAY_TASKS = (3, 6)
32+
COMMENT_POOL = ["👍", "支持", "不错", "很好", "赞", "学习了"]
33+
34+
# ==================== API 核心类 ====================
35+
class ShypAPI:
36+
def __init__(self, token: str, device_id: str, name: str):
37+
self.base_url = "https://app.ypmedia.cn"
38+
self.token = token
39+
self.device_id = device_id
40+
self.name = name
41+
self.site_id = "310110"
42+
self.headers = {
43+
"User-Agent": "okhttp/4.10.0",
44+
"deviceid": self.device_id,
45+
"siteid": self.site_id,
46+
"token": self.token,
47+
"Content-Type": "application/json; charset=UTF-8"
48+
}
49+
50+
def _post(self, endpoint: str, data: Dict = None) -> Optional[Dict]:
51+
try:
52+
res = requests.post(f"{self.base_url}{endpoint}", json=data, headers=self.headers, timeout=15)
53+
return res.json()
54+
except Exception as e:
55+
logger.error(f"请求失败: {e}")
56+
return None
57+
58+
def get_info(self):
59+
return self._post("/media-basic-port/api/app/personal/score/info",
60+
{"orderBy": "release_desc", "requestType": "2", "siteId": self.site_id})
61+
62+
def get_articles(self, channel_id: str, size: int):
63+
return self._post("/media-basic-port/api/app/news/content/list",
64+
{"channel": {"id": channel_id}, "pageNo": 1, "pageSize": size,
65+
"orderBy": "release_desc", "requestType": "1", "siteId": self.site_id})
66+
67+
# ==================== 任务执行器 ====================
68+
class ShypRunner:
69+
def __init__(self, api: ShypAPI):
70+
self.api = api
71+
72+
def start(self):
73+
logger.info(f"🚀 开始处理账号: {self.api.name}")
74+
info = self.api.get_info()
75+
if not info or info.get("code") != 0:
76+
logger.error(f"❌ {self.api.name} Token无效或获取失败")
77+
return
78+
79+
data = info.get("data", {})
80+
logger.info(f"📊 当前积分: {data.get('totalScore')} | 今日已获: {data.get('todayPoint')}")
81+
82+
for job in data.get("jobs", []):
83+
if job.get("status") == "1": continue
84+
85+
title = job.get("title")
86+
need = job.get("totalProgress", 0) - job.get("progress", 0)
87+
if need <= 0: continue
88+
89+
if "阅读" in title: self.do_read(need)
90+
elif "视频" in title: self.do_video(need)
91+
elif "收藏" in title: self.do_favor(need)
92+
elif "评论" in title: self.do_comment(need)
93+
elif "分享" in title: self.do_share(need)
94+
95+
time.sleep(random.uniform(*DELAY_TASKS))
96+
97+
def do_read(self, count):
98+
logger.info(f"📖 准备阅读 {count} 篇文章")
99+
res = self.api.get_articles("a978f44b3e284e5e86777f9d4e3be7bb", count)
100+
for a in res.get("data", {}).get("records", [])[:count]:
101+
self.api._post("/media-basic-port/api/app/common/count/usage/inc",
102+
{"countType": "contentRead", "id": a['id'], "requestType": "1", "siteId": self.api.site_id})
103+
self.api._post("/media-basic-port/api/app/points/read/add", {"requestType": "1", "siteId": self.api.site_id})
104+
logger.info(f"✅ 已阅: {a['title'][:12]}")
105+
time.sleep(random.uniform(2, 4))
106+
107+
def do_video(self, count):
108+
logger.info(f"📺 准备观看 {count} 个视频")
109+
res = self.api.get_articles("d7036c2839e047b48fe64bc36987650c", count)
110+
for v in res.get("data", {}).get("records", [])[:count]:
111+
self.api._post("/media-basic-port/api/app/points/video/add", {"requestType": "1", "siteId": self.api.site_id})
112+
logger.info(f"✅ 已看: {v['title'][:12]}")
113+
time.sleep(random.uniform(5, 8))
114+
115+
def do_favor(self, count):
116+
res = self.api.get_articles("a978f44b3e284e5e86777f9d4e3be7bb", count)
117+
for a in res.get("data", {}).get("records", [])[:count]:
118+
self.api._post("/media-basic-port/api/app/news/content/favor", {"id": a['id']})
119+
logger.info(f"✅ 已收藏: {a['title'][:12]}")
120+
time.sleep(2)
121+
122+
def do_comment(self, count):
123+
res = self.api.get_articles("a978f44b3e284e5e86777f9d4e3be7bb", count)
124+
for a in res.get("data", {}).get("records", [])[:count]:
125+
self.api._post("/media-basic-port/api/app/common/comment/add",
126+
{"content": random.choice(COMMENT_POOL), "targetType": "content", "targetId": a['id']})
127+
logger.info(f"✅ 已评论: {a['title'][:12]}")
128+
time.sleep(4)
129+
130+
def do_share(self, count):
131+
for _ in range(count):
132+
self.api._post("/media-basic-port/api/app/points/share/add", {"requestType": "1", "siteId": self.api.site_id})
133+
logger.info("✅ 已分享任务")
134+
time.sleep(2)
135+
136+
# ==================== 主程序入口 ====================
137+
def main():
138+
conf = os.getenv("SHYP_ACCOUNTS", "")
139+
if not conf:
140+
logger.error("❌ 未找到环境变量 SHYP_ACCOUNTS")
141+
return
142+
143+
# 1. 按照 & 或者 换行符 分割出多个账号
144+
account_blocks = re.split(r'[&\n]+', conf.strip())
145+
146+
for idx, block in enumerate(account_blocks):
147+
block = block.strip()
148+
if not block: continue
149+
150+
# 2. 按照 # 分割内部参数 (token#device_id#name)
151+
parts = block.split('#')
152+
153+
if len(parts) < 2:
154+
logger.warning(f"⚠️ 第 {idx+1} 个账号格式错误,需至少包含 token#device_id")
155+
continue
156+
157+
token = parts[0].strip()
158+
device_id = parts[1].strip()
159+
name = parts[2].strip() if len(parts) > 2 else f"账号{idx+1}"
160+
161+
api = ShypAPI(token, device_id, name)
162+
runner = ShypRunner(api)
163+
try:
164+
runner.start()
165+
except Exception as e:
166+
logger.error(f"💥 运行异常: {e}")
167+
168+
if idx < len(account_blocks) - 1:
169+
wait_time = random.uniform(*DELAY_ACCOUNTS)
170+
logger.info(f"💤 等待 {wait_time:.1f} 秒处理下一个账号...")
171+
time.sleep(wait_time)
172+
173+
if __name__ == "__main__":
174+
main()

0 commit comments

Comments
 (0)