Skip to content

Commit 7b239bd

Browse files
committed
up
1 parent fcacb5d commit 7b239bd

File tree

10 files changed

+319
-16
lines changed

10 files changed

+319
-16
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ Telegram:[@lgc2333](https://t.me/lgc2333)
156156

157157
- HTML 渲染方案
158158

159-
### [故梦 API](https://api.gumengya.com) & [LoliApi](https://docs.loliapi.com/) & [Lolicon API](https://api.lolicon.app/)
159+
### [LoliApi](https://docs.loliapi.com/) & [Lolicon API](https://api.lolicon.app/)
160160

161161
- 背景图来源
162162

examples/external_example/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# External Example
2+
3+
这是一个如何为本插件增加自定义内容的插件示例
4+
5+
你可以直接把此文件夹当做插件包扔给 nonebot2 加载,并按照下方说明更改配置项查看效果
6+
7+
```properties
8+
PS_TEMPLATE=example_template
9+
PS_BG_PROVIDER=lgc_icon
10+
```

examples/external_example/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# ruff: noqa: E402
2+
3+
from nonebot import require
4+
5+
require("nonebot_plugin_picstatus") # 别忘 require
6+
7+
from . import (
8+
bg_provider as bg_provider,
9+
collectors as collectors,
10+
templates as templates,
11+
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from httpx import AsyncClient
2+
from nonebot_plugin_picstatus.bg_provider import BgData, bg_provider, resp_to_bg_data
3+
from nonebot_plugin_picstatus.config import config
4+
5+
# 添加自定义背景源演示
6+
# 实际应用例请见 https://github.com/lgc-NB2Dev/nonebot-plugin-picstatus/blob/master/nonebot_plugin_picstatus/bg_provider.py
7+
8+
9+
# 需要用 bg_provider 装饰器注册函数为背景源
10+
# 背景源名称默认为函数名,当然你也可以手动指定名称,比如
11+
# @bg_provider("lgc_icon")
12+
@bg_provider()
13+
async def lgc_icon() -> BgData:
14+
async with AsyncClient(
15+
follow_redirects=True,
16+
proxies=config.proxy,
17+
timeout=config.ps_req_timeout,
18+
) as cli:
19+
return resp_to_bg_data(
20+
(
21+
await cli.get("https://blog.lgc2333.top/assets/favicon.png")
22+
).raise_for_status(),
23+
)
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import random
2+
from typing_extensions import override
3+
4+
from nonebot_plugin_picstatus.collectors import (
5+
TimeBasedCounterCollector,
6+
collector,
7+
first_time_collector,
8+
normal_collector,
9+
periodic_collector,
10+
)
11+
12+
# 添加自定义数据源展示
13+
# 实际应用例请见 https://github.com/lgc-NB2Dev/nonebot-plugin-picstatus/tree/master/nonebot_plugin_picstatus/collectors
14+
15+
# PicStatus 内置了三种可以直接用装饰器注册的 collector
16+
17+
# region 1. normal_collector
18+
19+
counter_count = 0
20+
21+
22+
# 该 collector 在每次生成状态图片时都会被调用
23+
# 使用该装饰器时,默认使用函数名作为数据源名称,你也可以手动指定,例如
24+
# @normal_collector("counter")
25+
@normal_collector()
26+
async def counter():
27+
global counter_count
28+
counter_count += 1
29+
return counter_count
30+
31+
32+
# endregion
33+
34+
# region 2. first_time_collector
35+
36+
first_time_counter_count = 0
37+
38+
39+
# 该 collector 只会在 nonebot 启动时被调用一次并缓存结果
40+
# 下面的例子中,该 collector 的结果将会始终为 1
41+
# 装饰器使用教程同上
42+
@first_time_collector()
43+
async def first_time_counter():
44+
global first_time_counter_count
45+
first_time_counter_count += 1
46+
return first_time_counter_count
47+
48+
49+
# endregion
50+
51+
# region 3. periodic_collector
52+
53+
periodic_counter_count = 0
54+
55+
56+
# 该 collector 每隔一定时间被调用一次,并保存一定数量的结果在 deque 中
57+
# 调用的间隔与保留的结果数量可以在 PicStatus 的配置中找到
58+
# 装饰器使用教程同上
59+
@periodic_collector()
60+
async def periodic_counter():
61+
global periodic_counter_count
62+
periodic_counter_count += 1
63+
return periodic_counter_count
64+
65+
66+
# endregion
67+
68+
69+
# 此外 PicStatus 内置了另外一种较特殊的 collector
70+
# 它不可以直接用装饰器装饰一个函数使用
71+
72+
73+
# region 4. TimeBasedCounterCollector
74+
75+
76+
# 该 collector 基于 PeriodicCollector
77+
# 它会记录该次调用与上次调用的时间间隔
78+
# 你可以根据这段时间间隔来计算你希望展示并缓存的值
79+
# 这对展示 IO 速度等数据可能会很有帮助
80+
81+
82+
# 使用此 collector 你需要继承两个 abstract method
83+
# 并使用 collector 装饰器注册,注意在这里 collector 名称必须手动指定
84+
# 你也可以使用 collector 装饰器来注册上述 collector,不过具体操作请自行看插件源码理解
85+
@collector("time_counter")
86+
class TestTimeBasedCounter(TimeBasedCounterCollector[int, str]):
87+
@override
88+
async def _calc(self, past: int, now: int, time_passed: float) -> str:
89+
"""
90+
此方法计算你最终想要返回的结果
91+
92+
Args:
93+
past: 上次调用时返回的原始结果
94+
now: 本次调用时返回的原始结果
95+
time_passed: 从上次调用到本次调用的时间间隔(秒)
96+
97+
Returns:
98+
处理后的结果,和 PeriodicCollector 一样保存在 deque 中
99+
"""
100+
return f"{(now - past) / time_passed:.2f}/s"
101+
102+
@override
103+
async def _get_obj(self) -> int:
104+
"""此方法返回传入 _calc 方法中的原始结果"""
105+
return random.randint(0, 100)
106+
107+
108+
# endregion
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
def _import():
2+
from pathlib import Path
3+
4+
from cookit import auto_import
5+
6+
auto_import(Path(__file__).parent, __package__)
7+
8+
9+
_import()
10+
del _import
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# ruff: noqa: E402
2+
3+
import json
4+
from collections import deque
5+
from pathlib import Path
6+
from typing import Any, Dict, Optional
7+
8+
import jinja2 as jj
9+
from cookit.jinja import make_register_jinja_filter_deco
10+
from nonebot import get_plugin_config, require
11+
from nonebot_plugin_picstatus.bg_provider import BgData
12+
from nonebot_plugin_picstatus.templates import pic_template
13+
from nonebot_plugin_picstatus.templates.pw_render import (
14+
ROUTE_URL,
15+
add_background_router,
16+
add_root_router,
17+
base_router_group,
18+
register_global_filter_to,
19+
)
20+
from pydantic import BaseModel
21+
22+
# 添加自定义图片模板示例
23+
# 示例应用例请见 https://github.com/lgc-NB2Dev/nonebot-plugin-picstatus/blob/master/nonebot_plugin_picstatus/templates/default/__init__.py
24+
25+
# 可以使用你喜欢的任意库来绘图
26+
# 示例使用 playwright,你也可以使用 Pillow 等
27+
# 本插件中自带一些使用 playwright 时绘图有帮助的工具函数
28+
# 详见 https://github.com/lgc-NB2Dev/nonebot-plugin-picstatus/blob/master/nonebot_plugin_picstatus/templates/pw_render.py
29+
30+
require("nonebot_plugin_htmlrender")
31+
32+
from nonebot_plugin_htmlrender import get_new_page
33+
34+
RES_DIR = Path(__file__).parent / "res"
35+
36+
template_env = jj.Environment(
37+
loader=jj.FileSystemLoader(RES_DIR),
38+
autoescape=True,
39+
enable_async=True,
40+
)
41+
# 将插件提供的实用模板 filter 注册到模板环境中
42+
register_global_filter_to(template_env)
43+
44+
45+
jinja_filter = make_register_jinja_filter_deco(template_env)
46+
47+
48+
@jinja_filter
49+
def json_dumps(obj: Any, **kwargs) -> str:
50+
if isinstance(obj, dict):
51+
obj = {k: list(v) if isinstance(v, deque) else v for k, v in obj.items()}
52+
return json.dumps(obj, **kwargs)
53+
54+
55+
# 复制一份插件自带的路由组以便在该份代码范围中增删
56+
template_router_group = base_router_group.copy()
57+
58+
59+
# 可以把模板特定配置项声明在这里
60+
# 你也可以不在这里写,换成写到插件的 config.py 文件中,看个人喜好了
61+
class TemplateConfig(BaseModel):
62+
ps_example_template_example_config: Optional[str] = "example"
63+
64+
65+
template_config = get_plugin_config(TemplateConfig)
66+
67+
68+
# 注册模板渲染函数
69+
# name 参数为模板名称,不提供时使用函数名
70+
# collecting 传入需要启用的 collector 名称集合,如不提供则启用所有 collector
71+
@pic_template(
72+
collecting={
73+
"counter",
74+
"first_time_counter",
75+
"periodic_counter",
76+
"time_counter",
77+
},
78+
)
79+
async def example_template(collected: Dict[str, Any], bg: BgData, **_):
80+
template = template_env.get_template("index.html.jinja")
81+
html = await template.render_async(d=collected, config=template_config)
82+
83+
# copy 一份以添加这次图片渲染中特定的 router
84+
router_group = template_router_group.copy()
85+
add_root_router(router_group, html) # 注册根路由,访问返回渲好的 html
86+
add_background_router(router_group, bg) # 注册背景图片路由
87+
88+
async with get_new_page() as page:
89+
await router_group.apply(page)
90+
await page.goto(f"{ROUTE_URL}/", wait_until="load")
91+
elem = await page.wait_for_selector("main")
92+
assert elem
93+
return await elem.screenshot(type="jpeg")
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<style>
7+
main {
8+
background: linear-gradient(to bottom right, #159957, #155799);
9+
width: 600px;
10+
font-family: sans-serif;
11+
}
12+
13+
pre,
14+
code {
15+
font-family: monospace;
16+
}
17+
18+
.box {
19+
padding: 20px;
20+
background-color: #fff8;
21+
}
22+
</style>
23+
</head>
24+
<body>
25+
<main>
26+
<div style="padding: 20px">
27+
<div class="box">
28+
<div style="text-align: center">
29+
<h1>Example Template</h1>
30+
<p>This is an example status picture template.</p>
31+
</div>
32+
<div style="margin: auto; width: fit-content">
33+
<pre><code>d = {{ d | json_dumps(indent=2) }}</code></pre>
34+
<pre><code>config = {{ config.model_dump_json(indent=2) }}</code></pre>
35+
</div>
36+
<div style="text-align: center">
37+
<!-- 使用 router 获取背景图 -->
38+
<img src="/api/background" style="max-width: 300px; height: auto" />
39+
</div>
40+
</div>
41+
</div>
42+
</main>
43+
</body>
44+
</html>

nonebot_plugin_picstatus/collectors/__init__.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from ..config import config
2525

2626
T = TypeVar("T")
27+
TI = TypeVar("TI")
28+
TR = TypeVar("TR")
2729
TC = TypeVar("TC", bound="Collector")
2830
TCF = TypeVar("TCF", bound=Callable[[], Awaitable[Any]])
2931
R = TypeVar("R")
@@ -35,24 +37,29 @@ class SkipCollectError(Exception):
3537
pass
3638

3739

38-
class Collector(Generic[T]):
40+
class Collector(Generic[TI, TR]):
3941
@abstractmethod
40-
async def _get(self) -> T: ...
42+
async def _get(self) -> TI: ...
4143

42-
async def get(self) -> T:
43-
return await self._get()
44+
@abstractmethod
45+
async def get(self) -> TR: ...
4446

4547

46-
class BaseNormalCollector(Collector[T], Generic[T]):
48+
class BaseNormalCollector(Collector[T, T], Generic[T]):
4749
def __init__(self) -> None:
4850
super().__init__()
4951

52+
@override
53+
async def get(self) -> T:
54+
return await self._get()
55+
5056

51-
class BaseFirstTimeCollector(Collector[T], Generic[T]):
57+
class BaseFirstTimeCollector(Collector[T, T], Generic[T]):
5258
def __init__(self) -> None:
5359
super().__init__()
5460
self._cached: Union[T, Undefined] = Undefined()
5561

62+
@override
5663
async def get(self) -> T:
5764
if not isinstance(self._cached, Undefined):
5865
return self._cached
@@ -61,21 +68,18 @@ async def get(self) -> T:
6168
return data
6269

6370

64-
class BasePeriodicCollector(Collector[Deque[T]], Generic[T]):
71+
class BasePeriodicCollector(Collector[T, Deque[T]], Generic[T]):
6572
def __init__(self, size: int = config.ps_default_collect_cache_size) -> None:
6673
super().__init__()
6774
self.data = deque(maxlen=size)
6875

6976
@override
70-
async def _get(self) -> Deque[T]:
77+
async def get(self) -> Deque[T]:
7178
return self.data
7279

73-
@abstractmethod
74-
async def _do_collect(self) -> T: ...
75-
7680
async def collect(self):
7781
try:
78-
data = await self._do_collect()
82+
data = await self._get()
7983
except SkipCollectError:
8084
return
8185
except Exception:
@@ -157,7 +161,7 @@ async def _calc(self, past: T, now: T, time_passed: float) -> R: ...
157161
async def _get_obj(self) -> T: ...
158162

159163
@override
160-
async def _do_collect(self) -> R:
164+
async def _get(self) -> R:
161165
past = self.last_obj
162166
past_time = self.last_time
163167
time_now = time.time()

0 commit comments

Comments
 (0)