nonebot-plugin-apod 1.3.2__tar.gz → 1.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/PKG-INFO +35 -5
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/README.md +32 -3
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/pyproject.toml +3 -2
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/__init__.py +4 -4
- nonebot_plugin_apod-1.3.3/src/nonebot_plugin_apod/nasa.py +113 -0
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/utils.py +47 -44
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/apod.py +0 -0
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/config.py +0 -0
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/css/dark.css +0 -0
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/css/light.css +0 -0
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/infopuzzle.py +0 -0
- {nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/singleflight.py +0 -0
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nonebot-plugin-apod
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.3
|
|
4
4
|
Summary: 定时发送 NASA 每日提供的天文图片
|
|
5
5
|
Keywords: nonebot2,NASA
|
|
6
6
|
Author: lyqgzbl
|
|
7
7
|
Author-email: lyqgzbl <admin@lyqgzbl.com>
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
Requires-Dist: aiofiles>=24.1.0
|
|
10
|
+
Requires-Dist: tzdata>=2024.1
|
|
10
11
|
Requires-Dist: httpx>=0.28.0,<1.0.0
|
|
11
12
|
Requires-Dist: nonebot2>=2.2.1,<3.0.0
|
|
12
13
|
Requires-Dist: nonebot-plugin-argot>=0.2.2
|
|
13
14
|
Requires-Dist: nonebot-plugin-localstore>=0.7.0,<1.0.0
|
|
14
15
|
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0,<1.0.0
|
|
15
16
|
Requires-Dist: nonebot-plugin-alconna>=0.57.1,<1.0.0
|
|
16
|
-
Requires-Dist: nonebot-plugin-htmlrender>=0.
|
|
17
|
+
Requires-Dist: nonebot-plugin-htmlrender>=0.6.7,<0.7.0
|
|
17
18
|
Requires-Python: >=3.10
|
|
18
19
|
Project-URL: Documentation, https://github.com/lyqgzbl/nonebot-plugin-apod#readme
|
|
19
20
|
Project-URL: Homepage, https://github.com/lyqgzbl/nonebot-plugin-apod
|
|
@@ -81,11 +82,11 @@ pip install nonebot-plugin-apod
|
|
|
81
82
|
|
|
82
83
|
配置方式:直接在 NoneBot 全局配置文件中添加以下配置项即可
|
|
83
84
|
|
|
84
|
-
### apod_api_key [
|
|
85
|
+
### apod_api_key [直连时必填]
|
|
85
86
|
|
|
86
87
|
- 类型:`str`
|
|
87
88
|
- 默认值:`None`
|
|
88
|
-
- 说明:用于获取每日天文一图的 [NASA API Key](https://api.nasa.gov/)
|
|
89
|
+
- 说明:用于获取每日天文一图的 [NASA API Key](https://api.nasa.gov/)。配置镜像地址和镜像密钥后可省略;镜像失败后的直连回退使用 `DEMO_KEY`。
|
|
89
90
|
|
|
90
91
|
### apod_default_send_time [选填]
|
|
91
92
|
|
|
@@ -159,4 +160,33 @@ pip install nonebot-plugin-apod
|
|
|
159
160
|
- 默认值:`https://api.openai.com/v1`
|
|
160
161
|
- 说明:OpenAI 兼容接口的 Base API URL(如 DeepSeek 可填 `https://api.deepseek.com`,Qwen 可填 `https://dashscope.aliyuncs.com/compatible-mode/v1`)
|
|
161
162
|
|
|
162
|
-
> **注**:为保持向后兼容,旧版本的 `apod_qwen_trans`、`apod_qwen_mt_model_name`、`apod_qwen_mt_api_key` 和 `apod_qwen_mt_api_url` 配置项依然生效。
|
|
163
|
+
> **注**:为保持向后兼容,旧版本的 `apod_qwen_trans`、`apod_qwen_mt_model_name`、`apod_qwen_mt_api_key` 和 `apod_qwen_mt_api_url` 配置项依然生效。
|
|
164
|
+
|
|
165
|
+
## NASA Science 主站迁移
|
|
166
|
+
|
|
167
|
+
直连使用 `https://science.nasa.gov/wp-json/wp/v2/apod-basic`。插件兼容对象和数组响应,严格匹配日期;集合接口未返回目标日期时,使用同端点下的 `YYMMDD` 日期路径。今日数据和缓存日期以 `America/New_York` 为准。
|
|
168
|
+
|
|
169
|
+
NASA 图片响应中的 `url` 可能指向文章页,因此插件将 `hdurl` 映射为内部图片 `url`,并将标题、说明和版权中的 HTML 转为纯文本。镜像响应的 `url` 保持不变,继续使用 apod-server 的图片缓存。视频仍提示为天文视频。
|
|
170
|
+
|
|
171
|
+
新端点的 `count=1` 实测返回最近多条记录,因此随机功能改为在 1995-06-16 至 NASA 当天之间随机选日期,再请求该日期;最多尝试三个日期。今日、指定日期和随机功能均支持镜像优先、NASA 直连回退。
|
|
172
|
+
|
|
173
|
+
镜像配置示例:
|
|
174
|
+
|
|
175
|
+
```dotenv
|
|
176
|
+
APOD_MIRROR_URL=https://your-apod-server.example/v1/apod
|
|
177
|
+
APOD_MIRROR_API_KEY=your_server_bearer_key
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`APOD_MIRROR_URL` 是完整元数据接口地址,`APOD_MIRROR_API_KEY` 对应服务端 `API_AUTH_KEY`,不等同于 NASA API Key。启用高清原图时仍可能直接访问 NASA 图片 CDN。
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
### htmlrender 版本兼容
|
|
184
|
+
|
|
185
|
+
当前拼图使用 `md_to_pic` 接口,依赖限制为 `nonebot-plugin-htmlrender>=0.6.7,<0.7.0`。htmlrender 新版更改了公开接口,0.8 已移除该旧接口,不能直接升级到新版。若安装环境出现 `cannot import name 'md_to_pic'`,请在机器人项目中重新解析依赖并同步环境;使用 uv 的项目可执行:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
uv add "nonebot-plugin-htmlrender>=0.6.7,<0.7.0"
|
|
189
|
+
uv sync
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
仅更新 APOD 源码不会自动调整已安装依赖。后续若迁移新版 htmlrender,需要同时适配渲染 API、返回值、Provider 和配置。
|
|
@@ -59,11 +59,11 @@ pip install nonebot-plugin-apod
|
|
|
59
59
|
|
|
60
60
|
配置方式:直接在 NoneBot 全局配置文件中添加以下配置项即可
|
|
61
61
|
|
|
62
|
-
### apod_api_key [
|
|
62
|
+
### apod_api_key [直连时必填]
|
|
63
63
|
|
|
64
64
|
- 类型:`str`
|
|
65
65
|
- 默认值:`None`
|
|
66
|
-
- 说明:用于获取每日天文一图的 [NASA API Key](https://api.nasa.gov/)
|
|
66
|
+
- 说明:用于获取每日天文一图的 [NASA API Key](https://api.nasa.gov/)。配置镜像地址和镜像密钥后可省略;镜像失败后的直连回退使用 `DEMO_KEY`。
|
|
67
67
|
|
|
68
68
|
### apod_default_send_time [选填]
|
|
69
69
|
|
|
@@ -137,4 +137,33 @@ pip install nonebot-plugin-apod
|
|
|
137
137
|
- 默认值:`https://api.openai.com/v1`
|
|
138
138
|
- 说明:OpenAI 兼容接口的 Base API URL(如 DeepSeek 可填 `https://api.deepseek.com`,Qwen 可填 `https://dashscope.aliyuncs.com/compatible-mode/v1`)
|
|
139
139
|
|
|
140
|
-
> **注**:为保持向后兼容,旧版本的 `apod_qwen_trans`、`apod_qwen_mt_model_name`、`apod_qwen_mt_api_key` 和 `apod_qwen_mt_api_url` 配置项依然生效。
|
|
140
|
+
> **注**:为保持向后兼容,旧版本的 `apod_qwen_trans`、`apod_qwen_mt_model_name`、`apod_qwen_mt_api_key` 和 `apod_qwen_mt_api_url` 配置项依然生效。
|
|
141
|
+
|
|
142
|
+
## NASA Science 主站迁移
|
|
143
|
+
|
|
144
|
+
直连使用 `https://science.nasa.gov/wp-json/wp/v2/apod-basic`。插件兼容对象和数组响应,严格匹配日期;集合接口未返回目标日期时,使用同端点下的 `YYMMDD` 日期路径。今日数据和缓存日期以 `America/New_York` 为准。
|
|
145
|
+
|
|
146
|
+
NASA 图片响应中的 `url` 可能指向文章页,因此插件将 `hdurl` 映射为内部图片 `url`,并将标题、说明和版权中的 HTML 转为纯文本。镜像响应的 `url` 保持不变,继续使用 apod-server 的图片缓存。视频仍提示为天文视频。
|
|
147
|
+
|
|
148
|
+
新端点的 `count=1` 实测返回最近多条记录,因此随机功能改为在 1995-06-16 至 NASA 当天之间随机选日期,再请求该日期;最多尝试三个日期。今日、指定日期和随机功能均支持镜像优先、NASA 直连回退。
|
|
149
|
+
|
|
150
|
+
镜像配置示例:
|
|
151
|
+
|
|
152
|
+
```dotenv
|
|
153
|
+
APOD_MIRROR_URL=https://your-apod-server.example/v1/apod
|
|
154
|
+
APOD_MIRROR_API_KEY=your_server_bearer_key
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`APOD_MIRROR_URL` 是完整元数据接口地址,`APOD_MIRROR_API_KEY` 对应服务端 `API_AUTH_KEY`,不等同于 NASA API Key。启用高清原图时仍可能直接访问 NASA 图片 CDN。
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
### htmlrender 版本兼容
|
|
161
|
+
|
|
162
|
+
当前拼图使用 `md_to_pic` 接口,依赖限制为 `nonebot-plugin-htmlrender>=0.6.7,<0.7.0`。htmlrender 新版更改了公开接口,0.8 已移除该旧接口,不能直接升级到新版。若安装环境出现 `cannot import name 'md_to_pic'`,请在机器人项目中重新解析依赖并同步环境;使用 uv 的项目可执行:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
uv add "nonebot-plugin-htmlrender>=0.6.7,<0.7.0"
|
|
166
|
+
uv sync
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
仅更新 APOD 源码不会自动调整已安装依赖。后续若迁移新版 htmlrender,需要同时适配渲染 API、返回值、Provider 和配置。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nonebot-plugin-apod"
|
|
3
|
-
version = "1.3.
|
|
3
|
+
version = "1.3.3"
|
|
4
4
|
description = "定时发送 NASA 每日提供的天文图片"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "lyqgzbl", email = "admin@lyqgzbl.com" }]
|
|
@@ -9,13 +9,14 @@ keywords = ["nonebot2", "NASA"]
|
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"aiofiles>=24.1.0",
|
|
12
|
+
"tzdata>=2024.1",
|
|
12
13
|
"httpx >=0.28.0, <1.0.0",
|
|
13
14
|
"nonebot2 >=2.2.1, <3.0.0",
|
|
14
15
|
"nonebot-plugin-argot >=0.2.2",
|
|
15
16
|
"nonebot-plugin-localstore >=0.7.0, <1.0.0",
|
|
16
17
|
"nonebot-plugin-apscheduler >=0.5.0, <1.0.0",
|
|
17
18
|
"nonebot-plugin-alconna >=0.57.1, <1.0.0",
|
|
18
|
-
"nonebot-plugin-htmlrender >=0.
|
|
19
|
+
"nonebot-plugin-htmlrender >=0.6.7, <0.7.0",
|
|
19
20
|
]
|
|
20
21
|
|
|
21
22
|
[project.urls]
|
|
@@ -47,7 +47,7 @@ __plugin_meta__ = PluginMetadata(
|
|
|
47
47
|
supported_adapters=inherit_supported_adapters("nonebot_plugin_alconna"),
|
|
48
48
|
extra={
|
|
49
49
|
"author": "lyqgzbl <admin@lyqgzbl.com>",
|
|
50
|
-
"version": "1.3.
|
|
50
|
+
"version": "1.3.3",
|
|
51
51
|
},
|
|
52
52
|
)
|
|
53
53
|
|
|
@@ -60,15 +60,15 @@ apod_cache_json = store.get_plugin_cache_file("apod.json")
|
|
|
60
60
|
task_config_file = store.get_plugin_data_file("apod_task_config.json")
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
if not plugin_config.apod_api_key:
|
|
63
|
+
if not (plugin_config.apod_api_key or (mirror_url and mirror_api_key)):
|
|
64
64
|
logger.opt(colors=True).warning(
|
|
65
|
-
"<yellow
|
|
65
|
+
"<yellow>未配置 NASA API Key 或完整镜像配置,已禁用该插件</yellow>"
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
def is_enable() -> Rule:
|
|
70
70
|
def _rule() -> bool:
|
|
71
|
-
return bool(plugin_config.apod_api_key)
|
|
71
|
+
return bool(plugin_config.apod_api_key or (mirror_url and mirror_api_key))
|
|
72
72
|
|
|
73
73
|
return Rule(_rule)
|
|
74
74
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Normalize NASA Science data to the plugin's existing APOD contract."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from datetime import date, datetime
|
|
5
|
+
from html.parser import HTMLParser
|
|
6
|
+
from urllib.parse import urlsplit
|
|
7
|
+
from zoneinfo import ZoneInfo
|
|
8
|
+
|
|
9
|
+
NASA_API_URL = "https://science.nasa.gov/wp-json/wp/v2/apod-basic"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def nasa_today() -> date:
|
|
13
|
+
return datetime.now(ZoneInfo("America/New_York")).date()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _Text(HTMLParser):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__(convert_charrefs=True)
|
|
19
|
+
self.parts: list[str] = []
|
|
20
|
+
self.hidden = 0
|
|
21
|
+
|
|
22
|
+
def handle_starttag(self, tag, attrs):
|
|
23
|
+
if tag in {"script", "style", "noscript"}:
|
|
24
|
+
self.hidden += 1
|
|
25
|
+
if tag in {"p", "br", "div"}:
|
|
26
|
+
self.parts.append(" ")
|
|
27
|
+
|
|
28
|
+
def handle_endtag(self, tag):
|
|
29
|
+
if tag in {"script", "style", "noscript"} and self.hidden:
|
|
30
|
+
self.hidden -= 1
|
|
31
|
+
if tag in {"p", "div"}:
|
|
32
|
+
self.parts.append(" ")
|
|
33
|
+
|
|
34
|
+
def handle_data(self, data):
|
|
35
|
+
if not self.hidden:
|
|
36
|
+
self.parts.append(data)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def plain_text(value: object) -> str:
|
|
40
|
+
if not isinstance(value, str):
|
|
41
|
+
return ""
|
|
42
|
+
parser = _Text()
|
|
43
|
+
parser.feed(value)
|
|
44
|
+
return " ".join("".join(parser.parts).split())
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def valid_media(
|
|
48
|
+
value: object, permalink: object = None, *, allow_article: bool = False
|
|
49
|
+
) -> bool:
|
|
50
|
+
if (
|
|
51
|
+
not isinstance(value, str)
|
|
52
|
+
or not value.strip()
|
|
53
|
+
or (not allow_article and value == permalink)
|
|
54
|
+
):
|
|
55
|
+
return False
|
|
56
|
+
try:
|
|
57
|
+
url = urlsplit(value)
|
|
58
|
+
return (
|
|
59
|
+
url.scheme in {"http", "https"}
|
|
60
|
+
and bool(url.hostname)
|
|
61
|
+
and url.username is None
|
|
62
|
+
and not (
|
|
63
|
+
not allow_article
|
|
64
|
+
and url.hostname == "science.nasa.gov"
|
|
65
|
+
and url.path.startswith("/image-article/")
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
except ValueError:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def normalize_apod(payload: object, expected_date: str, *, nasa: bool) -> dict | None:
|
|
73
|
+
records = payload if isinstance(payload, list) else [payload]
|
|
74
|
+
for record in records:
|
|
75
|
+
if not isinstance(record, dict) or record.get("date") != expected_date:
|
|
76
|
+
continue
|
|
77
|
+
result = _normalize_record(record, nasa=nasa)
|
|
78
|
+
if result is not None:
|
|
79
|
+
return result
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _normalize_record(record: dict, *, nasa: bool) -> dict | None:
|
|
84
|
+
kind = record.get("media_type")
|
|
85
|
+
media = record.get("hdurl") if nasa and kind == "image" else record.get("url")
|
|
86
|
+
if kind not in {"image", "video"} or not valid_media(
|
|
87
|
+
media, record.get("permalink"), allow_article=kind == "video"
|
|
88
|
+
):
|
|
89
|
+
return None
|
|
90
|
+
title = plain_text(record.get("title")) if nasa else record.get("title")
|
|
91
|
+
explanation = record.get("explanation")
|
|
92
|
+
if nasa:
|
|
93
|
+
explanation = plain_text(explanation)
|
|
94
|
+
explanation = re.sub(r"^Explanation:\s*", "", explanation, flags=re.I)
|
|
95
|
+
explanation = re.split(
|
|
96
|
+
r"APOD['’]s (?:email for image submissions|main NASA site)"
|
|
97
|
+
r"|Tomorrow['’]s picture:",
|
|
98
|
+
explanation,
|
|
99
|
+
maxsplit=1,
|
|
100
|
+
flags=re.I,
|
|
101
|
+
)[0].strip()
|
|
102
|
+
if not isinstance(title, str) or not title.strip():
|
|
103
|
+
return None
|
|
104
|
+
if not isinstance(explanation, str) or not explanation.strip():
|
|
105
|
+
return None
|
|
106
|
+
result = dict(record, title=title, explanation=explanation, url=media)
|
|
107
|
+
if nasa:
|
|
108
|
+
result["copyright"] = plain_text(record.get("copyright"))
|
|
109
|
+
result["service_version"] = "v1"
|
|
110
|
+
result.pop("basic_html", None)
|
|
111
|
+
if kind == "image" and not valid_media(result.get("hdurl")):
|
|
112
|
+
result["hdurl"] = media
|
|
113
|
+
return result
|
|
@@ -4,7 +4,7 @@ import random
|
|
|
4
4
|
import hashlib
|
|
5
5
|
import asyncio
|
|
6
6
|
import contextlib
|
|
7
|
-
from datetime import datetime
|
|
7
|
+
from datetime import date as calendar_date, datetime, timedelta
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
@@ -14,13 +14,13 @@ from nonebot import get_driver
|
|
|
14
14
|
import nonebot_plugin_localstore as store
|
|
15
15
|
|
|
16
16
|
from .config import plugin_config
|
|
17
|
+
from .nasa import NASA_API_URL, nasa_today, normalize_apod
|
|
17
18
|
|
|
18
19
|
nasa_api_key = plugin_config.apod_api_key
|
|
19
20
|
baidu_trans = plugin_config.apod_baidu_trans
|
|
20
21
|
deepl_trans = plugin_config.apod_deepl_trans
|
|
21
22
|
openai_trans = plugin_config.apod_openai_trans or plugin_config.apod_qwen_trans
|
|
22
23
|
apod_infopuzzle = plugin_config.apod_infopuzzle
|
|
23
|
-
NASA_API_URL = "https://api.nasa.gov/planetary/apod"
|
|
24
24
|
baidu_trans_appid = plugin_config.apod_baidu_trans_appid
|
|
25
25
|
DEEPL_API_URL = "https://api-free.deepl.com/v2/translate"
|
|
26
26
|
deepl_trans_api_key = plugin_config.apod_deepl_trans_api_key
|
|
@@ -103,9 +103,9 @@ async def ensure_apod_data() -> bool:
|
|
|
103
103
|
try:
|
|
104
104
|
content = apod_cache_json.read_text(encoding="utf-8")
|
|
105
105
|
data = json.loads(content)
|
|
106
|
-
cached_date = data.get("date", "")
|
|
107
|
-
today =
|
|
108
|
-
if cached_date == today:
|
|
106
|
+
cached_date = data.get("date", "") if isinstance(data, dict) else ""
|
|
107
|
+
today = nasa_today().isoformat()
|
|
108
|
+
if isinstance(data, dict) and cached_date == today:
|
|
109
109
|
return True
|
|
110
110
|
logger.debug(f"天文一图数据缓存过期({cached_date}),将重新获取")
|
|
111
111
|
except (json.JSONDecodeError, KeyError, OSError) as e:
|
|
@@ -267,55 +267,54 @@ async def translate_text_auto(text: str, timeout: int = 8) -> str:
|
|
|
267
267
|
|
|
268
268
|
|
|
269
269
|
async def fetch_apod_data() -> bool:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
response = await client.get(NASA_API_URL, params={"api_key": nasa_api_key})
|
|
273
|
-
response.raise_for_status()
|
|
274
|
-
data = response.json()
|
|
275
|
-
async with aiofiles.open(apod_cache_json, "w", encoding="utf-8") as f:
|
|
276
|
-
await f.write(json.dumps(data, indent=4))
|
|
277
|
-
return True
|
|
278
|
-
except (httpx.RequestError, httpx.HTTPStatusError) as e:
|
|
279
|
-
logger.error(f"获取 NASA 每日天文一图数据时发生错误: {e}")
|
|
270
|
+
data = await fetch_apod_data_by_date(nasa_today().isoformat())
|
|
271
|
+
if data is None:
|
|
280
272
|
return False
|
|
273
|
+
async with aiofiles.open(apod_cache_json, "w", encoding="utf-8") as f:
|
|
274
|
+
await f.write(json.dumps(data, indent=4))
|
|
275
|
+
return True
|
|
281
276
|
|
|
282
277
|
|
|
283
278
|
async def fetch_apod_data_by_date(date: str) -> dict | None:
|
|
284
279
|
try:
|
|
280
|
+
parsed_date = calendar_date.fromisoformat(date)
|
|
285
281
|
client = get_httpx_client()
|
|
282
|
+
params = {"api_key": nasa_api_key or "DEMO_KEY", "date": date}
|
|
283
|
+
response = await client.get(NASA_API_URL, params=params)
|
|
284
|
+
response.raise_for_status()
|
|
285
|
+
data = normalize_apod(response.json(), date, nasa=True)
|
|
286
|
+
if data is not None:
|
|
287
|
+
return data
|
|
288
|
+
# The collection endpoint currently ignores some date queries. Its
|
|
289
|
+
# date-specific route returns the requested historical entry directly.
|
|
286
290
|
response = await client.get(
|
|
287
|
-
NASA_API_URL,
|
|
288
|
-
params={"api_key": nasa_api_key
|
|
291
|
+
f"{NASA_API_URL}/{parsed_date.strftime('%y%m%d')}",
|
|
292
|
+
params={"api_key": nasa_api_key or "DEMO_KEY"},
|
|
289
293
|
)
|
|
290
294
|
response.raise_for_status()
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return data
|
|
294
|
-
if isinstance(data, list) and len(data) > 0:
|
|
295
|
-
return data[0]
|
|
296
|
-
return None
|
|
297
|
-
except (httpx.RequestError, httpx.HTTPStatusError) as e:
|
|
295
|
+
return normalize_apod(response.json(), date, nasa=True)
|
|
296
|
+
except (httpx.RequestError, httpx.HTTPStatusError, ValueError) as e:
|
|
298
297
|
logger.error(f"获取 NASA 指定日期天文一图数据时发生错误: {e}")
|
|
299
298
|
return None
|
|
300
299
|
|
|
301
300
|
|
|
302
301
|
async def fetch_randomly_apod_data() -> dict | None:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
302
|
+
# Do not rely on count: NASA currently returns its latest collection even
|
|
303
|
+
# for count=1. Sample a date across the full APOD archive instead.
|
|
304
|
+
first = calendar_date(1995, 6, 16)
|
|
305
|
+
span = (nasa_today() - first).days
|
|
306
|
+
for _ in range(3):
|
|
307
|
+
selected = (first + timedelta(days=random.randint(0, span))).isoformat()
|
|
308
|
+
if mirror_url and mirror_api_key:
|
|
309
|
+
data = await fetch_apod_data_by_date_from_mirror(
|
|
310
|
+
mirror_url, mirror_api_key, selected
|
|
311
|
+
)
|
|
312
|
+
if data is not None:
|
|
313
|
+
return data
|
|
314
|
+
data = await fetch_apod_data_by_date(selected)
|
|
315
|
+
if data is not None:
|
|
314
316
|
return data
|
|
315
|
-
|
|
316
|
-
except (httpx.RequestError, httpx.HTTPStatusError) as e:
|
|
317
|
-
logger.error(f"获取 NASA 随机天文一图数据时发生错误: {e}")
|
|
318
|
-
return None
|
|
317
|
+
return None
|
|
319
318
|
|
|
320
319
|
|
|
321
320
|
async def fetch_apod_data_from_mirror(url: str, api_key: str) -> bool:
|
|
@@ -324,12 +323,17 @@ async def fetch_apod_data_from_mirror(url: str, api_key: str) -> bool:
|
|
|
324
323
|
headers = {"Authorization": f"Bearer {api_key}"}
|
|
325
324
|
response = await client.get(url, headers=headers)
|
|
326
325
|
response.raise_for_status()
|
|
327
|
-
|
|
326
|
+
payload = response.json()
|
|
327
|
+
# The mirror may intentionally serve its latest cached day.
|
|
328
|
+
date = payload.get("date") if isinstance(payload, dict) else None
|
|
329
|
+
data = normalize_apod(payload, date, nasa=False) if date else None
|
|
330
|
+
if data is None:
|
|
331
|
+
return False
|
|
328
332
|
async with aiofiles.open(apod_cache_json, "w", encoding="utf-8") as f:
|
|
329
333
|
await f.write(json.dumps(data, indent=4))
|
|
330
334
|
logger.debug("成功通过镜像获取天文一图数据")
|
|
331
335
|
return True
|
|
332
|
-
except (httpx.RequestError, httpx.HTTPStatusError) as e:
|
|
336
|
+
except (httpx.RequestError, httpx.HTTPStatusError, ValueError) as e:
|
|
333
337
|
logger.error(f"通过镜像获取天文一图数据时发生错误: {e}")
|
|
334
338
|
return False
|
|
335
339
|
|
|
@@ -346,9 +350,8 @@ async def fetch_apod_data_by_date_from_mirror(
|
|
|
346
350
|
params={"date": date},
|
|
347
351
|
)
|
|
348
352
|
response.raise_for_status()
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
except (httpx.RequestError, httpx.HTTPStatusError) as e:
|
|
353
|
+
return normalize_apod(response.json(), date, nasa=False)
|
|
354
|
+
except (httpx.RequestError, httpx.HTTPStatusError, ValueError) as e:
|
|
352
355
|
logger.error(f"通过镜像获取指定日期天文一图数据时发生错误: {e}")
|
|
353
356
|
return None
|
|
354
357
|
|
|
File without changes
|
|
File without changes
|
{nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/css/dark.css
RENAMED
|
File without changes
|
{nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/css/light.css
RENAMED
|
File without changes
|
{nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/infopuzzle.py
RENAMED
|
File without changes
|
{nonebot_plugin_apod-1.3.2 → nonebot_plugin_apod-1.3.3}/src/nonebot_plugin_apod/singleflight.py
RENAMED
|
File without changes
|