koishi-plugin-codex-reset 0.1.3 → 0.1.5
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.
- package/LICENSE +21 -21
- package/README.md +58 -54
- package/lib/calendar.js +105 -104
- package/lib/event.js +77 -76
- package/lib/history.js +98 -95
- package/lib/index.js +258 -63
- package/lib/probability.js +90 -90
- package/lib/stats.js +5 -5
- package/package.json +51 -51
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 lumia1998
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lumia1998
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,54 +1,58 @@
|
|
|
1
|
-
# koishi-plugin-codex-reset
|
|
2
|
-
|
|
3
|
-
监控 [AIHOT Codex 重置 API](https://aihot.news/api/v1/codex-resets) 的 Koishi 插件。
|
|
4
|
-
|
|
5
|
-
## 功能
|
|
6
|
-
|
|
7
|
-
- 定时轮询 AIHOT 的 Codex / ChatGPT Work 重置事件快照。
|
|
8
|
-
- 支持推送新的重置预告、已确认的全员重置和重置卡事件。
|
|
9
|
-
- 目标支持平台 + 频道配置,例如 `onebot` + `123456789`。
|
|
10
|
-
- `codex.probability` / `重置概率`:生成精美的概率统计卡片图片;没有 Puppeteer 时回退为纯文本。
|
|
11
|
-
- `codex.event` / `最近重置`:生成最近一次已确认重置的浅色卡片,展示距今时间、类型和来源帖子。
|
|
12
|
-
- `codex.calendar` / `重置日历`:生成本月的 PNG 日历图片,右侧固定展示最近发布的 2 条来源动态。
|
|
13
|
-
- `codex.history` / `重置历史`:生成公开重置事件的历史时间线图片,默认展示最近 1 个月。
|
|
14
|
-
- 日历中的 Tibo 头像作为本地资源随插件发布,不依赖用户访问 X 或运行时下载图片。
|
|
15
|
-
|
|
16
|
-
## 命令
|
|
17
|
-
|
|
18
|
-
命令都可以直接使用;历史时间线支持时间范围参数:
|
|
19
|
-
|
|
20
|
-
- `codex.probability`,别名 `重置概率`:读取当前 API 快照,生成概率统计卡片,展示未来 24 小时和 7 天概率、样本概览、事件构成和最近确认日期;没有 Puppeteer 时回退为纯文本。
|
|
21
|
-
- `codex.event`,别名 `最近重置`:发送最近一次已确认重置的浅色卡片;没有 Puppeteer 时回退为文本。
|
|
22
|
-
- `codex.calendar`,别名 `重置日历`:发送本月日历 PNG;右侧固定显示最近发布的 2 条动态。
|
|
23
|
-
- `codex.history`,别名 `重置历史`:发送历史时间线 PNG;`重置历史` 查看最近 1 个月,`重置历史 -3` 查看最近 3 个月,`重置历史 -a` 查看全部历史。
|
|
24
|
-
|
|
25
|
-
概率、最近重置、日历和历史时间线命令始终启用;安装并启用 `koishi-plugin-puppeteer` 后会发送对应图片,没有 Puppeteer 时概率和最近重置会回退为纯文本,日历和历史时间线会提示安装 Puppeteer。
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
# koishi-plugin-codex-reset
|
|
2
|
+
|
|
3
|
+
监控 [AIHOT Codex 重置 API](https://aihot.news/api/v1/codex-resets) 的 Koishi 插件。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 定时轮询 AIHOT 的 Codex / ChatGPT Work 重置事件快照。
|
|
8
|
+
- 支持推送新的重置预告、已确认的全员重置和重置卡事件。
|
|
9
|
+
- 目标支持平台 + 频道配置,例如 `onebot` + `123456789`。
|
|
10
|
+
- `codex.probability` / `重置概率`:生成精美的概率统计卡片图片;没有 Puppeteer 时回退为纯文本。
|
|
11
|
+
- `codex.event` / `最近重置`:生成最近一次已确认重置的浅色卡片,展示距今时间、类型和来源帖子。
|
|
12
|
+
- `codex.calendar` / `重置日历`:生成本月的 PNG 日历图片,右侧固定展示最近发布的 2 条来源动态。
|
|
13
|
+
- `codex.history` / `重置历史`:生成公开重置事件的历史时间线图片,默认展示最近 1 个月。
|
|
14
|
+
- 日历中的 Tibo 头像作为本地资源随插件发布,不依赖用户访问 X 或运行时下载图片。
|
|
15
|
+
|
|
16
|
+
## 命令
|
|
17
|
+
|
|
18
|
+
命令都可以直接使用;历史时间线支持时间范围参数:
|
|
19
|
+
|
|
20
|
+
- `codex.probability`,别名 `重置概率`:读取当前 API 快照,生成概率统计卡片,展示未来 24 小时和 7 天概率、样本概览、事件构成和最近确认日期;没有 Puppeteer 时回退为纯文本。
|
|
21
|
+
- `codex.event`,别名 `最近重置`:发送最近一次已确认重置的浅色卡片;没有 Puppeteer 时回退为文本。
|
|
22
|
+
- `codex.calendar`,别名 `重置日历`:发送本月日历 PNG;右侧固定显示最近发布的 2 条动态。
|
|
23
|
+
- `codex.history`,别名 `重置历史`:发送历史时间线 PNG;`重置历史` 查看最近 1 个月,`重置历史 -3` 查看最近 3 个月,`重置历史 -a` 查看全部历史。
|
|
24
|
+
|
|
25
|
+
概率、最近重置、日历和历史时间线命令始终启用;安装并启用 `koishi-plugin-puppeteer` 后会发送对应图片,没有 Puppeteer 时概率和最近重置会回退为纯文本,日历和历史时间线会提示安装 Puppeteer。
|
|
26
|
+
|
|
27
|
+
日历、月度统计及右侧动态仅展示已确认且日期不晚于今天的事件,不展示预告。日期优先使用实际发生日期,缺失时使用北京时间的确认日期;两者均缺失时不展示,不使用预计时间窗口代替实际日期。
|
|
28
|
+
|
|
29
|
+
首次安装时的第一份快照只建立基线,不会把历史事件全部推送。插件将已见事件状态和待发送通知保存在 Koishi 工作目录的 `data/codex-reset/` 中;重启后继续去重并重试未成功的目标。API 暂时漏掉旧事件、随后又补回时,也不会把它再次当作新事件。只有最近 48 小时内发布的预告或确认事件才会新建推送;超过 48 小时仍未送达的通知会放弃。更改 API 地址或推送目标后会建立新的基线。
|
|
30
|
+
|
|
31
|
+
图片生成最多同时进行 2 项,新的图片任务至少间隔 3 秒;历史图片超过 120 条事件时会提示缩小查询范围。图片中的长篇来源文字会截短。API 响应上限为 4 MiB。
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install koishi-plugin-codex-reset
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
如果需要概率卡片、最近重置卡片、日历或历史时间线图片,再安装并启用 `koishi-plugin-puppeteer`。不安装 Puppeteer 时,监控、最近重置和概率分析仍然可以使用,概率与最近重置命令回退为纯文本,日历和历史命令会给出提示。
|
|
40
|
+
|
|
41
|
+
## 配置
|
|
42
|
+
|
|
43
|
+
- `监控接口地址`:监控 API 地址,默认 `https://aihot.news/api/v1/codex-resets`。
|
|
44
|
+
- `监控间隔`:轮询间隔,单位为秒,默认 300 秒。
|
|
45
|
+
- `请求超时时间`:单次 API 请求超时时间,单位为毫秒,默认 15000 毫秒。
|
|
46
|
+
- `推送目标`:推送目标表,填写“平台”和“频道或群组 ID”;平台留空会按频道尝试在线 Bot,同一平台/频道只发送一次。
|
|
47
|
+
- `推送重置预告`:是否推送预告。
|
|
48
|
+
- `推送已确认重置`:是否推送已确认的重置/重置卡事件。
|
|
49
|
+
- `艾特全体`:推送通知时是否艾特全体,默认关闭;需要目标平台支持全体艾特。
|
|
50
|
+
|
|
51
|
+
API 是只读公开接口,不需要 API Key。插件使用 `ETag` 条件请求,并保留来源链接。请遵守 [AIHOT 公开使用规则](https://aihot.news/terms):公开可访问不等于自动获得对外商业使用、镜像或转售授权。
|
|
52
|
+
|
|
53
|
+
## 开发
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install
|
|
57
|
+
npm test
|
|
58
|
+
```
|
package/lib/calendar.js
CHANGED
|
@@ -45,7 +45,8 @@ function parseDateKey(value) {
|
|
|
45
45
|
function timestampDateKey(value) {
|
|
46
46
|
if (!value)
|
|
47
47
|
return null;
|
|
48
|
-
|
|
48
|
+
const date = new Date(value);
|
|
49
|
+
return Number.isFinite(date.getTime()) ? beijingDateKey(date) : null;
|
|
49
50
|
}
|
|
50
51
|
function beijingDateKey(now) {
|
|
51
52
|
const parts = new Intl.DateTimeFormat('en-US', {
|
|
@@ -69,13 +70,10 @@ function escapeHtml(value) {
|
|
|
69
70
|
.replaceAll("'", ''');
|
|
70
71
|
}
|
|
71
72
|
function getEventDateRange(event) {
|
|
72
|
-
if (event.status
|
|
73
|
-
return [event.occurredOn, event.occurredOn];
|
|
74
|
-
const from = timestampDateKey(event.schedule?.from) ?? timestampDateKey(event.confirmedAt) ?? timestampDateKey(event.createdAt);
|
|
75
|
-
if (!from)
|
|
73
|
+
if (event.status !== 'confirmed')
|
|
76
74
|
return null;
|
|
77
|
-
const
|
|
78
|
-
return [
|
|
75
|
+
const date = event.occurredOn ?? timestampDateKey(event.confirmedAt);
|
|
76
|
+
return date ? [date, date] : null;
|
|
79
77
|
}
|
|
80
78
|
function buildMonthMarks(events, start) {
|
|
81
79
|
const end = monthStart(start, 1);
|
|
@@ -133,10 +131,10 @@ function renderDayCell(date, month, marks, selectedDate, today) {
|
|
|
133
131
|
const className = mark.type === 'direct_reset' ? 'reset-mark' : 'credit-mark';
|
|
134
132
|
return `<span class="${className}">${label}</span>`;
|
|
135
133
|
}).join('');
|
|
136
|
-
return `<div class="${classes.join(' ')}" aria-label="${escapeHtml(labels)}">
|
|
137
|
-
<span class="number">${date.getUTCDate()}</span>
|
|
138
|
-
${key === today ? '<span class="today-dot"></span>' : ''}
|
|
139
|
-
<div class="marks">${marksHtml}</div>
|
|
134
|
+
return `<div class="${classes.join(' ')}" aria-label="${escapeHtml(labels)}">
|
|
135
|
+
<span class="number">${date.getUTCDate()}</span>
|
|
136
|
+
${key === today ? '<span class="today-dot"></span>' : ''}
|
|
137
|
+
<div class="marks">${marksHtml}</div>
|
|
140
138
|
</div>`;
|
|
141
139
|
}
|
|
142
140
|
function renderCalendarGrid(month, marks, selectedDate, today) {
|
|
@@ -152,20 +150,19 @@ function renderCalendarGrid(month, marks, selectedDate, today) {
|
|
|
152
150
|
return cells.join('');
|
|
153
151
|
}
|
|
154
152
|
function renderPostCard(event, post) {
|
|
155
|
-
const
|
|
156
|
-
const
|
|
153
|
+
const originalText = post.text || post.originalText;
|
|
154
|
+
const text = originalText.length > 500 ? `${originalText.slice(0, 500)}…` : originalText;
|
|
157
155
|
const avatar = TIBO_AVATAR
|
|
158
156
|
? `<img class="avatar-image" src="${TIBO_AVATAR}" alt="">`
|
|
159
157
|
: '<span class="avatar-fallback">T</span>';
|
|
160
|
-
return `<article class="post-card">
|
|
161
|
-
<div class="post-head">
|
|
162
|
-
<div class="avatar">${avatar}</div>
|
|
163
|
-
<div class="author"><strong>Tibo</strong><span>@thsottiaux</span></div>
|
|
164
|
-
<time>${escapeHtml(formatPublishedAt(post.publishedAt))}</time>
|
|
165
|
-
</div>
|
|
166
|
-
<p class="post-text">${escapeHtml(text)}</p>
|
|
167
|
-
|
|
168
|
-
<div class="post-foot">${escapeHtml(post.stage)}</div>
|
|
158
|
+
return `<article class="post-card">
|
|
159
|
+
<div class="post-head">
|
|
160
|
+
<div class="avatar">${avatar}</div>
|
|
161
|
+
<div class="author"><strong>Tibo</strong><span>@thsottiaux</span></div>
|
|
162
|
+
<time>${escapeHtml(formatPublishedAt(post.publishedAt))}</time>
|
|
163
|
+
</div>
|
|
164
|
+
<p class="post-text">${escapeHtml(text)}</p>
|
|
165
|
+
<div class="post-foot">${escapeHtml(post.stage)}</div>
|
|
169
166
|
</article>`;
|
|
170
167
|
}
|
|
171
168
|
function renderDetails(events) {
|
|
@@ -176,19 +173,23 @@ function renderDetails(events) {
|
|
|
176
173
|
const groups = latestPosts.map(({ event, post }) => {
|
|
177
174
|
const status = event.status === 'confirmed' ? '已重置' : '未重置预告';
|
|
178
175
|
const statusClass = event.status === 'confirmed' ? 'confirmed-state' : 'announced-state';
|
|
179
|
-
return `<section class="event-group">
|
|
180
|
-
<div class="event-state ${statusClass}"><span class="state-icon">${event.status === 'confirmed' ? '✓' : '◷'}</span>${status}</div>
|
|
181
|
-
<h2>${escapeHtml(event.title)}</h2>
|
|
182
|
-
${renderPostCard(event, post)}
|
|
176
|
+
return `<section class="event-group">
|
|
177
|
+
<div class="event-state ${statusClass}"><span class="state-icon">${event.status === 'confirmed' ? '✓' : '◷'}</span>${status}</div>
|
|
178
|
+
<h2>${escapeHtml(event.title.slice(0, 200))}</h2>
|
|
179
|
+
${renderPostCard(event, post)}
|
|
183
180
|
</section>`;
|
|
184
181
|
}).join('');
|
|
185
|
-
return `<aside class="details-pane">
|
|
186
|
-
<div class="details-header"><h2>最近 2 条动态</h2><span>北京时间</span></div>
|
|
187
|
-
<div class="details-body">${groups || '<div class="empty-state">暂无来源帖子内容</div>'}</div>
|
|
182
|
+
return `<aside class="details-pane">
|
|
183
|
+
<div class="details-header"><h2>最近 2 条动态</h2><span>北京时间</span></div>
|
|
184
|
+
<div class="details-body">${groups || '<div class="empty-state">暂无来源帖子内容</div>'}</div>
|
|
188
185
|
</aside>`;
|
|
189
186
|
}
|
|
190
187
|
function renderCalendarHtml(events, now = new Date()) {
|
|
191
188
|
const today = beijingDateKey(now);
|
|
189
|
+
events = events.filter((event) => {
|
|
190
|
+
const range = getEventDateRange(event);
|
|
191
|
+
return range !== null && parseDateKey(range[0]) !== null && range[0] <= today;
|
|
192
|
+
});
|
|
192
193
|
const todayDate = parseDateKey(today) ?? new Date();
|
|
193
194
|
const month = monthStart(todayDate);
|
|
194
195
|
const marks = buildMonthMarks(events, month);
|
|
@@ -199,81 +200,81 @@ function renderCalendarHtml(events, now = new Date()) {
|
|
|
199
200
|
const resetCount = monthEvents.filter((event) => event.type === 'direct_reset').length;
|
|
200
201
|
const creditCount = monthEvents.filter((event) => event.type === 'reset_credit').length;
|
|
201
202
|
const monthLabel = `${month.getUTCFullYear()} 年 ${MONTH_NAMES[month.getUTCMonth()]}`;
|
|
202
|
-
return `<!doctype html>
|
|
203
|
-
<html lang="zh-CN">
|
|
204
|
-
<head>
|
|
205
|
-
<meta charset="utf-8">
|
|
206
|
-
<style>
|
|
207
|
-
* { box-sizing: border-box; }
|
|
208
|
-
body { margin: 0; padding: 16px; background: #f5f6f4; color: #25313a; font-family: Inter, "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; }
|
|
209
|
-
.calendar-card { width: 1248px; min-height: 578px; display: grid; grid-template-columns: 1.46fr 1fr; overflow: hidden; border: 1px solid #d2dadd; border-radius: 14px; background: #fff; }
|
|
210
|
-
.calendar-pane { padding: 30px 24px 22px; }
|
|
211
|
-
.calendar-header { display: flex; justify-content: space-between; align-items: flex-start; padding: 0 0 18px; }
|
|
212
|
-
.calendar-header h1 { margin: 0; font-size: 23px; line-height: 1.3; letter-spacing: .2px; }
|
|
213
|
-
.month-summary { margin-top: 20px; color: #617079; font-size: 12px; }
|
|
214
|
-
.calendar-nav { display: flex; align-items: center; gap: 22px; padding-top: 3px; color: #617079; font-size: 11px; }
|
|
215
|
-
.calendar-nav .arrow { color: #617079; font-size: 20px; font-weight: 300; line-height: 1; }
|
|
216
|
-
.weekday-row, .calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
|
|
217
|
-
.weekday-row { margin-bottom: 6px; color: #65737b; font-size: 12px; text-align: center; }
|
|
218
|
-
.weekday-row span { padding: 8px 0; }
|
|
219
|
-
.calendar-grid { grid-auto-rows: 70px; }
|
|
220
|
-
.day { position: relative; min-width: 0; padding: 11px 12px 7px; border: 1px solid transparent; border-radius: 10px; background: #fafafa; }
|
|
221
|
-
.day.outside { color: #adb6b9; background: #fcfcfc; }
|
|
222
|
-
.day.selected { border-color: #147487; background: #edf7f6; box-shadow: inset 0 0 0 1px rgba(20, 116, 135, .12); }
|
|
223
|
-
.day.has-reset { background: #eef6f2; }
|
|
224
|
-
.day.has-credit { background: #f7f3ec; }
|
|
225
|
-
.day.selected.has-reset, .day.selected.has-credit { background: #e8f3f2; }
|
|
226
|
-
.number { display: inline-block; font-size: 14px; line-height: 1; }
|
|
227
|
-
.outside .number { color: #aeb7ba; }
|
|
228
|
-
.today-dot { position: absolute; top: 11px; right: 13px; width: 4px; height: 4px; border-radius: 50%; background: #147487; }
|
|
229
|
-
.marks { display: flex; flex-direction: column; gap: 2px; margin-top: 12px; overflow: hidden; }
|
|
230
|
-
.marks span { overflow: hidden; color: #347465; font-size: 9px; line-height: 1.2; text-overflow: ellipsis; white-space: nowrap; }
|
|
231
|
-
.marks .credit-mark { color: #967044; }
|
|
232
|
-
.outside .marks span { color: #b6d0c9; }
|
|
233
|
-
.outside .marks .credit-mark { color: #cbbda6; }
|
|
234
|
-
.calendar-note { margin-top: 18px; color: #617079; font-size: 11px; }
|
|
235
|
-
.details-pane { min-width: 0; padding: 26px 24px 20px; border-left: 1px solid #d9dfe0; }
|
|
236
|
-
.details-header { display: flex; justify-content: space-between; align-items: center; padding: 0 0 17px; border-bottom: 1px solid #d9dfe0; }
|
|
237
|
-
.details-header h2 { margin: 0; font-size: 18px; }
|
|
238
|
-
.details-header span { color: #65737b; font-size: 11px; }
|
|
239
|
-
.details-body { padding-top: 18px; }
|
|
240
|
-
.event-group + .event-group { margin-top: 22px; }
|
|
241
|
-
.event-state { display: flex; align-items: center; gap: 7px; margin-bottom: 12px; font-size: 11px; font-weight: 700; }
|
|
242
|
-
.confirmed-state { color: #29735f; }
|
|
243
|
-
.announced-state { color: #98713d; }
|
|
244
|
-
.state-icon { font-size: 16px; font-weight: 500; }
|
|
245
|
-
.event-group h2 { margin: 0 0 16px; font-size: 18px; line-height: 1.35; }
|
|
246
|
-
.post-card { margin-bottom: 12px; padding: 16px; border: 1px solid #d2dadd; border-radius: 13px; background: #fff; }
|
|
247
|
-
.post-head { display: flex; align-items: center; min-width: 0; }
|
|
248
|
-
.avatar { display: grid; flex: 0 0 auto; width: 36px; height: 36px; place-items: center; overflow: hidden; border-radius: 50%; background: linear-gradient(145deg, #a57954, #d3aa7d); color: #fff; font-size: 18px; font-weight: 700; }
|
|
249
|
-
.avatar-image { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
250
|
-
.avatar-fallback { display: block; }
|
|
251
|
-
.author { display: flex; flex-direction: column; margin-left: 9px; line-height: 1.25; }
|
|
252
|
-
.author strong { font-size: 13px; }
|
|
253
|
-
.author span { color: #6f7d83; font-size: 10px; }
|
|
254
|
-
.post-head time { margin-left: auto; color: #6f7d83; font-size: 10px; }
|
|
255
|
-
.post-text { margin: 14px 0 10px; color: #27343c; font-size: 14px; line-height: 1.65; }
|
|
256
|
-
.schedule { margin: 0 0 10px; color: #08768b; font-size: 11px; }
|
|
257
|
-
.post-foot { color: #6f7d83; font-size: 10px; }
|
|
258
|
-
.empty-state, .empty-post { color: #7a888d; font-size: 13px; text-align: center; }
|
|
259
|
-
.empty-state { padding: 56px 0; }
|
|
260
|
-
.empty-post { padding: 24px 12px; }
|
|
261
|
-
</style>
|
|
262
|
-
</head>
|
|
263
|
-
<body>
|
|
264
|
-
<main class="calendar-card">
|
|
265
|
-
<section class="calendar-pane">
|
|
266
|
-
<header class="calendar-header">
|
|
267
|
-
<div><h1>${monthLabel}</h1><div class="month-summary">本月:${resetCount} 条全员重置 · ${creditCount} 张重置卡</div></div>
|
|
268
|
-
<div class="calendar-nav"><span>回到最近</span><span class="arrow">‹</span><span class="arrow">›</span></div>
|
|
269
|
-
</header>
|
|
270
|
-
<div class="weekday-row">${WEEK_NAMES.map((name) => `<span>${name}</span>`).join('')}</div>
|
|
271
|
-
<div class="calendar-grid">${renderCalendarGrid(month, marks, selectedDate, today)}</div>
|
|
272
|
-
<div class="calendar-note"
|
|
273
|
-
</section>
|
|
274
|
-
${renderDetails(events)}
|
|
275
|
-
</main>
|
|
276
|
-
</body>
|
|
203
|
+
return `<!doctype html>
|
|
204
|
+
<html lang="zh-CN">
|
|
205
|
+
<head>
|
|
206
|
+
<meta charset="utf-8">
|
|
207
|
+
<style>
|
|
208
|
+
* { box-sizing: border-box; }
|
|
209
|
+
body { margin: 0; padding: 16px; background: #f5f6f4; color: #25313a; font-family: Inter, "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; }
|
|
210
|
+
.calendar-card { width: 1248px; min-height: 578px; display: grid; grid-template-columns: 1.46fr 1fr; overflow: hidden; border: 1px solid #d2dadd; border-radius: 14px; background: #fff; }
|
|
211
|
+
.calendar-pane { padding: 30px 24px 22px; }
|
|
212
|
+
.calendar-header { display: flex; justify-content: space-between; align-items: flex-start; padding: 0 0 18px; }
|
|
213
|
+
.calendar-header h1 { margin: 0; font-size: 23px; line-height: 1.3; letter-spacing: .2px; }
|
|
214
|
+
.month-summary { margin-top: 20px; color: #617079; font-size: 12px; }
|
|
215
|
+
.calendar-nav { display: flex; align-items: center; gap: 22px; padding-top: 3px; color: #617079; font-size: 11px; }
|
|
216
|
+
.calendar-nav .arrow { color: #617079; font-size: 20px; font-weight: 300; line-height: 1; }
|
|
217
|
+
.weekday-row, .calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
|
|
218
|
+
.weekday-row { margin-bottom: 6px; color: #65737b; font-size: 12px; text-align: center; }
|
|
219
|
+
.weekday-row span { padding: 8px 0; }
|
|
220
|
+
.calendar-grid { grid-auto-rows: 70px; }
|
|
221
|
+
.day { position: relative; min-width: 0; padding: 11px 12px 7px; border: 1px solid transparent; border-radius: 10px; background: #fafafa; }
|
|
222
|
+
.day.outside { color: #adb6b9; background: #fcfcfc; }
|
|
223
|
+
.day.selected { border-color: #147487; background: #edf7f6; box-shadow: inset 0 0 0 1px rgba(20, 116, 135, .12); }
|
|
224
|
+
.day.has-reset { background: #eef6f2; }
|
|
225
|
+
.day.has-credit { background: #f7f3ec; }
|
|
226
|
+
.day.selected.has-reset, .day.selected.has-credit { background: #e8f3f2; }
|
|
227
|
+
.number { display: inline-block; font-size: 14px; line-height: 1; }
|
|
228
|
+
.outside .number { color: #aeb7ba; }
|
|
229
|
+
.today-dot { position: absolute; top: 11px; right: 13px; width: 4px; height: 4px; border-radius: 50%; background: #147487; }
|
|
230
|
+
.marks { display: flex; flex-direction: column; gap: 2px; margin-top: 12px; overflow: hidden; }
|
|
231
|
+
.marks span { overflow: hidden; color: #347465; font-size: 9px; line-height: 1.2; text-overflow: ellipsis; white-space: nowrap; }
|
|
232
|
+
.marks .credit-mark { color: #967044; }
|
|
233
|
+
.outside .marks span { color: #b6d0c9; }
|
|
234
|
+
.outside .marks .credit-mark { color: #cbbda6; }
|
|
235
|
+
.calendar-note { margin-top: 18px; color: #617079; font-size: 11px; }
|
|
236
|
+
.details-pane { min-width: 0; padding: 26px 24px 20px; border-left: 1px solid #d9dfe0; }
|
|
237
|
+
.details-header { display: flex; justify-content: space-between; align-items: center; padding: 0 0 17px; border-bottom: 1px solid #d9dfe0; }
|
|
238
|
+
.details-header h2 { margin: 0; font-size: 18px; }
|
|
239
|
+
.details-header span { color: #65737b; font-size: 11px; }
|
|
240
|
+
.details-body { padding-top: 18px; }
|
|
241
|
+
.event-group + .event-group { margin-top: 22px; }
|
|
242
|
+
.event-state { display: flex; align-items: center; gap: 7px; margin-bottom: 12px; font-size: 11px; font-weight: 700; }
|
|
243
|
+
.confirmed-state { color: #29735f; }
|
|
244
|
+
.announced-state { color: #98713d; }
|
|
245
|
+
.state-icon { font-size: 16px; font-weight: 500; }
|
|
246
|
+
.event-group h2 { margin: 0 0 16px; font-size: 18px; line-height: 1.35; }
|
|
247
|
+
.post-card { margin-bottom: 12px; padding: 16px; border: 1px solid #d2dadd; border-radius: 13px; background: #fff; }
|
|
248
|
+
.post-head { display: flex; align-items: center; min-width: 0; }
|
|
249
|
+
.avatar { display: grid; flex: 0 0 auto; width: 36px; height: 36px; place-items: center; overflow: hidden; border-radius: 50%; background: linear-gradient(145deg, #a57954, #d3aa7d); color: #fff; font-size: 18px; font-weight: 700; }
|
|
250
|
+
.avatar-image { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
251
|
+
.avatar-fallback { display: block; }
|
|
252
|
+
.author { display: flex; flex-direction: column; margin-left: 9px; line-height: 1.25; }
|
|
253
|
+
.author strong { font-size: 13px; }
|
|
254
|
+
.author span { color: #6f7d83; font-size: 10px; }
|
|
255
|
+
.post-head time { margin-left: auto; color: #6f7d83; font-size: 10px; }
|
|
256
|
+
.post-text { margin: 14px 0 10px; color: #27343c; font-size: 14px; line-height: 1.65; }
|
|
257
|
+
.schedule { margin: 0 0 10px; color: #08768b; font-size: 11px; }
|
|
258
|
+
.post-foot { color: #6f7d83; font-size: 10px; }
|
|
259
|
+
.empty-state, .empty-post { color: #7a888d; font-size: 13px; text-align: center; }
|
|
260
|
+
.empty-state { padding: 56px 0; }
|
|
261
|
+
.empty-post { padding: 24px 12px; }
|
|
262
|
+
</style>
|
|
263
|
+
</head>
|
|
264
|
+
<body>
|
|
265
|
+
<main class="calendar-card">
|
|
266
|
+
<section class="calendar-pane">
|
|
267
|
+
<header class="calendar-header">
|
|
268
|
+
<div><h1>${monthLabel}</h1><div class="month-summary">本月:${resetCount} 条全员重置 · ${creditCount} 张重置卡</div></div>
|
|
269
|
+
<div class="calendar-nav"><span>回到最近</span><span class="arrow">‹</span><span class="arrow">›</span></div>
|
|
270
|
+
</header>
|
|
271
|
+
<div class="weekday-row">${WEEK_NAMES.map((name) => `<span>${name}</span>`).join('')}</div>
|
|
272
|
+
<div class="calendar-grid">${renderCalendarGrid(month, marks, selectedDate, today)}</div>
|
|
273
|
+
<div class="calendar-note">仅显示已确认事件;按北京时间记录实际发生日期,缺失时按确认日期展示;不含预告。</div>
|
|
274
|
+
</section>
|
|
275
|
+
${renderDetails(events)}
|
|
276
|
+
</main>
|
|
277
|
+
</body>
|
|
277
278
|
</html>`;
|
|
278
279
|
}
|
|
279
280
|
async function renderCalendarImage(browser, events, now = new Date()) {
|
package/lib/event.js
CHANGED
|
@@ -44,89 +44,90 @@ function renderSourcePost(summary) {
|
|
|
44
44
|
const avatar = (0, calendar_1.getTiboAvatarDataUri)()
|
|
45
45
|
? `<img class="avatar-image" src="${(0, calendar_1.getTiboAvatarDataUri)()}" alt="">`
|
|
46
46
|
: '<span class="avatar-fallback">T</span>';
|
|
47
|
-
const
|
|
47
|
+
const originalText = post.text || post.originalText;
|
|
48
|
+
const text = originalText.length > 500 ? `${originalText.slice(0, 500)}…` : originalText;
|
|
48
49
|
const publishedAt = Date.parse(post.publishedAt);
|
|
49
50
|
const time = Number.isFinite(publishedAt) ? formatDateTime(publishedAt) : '时间未知';
|
|
50
|
-
return `<article class="post-card">
|
|
51
|
-
<div class="post-head">
|
|
52
|
-
<div class="avatar">${avatar}</div>
|
|
53
|
-
<div class="author"><strong>Tibo</strong><span>@thsottiaux</span></div>
|
|
54
|
-
<time>${escapeHtml(time)} 发布</time>
|
|
55
|
-
</div>
|
|
56
|
-
<p class="post-text">${escapeHtml(text)}</p>
|
|
57
|
-
<div class="post-foot">${escapeHtml(post.stage)}</div>
|
|
51
|
+
return `<article class="post-card">
|
|
52
|
+
<div class="post-head">
|
|
53
|
+
<div class="avatar">${avatar}</div>
|
|
54
|
+
<div class="author"><strong>Tibo</strong><span>@thsottiaux</span></div>
|
|
55
|
+
<time>${escapeHtml(time)} 发布</time>
|
|
56
|
+
</div>
|
|
57
|
+
<p class="post-text">${escapeHtml(text)}</p>
|
|
58
|
+
<div class="post-foot">${escapeHtml(post.stage)}</div>
|
|
58
59
|
</article>`;
|
|
59
60
|
}
|
|
60
61
|
function renderLatestResetHtml(summary, now = new Date()) {
|
|
61
62
|
const approximate = summary.approximate ? '时间按公开日期估算' : '已记录精确确认时间';
|
|
62
63
|
const event = summary.event;
|
|
63
|
-
return `<!doctype html>
|
|
64
|
-
<html lang="zh-CN">
|
|
65
|
-
<head>
|
|
66
|
-
<meta charset="utf-8">
|
|
67
|
-
<style>
|
|
68
|
-
* { box-sizing: border-box; }
|
|
69
|
-
html, body { margin: 0; padding: 0; background: #f4f6f5; color: #27343c; font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif; }
|
|
70
|
-
body { padding: 24px; }
|
|
71
|
-
.card { position: relative; display: grid; grid-template-columns: .92fr 1.08fr; width: 1120px; min-height: 520px; overflow: hidden; border: 1px solid #d2dadd; border-radius: 16px; background: #fff; box-shadow: 0 16px 38px rgba(42, 58, 64, .10); }
|
|
72
|
-
.card::before { content: ""; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(circle at 7% 12%, rgba(8, 118, 139, .11), transparent 34%), radial-gradient(circle at 95% 96%, rgba(184, 134, 59, .10), transparent 34%); }
|
|
73
|
-
.overview, .source { position: relative; z-index: 1; padding: 38px 40px; }
|
|
74
|
-
.overview { display: flex; flex-direction: column; border-right: 1px solid #d9dfe0; background: linear-gradient(145deg, rgba(237, 247, 246, .8), rgba(255, 255, 255, .96) 64%); }
|
|
75
|
-
.source { background: linear-gradient(160deg, rgba(250, 246, 238, .68), rgba(255, 255, 255, .98) 55%); }
|
|
76
|
-
.eyebrow { color: #08768b; font-size: 11px; font-weight: 700; letter-spacing: .18em; }
|
|
77
|
-
.source .eyebrow { color: #a27831; }
|
|
78
|
-
h1 { margin: 18px 0 7px; color: #27343c; font-size: 31px; letter-spacing: -.04em; }
|
|
79
|
-
.subtitle { margin: 0; color: #6f7d83; font-size: 13px; line-height: 1.6; }
|
|
80
|
-
.elapsed { margin-top: 40px; color: #08768b; font-family: Georgia, "Times New Roman", serif; font-size: 54px; font-weight: 700; letter-spacing: -.06em; line-height: 1; }
|
|
81
|
-
.elapsed-label { margin-top: 8px; color: #617079; font-size: 12px; }
|
|
82
|
-
.facts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: auto; padding-top: 32px; }
|
|
83
|
-
.fact { padding: 13px 14px; border: 1px solid #c8dedd; border-radius: 11px; background: rgba(255, 255, 255, .72); }
|
|
84
|
-
.fact-label { color: #6f7d83; font-size: 10px; }
|
|
85
|
-
.fact-value { margin-top: 5px; color: #27343c; font-size: 14px; font-weight: 700; }
|
|
86
|
-
.note { margin-top: 16px; color: #7a888d; font-size: 10px; line-height: 1.6; }
|
|
87
|
-
.source-header { display: flex; align-items: flex-start; justify-content: space-between; padding-bottom: 17px; border-bottom: 1px solid #e0e5e6; }
|
|
88
|
-
.source-header h2 { margin: 0; color: #27343c; font-size: 19px; }
|
|
89
|
-
.source-header span { color: #6f7d83; font-size: 10px; }
|
|
90
|
-
.event-title { margin: 25px 0 19px; color: #27343c; font-size: 22px; line-height: 1.4; }
|
|
91
|
-
.post-card { padding: 18px; border: 1px solid #d2dadd; border-radius: 13px; background: rgba(255, 255, 255, .86); }
|
|
92
|
-
.post-head { display: flex; align-items: center; min-width: 0; }
|
|
93
|
-
.avatar { display: grid; flex: 0 0 auto; width: 42px; height: 42px; place-items: center; overflow: hidden; border-radius: 50%; background: linear-gradient(145deg, #a57954, #d3aa7d); color: #fff; font-size: 19px; font-weight: 700; }
|
|
94
|
-
.avatar-image { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
95
|
-
.avatar-fallback { display: block; }
|
|
96
|
-
.author { display: flex; flex-direction: column; margin-left: 10px; line-height: 1.3; }
|
|
97
|
-
.author strong { font-size: 14px; }
|
|
98
|
-
.author span { color: #6f7d83; font-size: 10px; }
|
|
99
|
-
.post-head time { margin-left: auto; color: #6f7d83; font-size: 10px; }
|
|
100
|
-
.post-text { display: -webkit-box; margin: 19px 0 15px; overflow: hidden; color: #27343c; font-size: 15px; line-height: 1.7; -webkit-box-orient: vertical; -webkit-line-clamp: 5; }
|
|
101
|
-
.post-foot { color: #6f7d83; font-size: 10px; }
|
|
102
|
-
.post-empty { padding: 45px 20px; border: 1px dashed #d2dadd; border-radius: 13px; color: #7a888d; font-size: 13px; text-align: center; }
|
|
103
|
-
.source-note { margin-top: 17px; color: #7a888d; font-size: 10px; line-height: 1.6; }
|
|
104
|
-
.mark { margin-top: 34px; color: #a27831; font-size: 10px; font-weight: 700; letter-spacing: .12em; }
|
|
105
|
-
</style>
|
|
106
|
-
</head>
|
|
107
|
-
<body>
|
|
108
|
-
<main class="card">
|
|
109
|
-
<section class="overview">
|
|
110
|
-
<div class="eyebrow">CODEX RESET / LATEST</div>
|
|
111
|
-
<h1>最近重置</h1>
|
|
112
|
-
<p class="subtitle">AIHOT 公开快照中的最近一次已确认事件</p>
|
|
113
|
-
<div class="elapsed">${escapeHtml(formatElapsed(summary.timestamp, now))}</div>
|
|
114
|
-
<div class="elapsed-label">距离这次重置已经过去</div>
|
|
115
|
-
<div class="facts">
|
|
116
|
-
<div class="fact"><div class="fact-label">发生时间</div><div class="fact-value">${escapeHtml(formatDateTime(summary.timestamp))}</div></div>
|
|
117
|
-
<div class="fact"><div class="fact-label">事件类型</div><div class="fact-value">${typeLabel(summary)}</div></div>
|
|
118
|
-
</div>
|
|
119
|
-
<p class="note">${escapeHtml(approximate)}。数据来自 AIHOT 当前历史快照,不代表你的账户一定获得重置。</p>
|
|
120
|
-
</section>
|
|
121
|
-
<section class="source">
|
|
122
|
-
<header class="source-header"><h2>事件来源</h2><span>北京时间</span></header>
|
|
123
|
-
<h2 class="event-title">${escapeHtml(event.title)}</h2>
|
|
124
|
-
${renderSourcePost(summary)}
|
|
125
|
-
<p class="source-note">${escapeHtml(event.scope ? `范围:${event.scope}` : '公开来源帖子记录')}</p>
|
|
126
|
-
<div class="mark">RESET MONITOR</div>
|
|
127
|
-
</section>
|
|
128
|
-
</main>
|
|
129
|
-
</body>
|
|
64
|
+
return `<!doctype html>
|
|
65
|
+
<html lang="zh-CN">
|
|
66
|
+
<head>
|
|
67
|
+
<meta charset="utf-8">
|
|
68
|
+
<style>
|
|
69
|
+
* { box-sizing: border-box; }
|
|
70
|
+
html, body { margin: 0; padding: 0; background: #f4f6f5; color: #27343c; font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif; }
|
|
71
|
+
body { padding: 24px; }
|
|
72
|
+
.card { position: relative; display: grid; grid-template-columns: .92fr 1.08fr; width: 1120px; min-height: 520px; overflow: hidden; border: 1px solid #d2dadd; border-radius: 16px; background: #fff; box-shadow: 0 16px 38px rgba(42, 58, 64, .10); }
|
|
73
|
+
.card::before { content: ""; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(circle at 7% 12%, rgba(8, 118, 139, .11), transparent 34%), radial-gradient(circle at 95% 96%, rgba(184, 134, 59, .10), transparent 34%); }
|
|
74
|
+
.overview, .source { position: relative; z-index: 1; padding: 38px 40px; }
|
|
75
|
+
.overview { display: flex; flex-direction: column; border-right: 1px solid #d9dfe0; background: linear-gradient(145deg, rgba(237, 247, 246, .8), rgba(255, 255, 255, .96) 64%); }
|
|
76
|
+
.source { background: linear-gradient(160deg, rgba(250, 246, 238, .68), rgba(255, 255, 255, .98) 55%); }
|
|
77
|
+
.eyebrow { color: #08768b; font-size: 11px; font-weight: 700; letter-spacing: .18em; }
|
|
78
|
+
.source .eyebrow { color: #a27831; }
|
|
79
|
+
h1 { margin: 18px 0 7px; color: #27343c; font-size: 31px; letter-spacing: -.04em; }
|
|
80
|
+
.subtitle { margin: 0; color: #6f7d83; font-size: 13px; line-height: 1.6; }
|
|
81
|
+
.elapsed { margin-top: 40px; color: #08768b; font-family: Georgia, "Times New Roman", serif; font-size: 54px; font-weight: 700; letter-spacing: -.06em; line-height: 1; }
|
|
82
|
+
.elapsed-label { margin-top: 8px; color: #617079; font-size: 12px; }
|
|
83
|
+
.facts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: auto; padding-top: 32px; }
|
|
84
|
+
.fact { padding: 13px 14px; border: 1px solid #c8dedd; border-radius: 11px; background: rgba(255, 255, 255, .72); }
|
|
85
|
+
.fact-label { color: #6f7d83; font-size: 10px; }
|
|
86
|
+
.fact-value { margin-top: 5px; color: #27343c; font-size: 14px; font-weight: 700; }
|
|
87
|
+
.note { margin-top: 16px; color: #7a888d; font-size: 10px; line-height: 1.6; }
|
|
88
|
+
.source-header { display: flex; align-items: flex-start; justify-content: space-between; padding-bottom: 17px; border-bottom: 1px solid #e0e5e6; }
|
|
89
|
+
.source-header h2 { margin: 0; color: #27343c; font-size: 19px; }
|
|
90
|
+
.source-header span { color: #6f7d83; font-size: 10px; }
|
|
91
|
+
.event-title { margin: 25px 0 19px; color: #27343c; font-size: 22px; line-height: 1.4; }
|
|
92
|
+
.post-card { padding: 18px; border: 1px solid #d2dadd; border-radius: 13px; background: rgba(255, 255, 255, .86); }
|
|
93
|
+
.post-head { display: flex; align-items: center; min-width: 0; }
|
|
94
|
+
.avatar { display: grid; flex: 0 0 auto; width: 42px; height: 42px; place-items: center; overflow: hidden; border-radius: 50%; background: linear-gradient(145deg, #a57954, #d3aa7d); color: #fff; font-size: 19px; font-weight: 700; }
|
|
95
|
+
.avatar-image { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
96
|
+
.avatar-fallback { display: block; }
|
|
97
|
+
.author { display: flex; flex-direction: column; margin-left: 10px; line-height: 1.3; }
|
|
98
|
+
.author strong { font-size: 14px; }
|
|
99
|
+
.author span { color: #6f7d83; font-size: 10px; }
|
|
100
|
+
.post-head time { margin-left: auto; color: #6f7d83; font-size: 10px; }
|
|
101
|
+
.post-text { display: -webkit-box; margin: 19px 0 15px; overflow: hidden; color: #27343c; font-size: 15px; line-height: 1.7; -webkit-box-orient: vertical; -webkit-line-clamp: 5; }
|
|
102
|
+
.post-foot { color: #6f7d83; font-size: 10px; }
|
|
103
|
+
.post-empty { padding: 45px 20px; border: 1px dashed #d2dadd; border-radius: 13px; color: #7a888d; font-size: 13px; text-align: center; }
|
|
104
|
+
.source-note { margin-top: 17px; color: #7a888d; font-size: 10px; line-height: 1.6; }
|
|
105
|
+
.mark { margin-top: 34px; color: #a27831; font-size: 10px; font-weight: 700; letter-spacing: .12em; }
|
|
106
|
+
</style>
|
|
107
|
+
</head>
|
|
108
|
+
<body>
|
|
109
|
+
<main class="card">
|
|
110
|
+
<section class="overview">
|
|
111
|
+
<div class="eyebrow">CODEX RESET / LATEST</div>
|
|
112
|
+
<h1>最近重置</h1>
|
|
113
|
+
<p class="subtitle">AIHOT 公开快照中的最近一次已确认事件</p>
|
|
114
|
+
<div class="elapsed">${escapeHtml(formatElapsed(summary.timestamp, now))}</div>
|
|
115
|
+
<div class="elapsed-label">距离这次重置已经过去</div>
|
|
116
|
+
<div class="facts">
|
|
117
|
+
<div class="fact"><div class="fact-label">发生时间</div><div class="fact-value">${escapeHtml(formatDateTime(summary.timestamp))}</div></div>
|
|
118
|
+
<div class="fact"><div class="fact-label">事件类型</div><div class="fact-value">${typeLabel(summary)}</div></div>
|
|
119
|
+
</div>
|
|
120
|
+
<p class="note">${escapeHtml(approximate)}。数据来自 AIHOT 当前历史快照,不代表你的账户一定获得重置。</p>
|
|
121
|
+
</section>
|
|
122
|
+
<section class="source">
|
|
123
|
+
<header class="source-header"><h2>事件来源</h2><span>北京时间</span></header>
|
|
124
|
+
<h2 class="event-title">${escapeHtml(event.title.slice(0, 200))}</h2>
|
|
125
|
+
${renderSourcePost(summary)}
|
|
126
|
+
<p class="source-note">${escapeHtml(event.scope ? `范围:${event.scope}` : '公开来源帖子记录')}</p>
|
|
127
|
+
<div class="mark">RESET MONITOR</div>
|
|
128
|
+
</section>
|
|
129
|
+
</main>
|
|
130
|
+
</body>
|
|
130
131
|
</html>`;
|
|
131
132
|
}
|
|
132
133
|
async function renderLatestResetImage(browser, summary, now = new Date()) {
|