Skip to content

Commit 14050d4

Browse files
authored
v2.5.0: 正则表达式适配国内直连网站,引入新插件[jm-server],实现基于浏览器观看本地本子 (#192) (#194)
1 parent fc2f2a9 commit 14050d4

File tree

8 files changed

+203
-12
lines changed

8 files changed

+203
-12
lines changed

assets/docs/sources/TODO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Plan For Update Content
22

3-
| 版本范围 | 更新内容 |
3+
| 版本范围 | 更新计划 |
44
|:--------:|:--------------------------------------:|
5+
| v2.5.* | 引入新插件`jm-server`,实现基于浏览器观看本地本子。 |
56
| v2.4.* | 项目实现基本稳定,进入维护期,按需增加功能。 |
67
| v2.3.* | 实现移动端API的基础功能,统一HTML和API的实现。 |
78
| v2.2.* | 新的插件体系,新的命令行调用,完善搜索功能。 |

assets/docs/sources/option_file_syntax.md

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,22 @@
55
* option有`默认值`,当你使用配置文件来创建option时,你配置文件中的值会覆盖`默认值`
66

77
因此,在配置option时,不需要配置全部的值,只需要配置特定部分即可。
8-
98
* 你可以使用下面的代码来得到option的默认值,你可以删除其中的大部分配置项,只保留你要覆盖的配置项
109

1110
```python
1211
from jmcomic import JmOption
1312
JmOption.default().to_file('./option.yml') # 创建默认option,导出为option.yml文件
1413
```
1514

16-
## 2. option常用配置项
15+
## 2. option常规配置项
1716

1817
```yml
1918
# 配置客户端相关
2019
client:
21-
# impl: 客户端实现类,不配默认是html,表示网页端
20+
# impl: 客户端实现类,不配置默认会使用JmModuleConfig.DEFAULT_CLIENT_IMPL
21+
# 可配置:
22+
# html - 表示网页端
23+
# api - 表示使用APP端
2224
impl: html
2325

2426
# domain: 域名配置,默认是 [],表示运行时自动获取域名。
@@ -89,8 +91,8 @@ dir_rule:
8991
rule: Bd_Ptitle
9092
```
9193
92-
9394
## 3. option插件配置项
95+
9496
```yml
9597
# 插件的配置示例
9698
# 当kwargs的值为字符串类型时,支持使用环境变量,语法为 ${环境变量名}
@@ -109,11 +111,11 @@ plugins:
109111
- plugin: find_update # 只下载新章插件
110112
kwargs:
111113
145504: 290266 # 下载本子145504的章节290266以后的新章
112-
114+
113115
- plugin: image_suffix_filter # 图片后缀过滤器插件,可以控制只下载哪些后缀的图片
114116
kwargs:
115117
allowed_orig_suffix: # 后缀列表,表示只想下载以.gif结尾的图片
116-
- .gif
118+
- .gif
117119

118120
- plugin: client_proxy # 客户端实现类代理插件,不建议非开发人员使用
119121
kwargs:
@@ -124,6 +126,28 @@ plugins:
124126
kwargs:
125127
browser: chrome
126128
domain: 18comic.vip
129+
130+
# v2.5.0 引入的插件
131+
# 可以启动一个服务器,可以在浏览器上查看本子
132+
# 基于flask框架,需要安装额外库: pip install plugin_jm_server
133+
# 源码:https://github.com/hect0x7/plugin-jm-server
134+
- plugin: jm_server
135+
kwargs:
136+
password: '3333' # 服务器访问密码
137+
base_dir: D:/a/b/c/ # 根目录,默认使用dir_rule.base_dir
138+
139+
# 下面是高级配置,不配置也可以
140+
141+
# run下的参数是flask框架的app对象的run方法参数,详见flask文档
142+
run:
143+
host: 0.0.0.0 # 默认接收所有ip的请求
144+
port: 80 # 服务器端口,默认为80
145+
debug: false # 是否开启debug模式,默认为false
146+
147+
# 支持重写背景图片,可以使用你喜欢的背景图片作为背景
148+
img_overwrite:
149+
bg.jpg: D:/浏览器的背景图
150+
m_bg.jpeg: D:/移动设备浏览器的背景图
127151

128152
after_album:
129153
- plugin: zip # 压缩文件插件
@@ -156,4 +180,4 @@ plugins:
156180
filename_rule: Pid # pdf命名规则
157181
quality: 100 # pdf质量,0 - 100
158182

159-
```
183+
```

requirements-dev.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ PyYAML
44
Pillow
55
psutil
66
pycryptodome
7-
requests
7+
requests
8+
plugin_jm_server

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"Programming Language :: Python :: 3.9",
4343
"Programming Language :: Python :: 3.10",
4444
"Programming Language :: Python :: 3.11",
45+
"Programming Language :: Python :: 3.12",
4546
"Operating System :: MacOS",
4647
"Operating System :: POSIX :: Linux",
4748
"Operating System :: Microsoft :: Windows",

src/jmcomic/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# 被依赖方 <--- 使用方
33
# config <--- entity <--- toolkit <--- client <--- option <--- downloader
44

5-
__version__ = '2.4.14'
5+
__version__ = '2.5.0'
66

77
from .api import *
88
from .jm_plugin import *

src/jmcomic/jm_option.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ def __init__(self,
187187
# 其他配置
188188
self.filepath = filepath
189189

190+
# 需要主线程等待完成的插件
191+
self.need_wait_plugins = []
192+
190193
self.call_all_plugin('after_init', safe=True)
191194

192195
"""
@@ -630,3 +633,9 @@ def fix_kwargs(self, kwargs: Optional[Dict]) -> Dict[str, Any]:
630633
)
631634

632635
return new_kwargs
636+
637+
def wait_all_plugins_finish(self):
638+
from .jm_plugin import JmOptionPlugin
639+
for plugin in self.need_wait_plugins:
640+
plugin: JmOptionPlugin
641+
plugin.wait_until_finish()

src/jmcomic/jm_plugin.py

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ def execute_multi_line_cmd(self, cmd: str):
9494
import subprocess
9595
subprocess.run(cmd, shell=True, check=True)
9696

97+
def enter_wait_list(self):
98+
self.option.need_wait_plugins.append(self)
99+
100+
def leave_wait_list(self):
101+
self.option.need_wait_plugins.remove(self)
102+
103+
def wait_until_finish(self):
104+
pass
105+
97106

98107
class JmLoginPlugin(JmOptionPlugin):
99108
"""
@@ -407,7 +416,7 @@ def invoke(self,
407416

408417
proxy_clazz = JmModuleConfig.client_impl_class(proxy_client_key)
409418
clazz_init_kwargs = kwargs
410-
new_jm_client = self.option.new_jm_client
419+
new_jm_client: Callable = self.option.new_jm_client
411420

412421
def hook_new_jm_client(*args, **kwargs):
413422
client = new_jm_client(*args, **kwargs)
@@ -749,3 +758,149 @@ def generate_cmd():
749758

750759
paths.append(self.option.decide_image_save_dir(photo, ensure_exists=False))
751760
self.execute_deletion(paths)
761+
762+
763+
class JmServerPlugin(JmOptionPlugin):
764+
plugin_key = 'jm_server'
765+
766+
default_run_kwargs = {
767+
'host': '0.0.0.0',
768+
'port': '80',
769+
'debug': False,
770+
}
771+
772+
from threading import Lock
773+
single_instance_lock = Lock()
774+
775+
def __init__(self, option: JmOption):
776+
super().__init__(option)
777+
self.run_server_lock = Lock()
778+
self.running = False
779+
self.server_thread: Optional[Thread] = None
780+
781+
def invoke(self,
782+
password='',
783+
base_dir=None,
784+
album=None,
785+
photo=None,
786+
downloader=None,
787+
run=None,
788+
**kwargs
789+
):
790+
"""
791+
792+
:param password: 密码
793+
:param base_dir: 初始访问服务器的根路径
794+
:param album: 为了支持 after_album 这种调用时机
795+
:param photo: 为了支持 after_album 这种调用时机
796+
:param downloader: 为了支持 after_album 这种调用时机
797+
:param run: 用于启动服务器: app.run(**run_kwargs)
798+
:param kwargs: 用于JmServer构造函数: JmServer(base_dir, password, **kwargs)
799+
"""
800+
801+
if base_dir is None:
802+
base_dir = self.option.dir_rule.base_dir
803+
804+
if run is None:
805+
run = self.default_run_kwargs
806+
else:
807+
base_run_kwargs = self.default_run_kwargs.copy()
808+
base_run_kwargs.update(run)
809+
run = base_run_kwargs
810+
811+
if self.running is True:
812+
return
813+
814+
with self.run_server_lock:
815+
if self.running is True:
816+
return
817+
818+
self.running = True
819+
820+
# 服务器的代码位于一个独立库:plugin_jm_server,需要独立安装
821+
# 源代码仓库:https://github.com/hect0x7/plugin-jm-server
822+
try:
823+
import plugin_jm_server
824+
self.log(f'当前使用plugin_jm_server版本: {plugin_jm_server.__version__}')
825+
except ImportError:
826+
self.warning_lib_not_install('plugin_jm_server')
827+
return
828+
829+
# 核心函数,启动服务器,会阻塞当前线程
830+
def blocking_run_server():
831+
self.server_thread = current_thread()
832+
self.enter_wait_list()
833+
server = plugin_jm_server.JmServer(base_dir, password, **kwargs)
834+
# run方法会阻塞当前线程直到flask退出
835+
server.run(**run)
836+
837+
# 对于debug模式,特殊处理
838+
if run['debug'] is True:
839+
run.setdefault('use_reloader', False)
840+
841+
# debug模式只能在主线程启动,判断当前线程是不是主线程
842+
if current_thread() is not threading.main_thread():
843+
# 不是主线程,return
844+
return self.warning_wrong_usage_of_debug()
845+
else:
846+
# 是主线程,启动服务器
847+
blocking_run_server()
848+
849+
else:
850+
# 非debug模式,开新线程启动
851+
threading.Thread(target=blocking_run_server, daemon=True).start()
852+
atexit_register(self.wait_server_stop)
853+
854+
def warning_wrong_usage_of_debug(self):
855+
self.log('注意!当配置debug=True时,请确保当前插件是在主线程中被调用。\n'
856+
'因为如果本插件配置在 [after_album/after_photo] 这种时机调用,\n'
857+
'会使得flask框架不在主线程debug运行,\n'
858+
'导致报错(ValueError: signal only works in main thread of the main interpreter)。\n',
859+
'【基于上述原因,当前线程非主线程,不启动服务器】'
860+
'warning'
861+
)
862+
863+
def wait_server_stop(self, proactive=False):
864+
st = self.server_thread
865+
if (
866+
st is None
867+
or st == current_thread()
868+
or not st.is_alive()
869+
):
870+
return
871+
872+
if proactive:
873+
msg = f'[{self.plugin_key}]的服务器线程仍运行中,可按下ctrl+c结束程序'
874+
else:
875+
msg = f'主线程执行完毕,但插件[{self.plugin_key}]的服务器线程仍运行中,可按下ctrl+c结束程序'
876+
877+
self.log(msg, 'wait')
878+
879+
while st.is_alive():
880+
try:
881+
st.join(timeout=0.5)
882+
except KeyboardInterrupt:
883+
self.log('收到ctrl+c,结束程序', 'wait')
884+
return
885+
886+
def wait_until_finish(self):
887+
self.wait_server_stop(proactive=True)
888+
889+
@classmethod
890+
def build(cls, option: JmOption) -> 'JmOptionPlugin':
891+
"""
892+
单例模式
893+
"""
894+
field_name = 'single_instance'
895+
896+
instance = getattr(cls, field_name, None)
897+
if instance is not None:
898+
return instance
899+
900+
with cls.single_instance_lock:
901+
instance = getattr(cls, field_name, None)
902+
if instance is not None:
903+
return instance
904+
instance = JmServerPlugin(option)
905+
setattr(cls, field_name, instance)
906+
return instance

src/jmcomic/jm_toolkit.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class JmcomicText:
5151
# 點擊喜歡
5252
pattern_html_album_likes = compile(r'<span id="albim_likes_\d+">(.*?)</span>')
5353
# 觀看
54-
pattern_html_album_views = compile(r'<span>(.*?)</span>\n<span>(次觀看|观看次数)</span>')
54+
pattern_html_album_views = compile(r'<span>(.*?)</span>\n *<span>(次觀看|观看次数)</span>')
5555
# 評論(div)
5656
pattern_html_album_comment_count = compile(r'<div class="badge"[^>]*?id="total_video_comments">(\d+)</div>'), 0
5757

0 commit comments

Comments
 (0)