Skip to content

Commit

Permalink
Merge pull request #94 from gulideshanhe/dev
Browse files Browse the repository at this point in the history
Support server operation
  • Loading branch information
VermiIIi0n committed Sep 6, 2024
2 parents 3df55fb + 362e5ff commit e939893
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 34 deletions.
38 changes: 27 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@

### Login

_\*如果非常用地登入会需要短信验证, 您应该先用浏览器登入一次, 以让您的所在地列入白名单._
_\*如果非常用地登入(可能?)会需要短信验证, 您应该先用浏览器登入一次, 以让您的所在地列入白名单_
_\*\*信息优先级: 命令行 > 配置文件 > 交互输入_

### 使用配置文件
Expand All @@ -90,24 +90,37 @@ _\*如果非常用地登入会需要短信验证, 您应该先用浏览器登入
"username": "",
"password": "",
"qrlogin": true,
"save_cookies": true,
"proxies": {},
"logLevel": "INFO",
"tree_view": true,
"progressbar_vier": false,
"qr_extra": {
"show_in_terminal": false,
"ensure_unicode": false
},
"push": {
"enable": false,
"token": "",
}
"show_in_terminal": null,
"ensure_unicode": false
},
"image_path": "",
"pushplus": {
"enable": false,
"token": ""
},
"bark": {
"enable": false,
"token": "https://example.com/xxxxxxxxx"
},
"config_version": "1.3.0"
}
```

- `username`: 账号
- `password`: 密码
- `qrlogin`: 启用二维码登陆, 方便在服务器上部署, 优先级高于账号密码
- `qrlogin`: **目前强制启用**二维码登陆, 方便在服务器上部署, 优先级高于账号密码
- `proxies`: 代理, 可留空, 在 _Windows_ 上还可解决 _Clash_ 等代理造成的证书错误, 详见 [_常见问题_](https://github.com/VermiIIi0n/fuckZHS/discussions/25)
- `logLevel`: 日志等级, 可选 `NOTSET` `DEBUG` `INFO` `WARNING` `ERROR` `CRITICAL`
- `save_cookies`: 保存cookies,*短时间*内可以自动登录。
- `tree_view`: 课程目录结构,关闭后不显示所有课程目录。
- `progressbar_vier`: 进度条控制,关闭后不显示当前视频进度。
- `image_path`: 登录二维码保存路径,留空则不保存。
- `qr_extra`: QR 相关配置
- `show_in_terminal`: 将二维码打印至终端
- `ensure_unicode`: 仅使用 Unicode 字符打印二维码
Expand All @@ -118,7 +131,7 @@ _\*如果非常用地登入会需要短信验证, 您应该先用浏览器登入
- `enable`: 启用推送
- `token`: 推送 token,例如:`https://api.day.app/xxxxxxxxxxxxx`

~填入账号密码即可无干预自动登入~ 当前失效
~~填入账号密码即可无干预自动登入~~ **当前失效**
_\*配置文件如果没有的话会在 main.py 执行时自动创建._

### 使用命令行参数登入
Expand Down Expand Up @@ -192,14 +205,17 @@ python main.py --fetch
- `-v`, `--videos`: 视频 ID, `fileId``videoId`, 可输入多个
- `-u`, `--username`: 账号
- `-p`, `--password`: 密码
- `-q`, `--qrlogin`:
- `-q`, `--qrlogin`: 二维码登录,目前**强制开启**
- `-s`, `--speed`: ~~**POWERR AND SPEEEEED!**~~ 指定播放速度, 想要秒过可以设个很高的值(e.g. 644), 但不推荐. 默认为浏览器观看能到的最大值
- `-t`, `--threshold`: 完成时播放百分比, 高于该值视作完成, 想要重复刷课可以使用大于 `1.0` 的值, 例如 `2.0` 则会再刷一遍
- `-l`, `--limit`: 单节课的时限, 如果您看得上内点习惯分就用吧
- `-d`, `--debug`: 调试级日志记录, 会记录请求到日志 **_(可能包含账号密码, 别乱分享, 当心被盒武_**
- `-f`, `--fetch`: 获取课程清单并存入 _execution.json_ 文件
- `--show_in_terminal`: 将二维码打印至终端
- `--proxy`: 代理设置, 本来用来调试的(e.g. <http://127.0.0.1:8080>)
- `--tree_view`: 课程目录结构,关闭后不显示所有课程目录。
- `--progressbar_view`: 进度条控制,关闭后不显示当前视频进度。
- `--image_path`: 登录二维码保存路径,留空则不保存。
- `-h` `--help`: 显示帮助

运行示例如下:
Expand Down
57 changes: 38 additions & 19 deletions fucker.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ def __init__(self, cookies: dict = None,
speed: float = None,
end_thre: float = None,
pushplus_token: str = '',
bark_token: str = ''):
bark_token: str = '',
tree_view:bool = True,
progressbar_view:bool = True,
image_path:str = ""):
"""
### Fucker Class
* `cookies`: dict, optional, cookies to use for the session
Expand All @@ -64,6 +67,7 @@ def __init__(self, cookies: dict = None,
* `limit`: int, optional, time limit for each course, in minutes (default is 0), auto resets on fuck*Course methods call
* `speed`: float, optional, video playback speed
* `end_thre`: float, optional, threshold to stop the fucker, overloaded when there are questions left unanswered
* `tree_view` :bool, optional, print the tree progress view of the course
"""
logger.debug(f"created a Fucker {id(self)}, limit: {limit}, speed: {speed}, end_thre: {end_thre}")

Expand Down Expand Up @@ -98,6 +102,9 @@ def __init__(self, cookies: dict = None,
self.courses = ObjDict(default=None) # store courses info
self._pushplus = partial(pushpluser, token=pushplus_token) if pushplus_token else lambda *args, **kwargs: None
self._bark = partial(barkpusher, token=bark_token) if bark_token else lambda *args, **kwargs: None
self.tree_view = tree_view
self.progressbar_view = progressbar_view
self.image_path = image_path

@property # cannot directly manipulate _cookies property, we need to parse uuid from cookies
def cookies(self) -> RequestsCookieJar:
Expand Down Expand Up @@ -190,6 +197,11 @@ def _qrlogin(self, qr_callback):
r = self.session.get(qr_page, timeout=10).json()
qrToken = r["qrToken"]
img = b64decode(r["img"])
if self.image_path != "": # 路径非空时保存图片到指定路径
image_path = f"{os.path.join(self.image_path, time.strftime('%Y-%m-%dT%H-%M-%S'))}.png"
with open(image_path, "wb") as f:
f.write(img)
logger.info(f"图片已保存至{image_path}")
qr_callback(img)
logger.debug(f"QR login received, token{qrToken}")
scanned = False
Expand Down Expand Up @@ -290,16 +302,15 @@ def fuckWhatever(self):
logger.exception(e)
continue

def fuckCourse(self, course_id:str, tree_view:bool=True):
def fuckCourse(self, course_id:str):
"""
### Fuck the whole course
* `course_id`: `courseId`(Hike) or `recuitAndCourseId`(Zhidao)
* `tree_view`: whether to print the tree view of the progress
"""
if re.match(r".*[a-zA-Z].*", course_id): # determine if it's a courseId or a recruitAndCourseId
self.fuckZhidaoCourse(course_id, tree_view=tree_view) # it's a recruitAndCourseId
self.fuckZhidaoCourse(course_id) # it's a recruitAndCourseId
else: # it's a courseId
self.fuckHikeCourse(course_id, tree_view=tree_view)
self.fuckHikeCourse(course_id)

def fuckVideo(self, course_id, video_id:str):
"""
Expand Down Expand Up @@ -406,13 +417,12 @@ def getZhidaoContext(self, RAC_id:str, force:bool=False):
self.context[RAC_id] = ctx
return ctx

def fuckZhidaoCourse(self, RAC_id:str, tree_view:bool=True):
def fuckZhidaoCourse(self, RAC_id:str):
"""
* `RAC_id`: `recruitAndCourseId`
* `tree_view`: whether to print the tree progress view of the course
"""
logger.info(f"Fucking Zhidao course {RAC_id}")
tprint = print if tree_view else lambda *a, **k: None
tprint = print if self.tree_view else lambda *a, **k: None

# load context
ctx = self.getZhidaoContext(RAC_id)
Expand All @@ -423,7 +433,12 @@ def fuckZhidaoCourse(self, RAC_id:str, tree_view:bool=True):
tprint(f"Fucking Zhidao course: {course.courseInfo.name or course.courseInfo.enName}")
begin_time = time.time() # real world time
prefix = self.prefix # prefix for tree-like print
w_lim = os.get_terminal_size().columns-1 # width limit for terminal output
try:
# 在 nohup 下运行无法获取,进行捕获
w_lim = os.get_terminal_size().columns-1 # width limit for terminal output
except Exception as e:
# 考虑直接移除此变量,但是保留原代码风格,故进行赋值
w_lim = 80
try:
for chapter in chapters.videoChapterDtos:
tprint(prefix) # extra line as separator
Expand Down Expand Up @@ -568,7 +583,7 @@ def fuckZhidaoVideo(self, RAC_id, video_id):
# have a glance of when quiz is answered
action = "pause a minute" if pause else \
f"fucking {video.videoId}" if answer is None else "answering quiz"
progressBar(s, e, prefix=action, suffix="done")
progressBar(s, e, prefix=action, suffix="done", progressbar_view=self.progressbar_view)
##### end main event loop
time.sleep(random()+1) # old Joe needs more sleep

Expand Down Expand Up @@ -855,8 +870,8 @@ def getHikeContext(self, course_id:str, force:bool=False):
self.context[course_id] = ctx
return ctx

def fuckHikeCourse(self, course_id:str, tree_view:bool=True):
tprint = print if tree_view else lambda *a, **k: None
def fuckHikeCourse(self, course_id:str):
tprint = print if self.tree_view else lambda *a, **k: None
begin_time = time.time()
root = self.getHikeContext(course_id).root

Expand All @@ -865,7 +880,7 @@ def fuckHikeCourse(self, course_id:str, tree_view:bool=True):
tprint(f"Fucking course {course_id} (total root chapters: {len(root)})")
try:
for chapter in root:
self._traverse(course_id, chapter, tree_view=tree_view)
self._traverse(course_id, chapter)
except KeyboardInterrupt:
logger.info("user interrupted")
logger.info(f"Fucked course {course_id}, cost {time.time()-begin_time}s")
Expand Down Expand Up @@ -903,27 +918,31 @@ def fuckHikeVideo(self, course_id, file_id, prev_time=0):
not (int(played_time-prev_time) % interval):
ret_time = self.saveStuStudyRecord(course_id,file_id,played_time,prev_time,start_date) # report progress
prev_time, played_time = ret_time, ret_time
progressBar(played_time, end_time,
prefix=f"fucking {file_id}", suffix="done")
progressBar(played_time, end_time, prefix=f"fucking {file_id}", suffix="done", progressbar_view=self.progressbar_view)
logger.info(f"Fucked video {file_id} of course {course_id}, cost {time.time()-begin_time:.2f}s")
time.sleep(random()+1) # more human-like

def fuckFile(self, course_id, file_id):
self.stuViewFile(course_id, file_id)
time.sleep(random()*2+1) # more human-like

def _traverse(self,course_id, node: ObjDict, depth=0, tree_view=True):
def _traverse(self,course_id, node: ObjDict, depth=0):
depth += 1
tprint = print if tree_view else lambda *a, **k: None
w_lim = os.get_terminal_size().columns-1 # width limit for terminal output
tprint = print if self.tree_view else lambda *a, **k: None
try:
# 在 nohup 下运行无法获取,进行捕获
w_lim = os.get_terminal_size().columns-1 # width limit for terminal output
except Exception as e:
# 考虑直接移除此变量,但是保留原代码风格,故进行赋值
w_lim = 80
prefix = self.prefix * depth
if node.childList: # if childList is not None, then it's a chapter
chapter = node
logger.debug(f"Fucking chapter {chapter.id}")
tprint(prefix) # separate chapters
tprint(f"{prefix}__Fucking chapter {chapter.name}"[:w_lim])
for child in chapter.childList:
self._traverse(course_id, child, depth=depth, tree_view=tree_view)
self._traverse(course_id, child, depth=depth)
else: # if childList is None, then it's a file
file = node
file.studyTime = file.studyTime or 0 # sometimes it's None
Expand Down
19 changes: 16 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@
"save_cookies": True,
"proxies": {},
"logLevel": "INFO",
"tree_view": True,
"progressbar_vier": True,
"qr_extra": {
"show_in_terminal": None,
"ensure_unicode": False
},
"image_path":"",
"pushplus": {
"enable": False,
"token": ""
Expand All @@ -35,7 +38,8 @@
}
# get config or create one if not exist
if os.path.isfile(getConfigPath()):
with open(getConfigPath(), 'r+') as f:
with open(getConfigPath(), 'r+', encoding="UTF-8") as f:
# 不指定编码格式会导致config中可能存在的中文字符乱码
config = ObjDict(json.load(f), default=None)
if "config_version" not in config:
config.config_version = "1.0.0"
Expand Down Expand Up @@ -83,6 +87,12 @@
action="store_true", help="Show QR in terminal")
parser.add_argument("--proxy", type=str,
help="Proxy Config, e.g: http://127.0.0.1:8080")
parser.add_argument("--tree_view", type=bool,
help="print the tree progress view of the course")
parser.add_argument("--progressbar_view", type=bool,
help="print the progressbar view of the course")
parser.add_argument("--image_path", type=str,
help="Image save path, default is empty (do not save)")

args = parser.parse_args()

Expand All @@ -93,6 +103,9 @@
save_cookies = config.save_cookies or False
qr_extra = config.qr_extra or ObjDict(default=None)
show_in_terminal = args.show_in_terminal or config.qr_extra.show_in_terminal
tree_view = args.tree_view or config.tree_view
progressbar_view = args.progressbar_view or config.progressbar_view
image_path = args.image_path or config.image_path
if show_in_terminal is None:
# Defaults to terminal in Windows
show_in_terminal = platform.system() == "Windows"
Expand Down Expand Up @@ -140,8 +153,8 @@
print("*Failed to check update\n")

# create an instance, now we are talking... or fucking
fucker = Fucker(proxies=proxies, speed=args.speed,
end_thre=args.threshold, limit=args.limit, pushplus_token=pushplus_token, bark_token=bark_token)
fucker = Fucker(proxies=proxies, speed=args.speed, end_thre=args.threshold, limit=args.limit,
pushplus_token=pushplus_token, bark_token=bark_token, tree_view=tree_view,progressbar_view=progressbar_view, image_path=image_path)

cookies_path = getRealPath("./cookies.json")
cookies_loaded = False
Expand Down
4 changes: 3 additions & 1 deletion utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def wipeLine():
print('\r' + ' ' * (width), end = '\r', flush=True)

def progressBar (iteration, total, prefix = '', suffix = '', decimals = 1,
length = None, fill = '#'):
length = None, fill = '#', progressbar_view:bool = True):
"""
### Call in a loop to create terminal progress bar
* `iteration` - Required : current iteration (Int)
Expand All @@ -105,6 +105,8 @@ def progressBar (iteration, total, prefix = '', suffix = '', decimals = 1,
* `length` - Optional : character length of bar (Int)
* `fill` - Optional : bar fill character (Str)
"""
if not progressbar_view:
return False
if not length:
try:
length = os.get_terminal_size().columns - 4
Expand Down

0 comments on commit e939893

Please sign in to comment.