koishi-plugin-codex-reset 0.1.2 → 0.1.4
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 +54 -55
- package/lib/calendar.js +111 -119
- package/lib/event.d.ts +4 -0
- package/lib/event.js +145 -0
- package/lib/history.d.ts +2 -0
- package/lib/history.js +110 -91
- package/lib/index.js +38 -25
- package/lib/probability.js +90 -90
- package/lib/types.d.ts +0 -3
- package/package.json +51 -51
package/lib/history.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterHistoryEvents = filterHistoryEvents;
|
|
3
4
|
exports.renderHistoryHtml = renderHistoryHtml;
|
|
4
5
|
exports.renderHistoryImage = renderHistoryImage;
|
|
5
6
|
const calendar_1 = require("./calendar");
|
|
@@ -31,6 +32,24 @@ function eventTimestamp(event) {
|
|
|
31
32
|
}
|
|
32
33
|
return 0;
|
|
33
34
|
}
|
|
35
|
+
function filterHistoryEvents(events, now = new Date(), range = 1) {
|
|
36
|
+
if (range === 'all')
|
|
37
|
+
return [...events];
|
|
38
|
+
const months = Math.max(1, Math.floor(Math.abs(range)));
|
|
39
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
40
|
+
timeZone: 'Asia/Shanghai',
|
|
41
|
+
year: 'numeric',
|
|
42
|
+
month: 'numeric',
|
|
43
|
+
day: 'numeric',
|
|
44
|
+
}).formatToParts(now);
|
|
45
|
+
const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
|
46
|
+
const cutoff = Date.UTC(Number(values.year), Number(values.month) - 1 - months, Number(values.day), -8);
|
|
47
|
+
const current = now.getTime();
|
|
48
|
+
return events.filter((event) => {
|
|
49
|
+
const timestamp = eventTimestamp(event);
|
|
50
|
+
return timestamp >= cutoff && timestamp <= current;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
34
53
|
function dateLabel(timestamp) {
|
|
35
54
|
const parts = new Intl.DateTimeFormat('zh-CN', {
|
|
36
55
|
timeZone: 'Asia/Shanghai',
|
|
@@ -74,28 +93,28 @@ function renderPostPreview(event) {
|
|
|
74
93
|
const avatar = (0, calendar_1.getTiboAvatarDataUri)()
|
|
75
94
|
? `<img class="avatar-image" src="${(0, calendar_1.getTiboAvatarDataUri)()}" alt="">`
|
|
76
95
|
: '<span class="avatar-fallback">T</span>';
|
|
77
|
-
return `<div class="post-preview">
|
|
78
|
-
<div class="post-head">
|
|
79
|
-
<div class="avatar">${avatar}</div>
|
|
80
|
-
<div class="author"><strong>Tibo</strong><span>@thsottiaux</span></div>
|
|
81
|
-
<time>${escapeHtml(timeLabel(parseTimestamp(post.publishedAt) ?? eventTimestamp(event)))} 发布</time>
|
|
82
|
-
</div>
|
|
83
|
-
<p>${escapeHtml(text)}</p>
|
|
96
|
+
return `<div class="post-preview">
|
|
97
|
+
<div class="post-head">
|
|
98
|
+
<div class="avatar">${avatar}</div>
|
|
99
|
+
<div class="author"><strong>Tibo</strong><span>@thsottiaux</span></div>
|
|
100
|
+
<time>${escapeHtml(timeLabel(parseTimestamp(post.publishedAt) ?? eventTimestamp(event)))} 发布</time>
|
|
101
|
+
</div>
|
|
102
|
+
<p>${escapeHtml(text)}</p>
|
|
84
103
|
</div>`;
|
|
85
104
|
}
|
|
86
105
|
function renderTimelineItem(event) {
|
|
87
106
|
const timestamp = eventTimestamp(event);
|
|
88
107
|
const statusClass = event.status === 'confirmed' ? 'confirmed' : 'announced';
|
|
89
108
|
const schedule = event.schedule?.label ? `<div class="schedule">时间:${escapeHtml(event.schedule.label)}</div>` : '';
|
|
90
|
-
return `<article class="timeline-item">
|
|
91
|
-
<div class="timeline-date"><strong>${escapeHtml(dateLabel(timestamp))}</strong><span>${escapeHtml(timeLabel(timestamp))}</span></div>
|
|
92
|
-
<div class="timeline-dot ${statusClass}"></div>
|
|
93
|
-
<div class="event-card">
|
|
94
|
-
<div class="event-meta"><span class="type-label">${typeLabel(event)}</span><span class="status ${statusClass}">${statusLabel(event)}</span></div>
|
|
95
|
-
<h2>${escapeHtml(event.title)}</h2>
|
|
96
|
-
${schedule}
|
|
97
|
-
${renderPostPreview(event)}
|
|
98
|
-
</div>
|
|
109
|
+
return `<article class="timeline-item">
|
|
110
|
+
<div class="timeline-date"><strong>${escapeHtml(dateLabel(timestamp))}</strong><span>${escapeHtml(timeLabel(timestamp))}</span></div>
|
|
111
|
+
<div class="timeline-dot ${statusClass}"></div>
|
|
112
|
+
<div class="event-card">
|
|
113
|
+
<div class="event-meta"><span class="type-label">${typeLabel(event)}</span><span class="status ${statusClass}">${statusLabel(event)}</span></div>
|
|
114
|
+
<h2>${escapeHtml(event.title)}</h2>
|
|
115
|
+
${schedule}
|
|
116
|
+
${renderPostPreview(event)}
|
|
117
|
+
</div>
|
|
99
118
|
</article>`;
|
|
100
119
|
}
|
|
101
120
|
function renderHistoryHtml(events, now = new Date()) {
|
|
@@ -107,81 +126,81 @@ function renderHistoryHtml(events, now = new Date()) {
|
|
|
107
126
|
const firstTimestamp = sorted.at(-1) ? eventTimestamp(sorted.at(-1)) : now.getTime();
|
|
108
127
|
const lastTimestamp = sorted[0] ? eventTimestamp(sorted[0]) : now.getTime();
|
|
109
128
|
const period = `${dateLabel(firstTimestamp)} 至 ${dateLabel(lastTimestamp)}`;
|
|
110
|
-
return `<!doctype html>
|
|
111
|
-
<html lang="zh-CN">
|
|
112
|
-
<head>
|
|
113
|
-
<meta charset="utf-8">
|
|
114
|
-
<style>
|
|
115
|
-
* { box-sizing: border-box; }
|
|
116
|
-
html, body { margin: 0; padding: 0; background: #f4f6f5; color: #27343c; font-family: "Microsoft YaHei", "Noto Sans CJK SC", Arial, sans-serif; }
|
|
117
|
-
body { padding: 24px; }
|
|
118
|
-
.history-shell { max-width: 1280px; margin: 0 auto; padding: 28px 30px 34px; border: 1px solid #d2dadd; border-radius: 16px; background: #fff; }
|
|
119
|
-
.page-header { display: flex; align-items: flex-start; justify-content: space-between; padding-bottom: 20px; border-bottom: 1px solid #e0e5e6; }
|
|
120
|
-
.eyebrow { color: #08768b; font-size: 12px; letter-spacing: .08em; }
|
|
121
|
-
h1 { margin: 7px 0 8px; font-size: 28px; line-height: 1.2; }
|
|
122
|
-
.subtitle { margin: 0; color: #6f7d83; font-size: 13px; }
|
|
123
|
-
.generated { color: #6f7d83; font-size: 11px; text-align: right; line-height: 1.6; }
|
|
124
|
-
.layout { display: grid; grid-template-columns: minmax(0, 1fr) 286px; gap: 28px; padding-top: 26px; }
|
|
125
|
-
.timeline { position: relative; }
|
|
126
|
-
.timeline::before { content: ""; position: absolute; left: 112px; top: 16px; bottom: 16px; width: 1px; background: #cbd7d9; }
|
|
127
|
-
.timeline-item { position: relative; display: grid; grid-template-columns: 94px 36px minmax(0, 1fr); gap: 0; margin-bottom: 18px; }
|
|
128
|
-
.timeline-date { padding-top: 17px; padding-right: 14px; text-align: right; line-height: 1.5; }
|
|
129
|
-
.timeline-date strong { display: block; color: #27343c; font-size: 11px; white-space: nowrap; }
|
|
130
|
-
.timeline-date span { color: #89969a; font-size: 10px; }
|
|
131
|
-
.timeline-dot { position: relative; z-index: 1; width: 12px; height: 12px; margin: 22px auto 0; border: 3px solid #fff; border-radius: 50%; background: #08768b; box-shadow: 0 0 0 1px #08768b; }
|
|
132
|
-
.timeline-dot.announced { background: #b8863b; box-shadow: 0 0 0 1px #b8863b; }
|
|
133
|
-
.event-card { padding: 15px 17px 16px; border: 1px solid #d2dadd; border-radius: 13px; background: #fff; }
|
|
134
|
-
.event-meta { display: flex; gap: 7px; align-items: center; margin-bottom: 7px; }
|
|
135
|
-
.type-label, .status { display: inline-block; padding: 3px 7px; border-radius: 999px; font-size: 10px; }
|
|
136
|
-
.type-label { color: #08768b; background: #edf7f8; }
|
|
137
|
-
.status.confirmed { color: #177255; background: #edf8f1; }
|
|
138
|
-
.status.announced { color: #8b6426; background: #fbf4e6; }
|
|
139
|
-
.event-card h2 { margin: 0 0 10px; font-size: 17px; line-height: 1.35; }
|
|
140
|
-
.schedule { margin-bottom: 10px; color: #08768b; font-size: 11px; }
|
|
141
|
-
.post-preview { padding: 11px 12px; border: 1px solid #e0e5e6; border-radius: 10px; background: #fbfcfc; }
|
|
142
|
-
.post-preview.empty { color: #89969a; font-size: 12px; }
|
|
143
|
-
.post-head { display: flex; align-items: center; min-width: 0; }
|
|
144
|
-
.avatar { display: grid; flex: 0 0 auto; width: 28px; height: 28px; place-items: center; overflow: hidden; border-radius: 50%; background: linear-gradient(145deg, #a57954, #d3aa7d); color: #fff; font-size: 14px; font-weight: 700; }
|
|
145
|
-
.avatar-image { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
146
|
-
.avatar-fallback { display: block; }
|
|
147
|
-
.author { display: flex; flex-direction: column; margin-left: 8px; line-height: 1.2; }
|
|
148
|
-
.author strong { font-size: 11px; }
|
|
149
|
-
.author span { color: #89969a; font-size: 9px; }
|
|
150
|
-
.post-head time { margin-left: auto; color: #89969a; font-size: 10px; }
|
|
151
|
-
.post-preview p { margin: 10px 0 0; color: #27343c; font-size: 12px; line-height: 1.55; }
|
|
152
|
-
.summary { align-self: start; padding: 18px; border: 1px solid #d2dadd; border-radius: 13px; background: #fbfcfc; }
|
|
153
|
-
.summary h2 { margin: 0 0 15px; font-size: 17px; }
|
|
154
|
-
.summary-period { margin-bottom: 18px; color: #6f7d83; font-size: 11px; line-height: 1.5; }
|
|
155
|
-
.stat { display: flex; align-items: baseline; justify-content: space-between; padding: 11px 0; border-top: 1px solid #e0e5e6; }
|
|
156
|
-
.stat span { color: #6f7d83; font-size: 12px; }
|
|
157
|
-
.stat strong { color: #27343c; font-size: 22px; }
|
|
158
|
-
.summary-note { margin: 17px 0 0; color: #89969a; font-size: 10px; line-height: 1.6; }
|
|
159
|
-
.empty { padding: 54px 0; color: #89969a; text-align: center; }
|
|
160
|
-
</style>
|
|
161
|
-
</head>
|
|
162
|
-
<body>
|
|
163
|
-
<main class="history-shell">
|
|
164
|
-
<header class="page-header">
|
|
165
|
-
<div><div class="eyebrow">CODEX RESET MONITOR</div><h1>Codex 重置历史</h1><p class="subtitle">按北京时间排列的公开重置事件时间线</p></div>
|
|
166
|
-
<div class="generated">生成时间<br>${escapeHtml(formatGeneratedAt(now))}</div>
|
|
167
|
-
</header>
|
|
168
|
-
<div class="layout">
|
|
169
|
-
<section class="timeline">
|
|
170
|
-
${sorted.length ? sorted.map(renderTimelineItem).join('') : '<div class="empty">暂无历史事件</div>'}
|
|
171
|
-
</section>
|
|
172
|
-
<aside class="summary">
|
|
173
|
-
<h2>历史概览</h2>
|
|
174
|
-
<div class="summary-period">统计区间<br>${escapeHtml(period)}</div>
|
|
175
|
-
<div class="stat"><span>事件总数</span><strong>${events.length}</strong></div>
|
|
176
|
-
<div class="stat"><span>已确认</span><strong>${confirmed.length}</strong></div>
|
|
177
|
-
<div class="stat"><span>全员重置</span><strong>${directResetCount}</strong></div>
|
|
178
|
-
<div class="stat"><span>重置卡</span><strong>${resetCreditCount}</strong></div>
|
|
179
|
-
<div class="stat"><span>未重置预告</span><strong>${announcedCount}</strong></div>
|
|
180
|
-
<p class="summary-note">数据来自 AIHOT 公开快照。时间线仅展示公开事件,不代表账户一定获得重置。</p>
|
|
181
|
-
</aside>
|
|
182
|
-
</div>
|
|
183
|
-
</main>
|
|
184
|
-
</body>
|
|
129
|
+
return `<!doctype html>
|
|
130
|
+
<html lang="zh-CN">
|
|
131
|
+
<head>
|
|
132
|
+
<meta charset="utf-8">
|
|
133
|
+
<style>
|
|
134
|
+
* { box-sizing: border-box; }
|
|
135
|
+
html, body { margin: 0; padding: 0; background: #f4f6f5; color: #27343c; font-family: "Microsoft YaHei", "Noto Sans CJK SC", Arial, sans-serif; }
|
|
136
|
+
body { padding: 24px; }
|
|
137
|
+
.history-shell { max-width: 1280px; margin: 0 auto; padding: 28px 30px 34px; border: 1px solid #d2dadd; border-radius: 16px; background: #fff; }
|
|
138
|
+
.page-header { display: flex; align-items: flex-start; justify-content: space-between; padding-bottom: 20px; border-bottom: 1px solid #e0e5e6; }
|
|
139
|
+
.eyebrow { color: #08768b; font-size: 12px; letter-spacing: .08em; }
|
|
140
|
+
h1 { margin: 7px 0 8px; font-size: 28px; line-height: 1.2; }
|
|
141
|
+
.subtitle { margin: 0; color: #6f7d83; font-size: 13px; }
|
|
142
|
+
.generated { color: #6f7d83; font-size: 11px; text-align: right; line-height: 1.6; }
|
|
143
|
+
.layout { display: grid; grid-template-columns: minmax(0, 1fr) 286px; gap: 28px; padding-top: 26px; }
|
|
144
|
+
.timeline { position: relative; }
|
|
145
|
+
.timeline::before { content: ""; position: absolute; left: 112px; top: 16px; bottom: 16px; width: 1px; background: #cbd7d9; }
|
|
146
|
+
.timeline-item { position: relative; display: grid; grid-template-columns: 94px 36px minmax(0, 1fr); gap: 0; margin-bottom: 18px; }
|
|
147
|
+
.timeline-date { padding-top: 17px; padding-right: 14px; text-align: right; line-height: 1.5; }
|
|
148
|
+
.timeline-date strong { display: block; color: #27343c; font-size: 11px; white-space: nowrap; }
|
|
149
|
+
.timeline-date span { color: #89969a; font-size: 10px; }
|
|
150
|
+
.timeline-dot { position: relative; z-index: 1; width: 12px; height: 12px; margin: 22px auto 0; border: 3px solid #fff; border-radius: 50%; background: #08768b; box-shadow: 0 0 0 1px #08768b; }
|
|
151
|
+
.timeline-dot.announced { background: #b8863b; box-shadow: 0 0 0 1px #b8863b; }
|
|
152
|
+
.event-card { padding: 15px 17px 16px; border: 1px solid #d2dadd; border-radius: 13px; background: #fff; }
|
|
153
|
+
.event-meta { display: flex; gap: 7px; align-items: center; margin-bottom: 7px; }
|
|
154
|
+
.type-label, .status { display: inline-block; padding: 3px 7px; border-radius: 999px; font-size: 10px; }
|
|
155
|
+
.type-label { color: #08768b; background: #edf7f8; }
|
|
156
|
+
.status.confirmed { color: #177255; background: #edf8f1; }
|
|
157
|
+
.status.announced { color: #8b6426; background: #fbf4e6; }
|
|
158
|
+
.event-card h2 { margin: 0 0 10px; font-size: 17px; line-height: 1.35; }
|
|
159
|
+
.schedule { margin-bottom: 10px; color: #08768b; font-size: 11px; }
|
|
160
|
+
.post-preview { padding: 11px 12px; border: 1px solid #e0e5e6; border-radius: 10px; background: #fbfcfc; }
|
|
161
|
+
.post-preview.empty { color: #89969a; font-size: 12px; }
|
|
162
|
+
.post-head { display: flex; align-items: center; min-width: 0; }
|
|
163
|
+
.avatar { display: grid; flex: 0 0 auto; width: 28px; height: 28px; place-items: center; overflow: hidden; border-radius: 50%; background: linear-gradient(145deg, #a57954, #d3aa7d); color: #fff; font-size: 14px; font-weight: 700; }
|
|
164
|
+
.avatar-image { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
165
|
+
.avatar-fallback { display: block; }
|
|
166
|
+
.author { display: flex; flex-direction: column; margin-left: 8px; line-height: 1.2; }
|
|
167
|
+
.author strong { font-size: 11px; }
|
|
168
|
+
.author span { color: #89969a; font-size: 9px; }
|
|
169
|
+
.post-head time { margin-left: auto; color: #89969a; font-size: 10px; }
|
|
170
|
+
.post-preview p { margin: 10px 0 0; color: #27343c; font-size: 12px; line-height: 1.55; }
|
|
171
|
+
.summary { align-self: start; padding: 18px; border: 1px solid #d2dadd; border-radius: 13px; background: #fbfcfc; }
|
|
172
|
+
.summary h2 { margin: 0 0 15px; font-size: 17px; }
|
|
173
|
+
.summary-period { margin-bottom: 18px; color: #6f7d83; font-size: 11px; line-height: 1.5; }
|
|
174
|
+
.stat { display: flex; align-items: baseline; justify-content: space-between; padding: 11px 0; border-top: 1px solid #e0e5e6; }
|
|
175
|
+
.stat span { color: #6f7d83; font-size: 12px; }
|
|
176
|
+
.stat strong { color: #27343c; font-size: 22px; }
|
|
177
|
+
.summary-note { margin: 17px 0 0; color: #89969a; font-size: 10px; line-height: 1.6; }
|
|
178
|
+
.empty { padding: 54px 0; color: #89969a; text-align: center; }
|
|
179
|
+
</style>
|
|
180
|
+
</head>
|
|
181
|
+
<body>
|
|
182
|
+
<main class="history-shell">
|
|
183
|
+
<header class="page-header">
|
|
184
|
+
<div><div class="eyebrow">CODEX RESET MONITOR</div><h1>Codex 重置历史</h1><p class="subtitle">按北京时间排列的公开重置事件时间线</p></div>
|
|
185
|
+
<div class="generated">生成时间<br>${escapeHtml(formatGeneratedAt(now))}</div>
|
|
186
|
+
</header>
|
|
187
|
+
<div class="layout">
|
|
188
|
+
<section class="timeline">
|
|
189
|
+
${sorted.length ? sorted.map(renderTimelineItem).join('') : '<div class="empty">暂无历史事件</div>'}
|
|
190
|
+
</section>
|
|
191
|
+
<aside class="summary">
|
|
192
|
+
<h2>历史概览</h2>
|
|
193
|
+
<div class="summary-period">统计区间<br>${escapeHtml(period)}</div>
|
|
194
|
+
<div class="stat"><span>事件总数</span><strong>${events.length}</strong></div>
|
|
195
|
+
<div class="stat"><span>已确认</span><strong>${confirmed.length}</strong></div>
|
|
196
|
+
<div class="stat"><span>全员重置</span><strong>${directResetCount}</strong></div>
|
|
197
|
+
<div class="stat"><span>重置卡</span><strong>${resetCreditCount}</strong></div>
|
|
198
|
+
<div class="stat"><span>未重置预告</span><strong>${announcedCount}</strong></div>
|
|
199
|
+
<p class="summary-note">数据来自 AIHOT 公开快照。时间线仅展示公开事件,不代表账户一定获得重置。</p>
|
|
200
|
+
</aside>
|
|
201
|
+
</div>
|
|
202
|
+
</main>
|
|
203
|
+
</body>
|
|
185
204
|
</html>`;
|
|
186
205
|
}
|
|
187
206
|
async function renderHistoryImage(browser, events, now = new Date()) {
|
package/lib/index.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.apply = apply;
|
|
|
5
5
|
const koishi_1 = require("koishi");
|
|
6
6
|
const calendar_1 = require("./calendar");
|
|
7
7
|
const history_1 = require("./history");
|
|
8
|
+
const event_1 = require("./event");
|
|
8
9
|
const probability_1 = require("./probability");
|
|
9
10
|
const stats_1 = require("./stats");
|
|
10
11
|
exports.name = 'codex-reset';
|
|
@@ -40,15 +41,6 @@ exports.Config = koishi_1.Schema.object({
|
|
|
40
41
|
mentionAll: koishi_1.Schema.boolean()
|
|
41
42
|
.default(false)
|
|
42
43
|
.description('推送通知时是否艾特全体。需要平台和频道支持全体艾特。'),
|
|
43
|
-
enableProbabilityCommand: koishi_1.Schema.boolean()
|
|
44
|
-
.default(true)
|
|
45
|
-
.description('启用 codex-reset.probability(别名:重置概率)。'),
|
|
46
|
-
enableCalendarCommand: koishi_1.Schema.boolean()
|
|
47
|
-
.default(true)
|
|
48
|
-
.description('启用日历图片命令,需要 Puppeteer 服务生成 PNG。'),
|
|
49
|
-
enableHistoryCommand: koishi_1.Schema.boolean()
|
|
50
|
-
.default(true)
|
|
51
|
-
.description('启用历史时间线图片命令,需要 Puppeteer 服务生成 PNG。'),
|
|
52
44
|
}).i18n({
|
|
53
45
|
'zh-CN': {
|
|
54
46
|
apiUrl: '监控接口地址',
|
|
@@ -62,9 +54,6 @@ exports.Config = koishi_1.Schema.object({
|
|
|
62
54
|
pushAnnouncements: '推送重置预告',
|
|
63
55
|
pushConfirmedResets: '推送已确认重置',
|
|
64
56
|
mentionAll: '艾特全体',
|
|
65
|
-
enableProbabilityCommand: '启用重置概率命令',
|
|
66
|
-
enableCalendarCommand: '启用重置日历命令',
|
|
67
|
-
enableHistoryCommand: '启用重置历史命令',
|
|
68
57
|
},
|
|
69
58
|
});
|
|
70
59
|
function isRecord(value) {
|
|
@@ -229,12 +218,12 @@ class CodexResetMonitor {
|
|
|
229
218
|
return null;
|
|
230
219
|
return (0, calendar_1.renderCalendarImage)(browser, snapshot.events);
|
|
231
220
|
}
|
|
232
|
-
async createHistoryImage() {
|
|
221
|
+
async createHistoryImage(events, now = new Date()) {
|
|
233
222
|
const snapshot = await this.ensureSnapshot();
|
|
234
223
|
const browser = this.ctx.puppeteer;
|
|
235
224
|
if (!snapshot || !browser)
|
|
236
225
|
return null;
|
|
237
|
-
return (0, history_1.renderHistoryImage)(browser, snapshot.events);
|
|
226
|
+
return (0, history_1.renderHistoryImage)(browser, events ?? snapshot.events, now);
|
|
238
227
|
}
|
|
239
228
|
async createProbabilityImage(summary) {
|
|
240
229
|
const browser = this.ctx.puppeteer;
|
|
@@ -242,13 +231,19 @@ class CodexResetMonitor {
|
|
|
242
231
|
return null;
|
|
243
232
|
return (0, probability_1.renderProbabilityImage)(browser, summary);
|
|
244
233
|
}
|
|
234
|
+
async createLatestResetImage(summary, now = new Date()) {
|
|
235
|
+
const browser = this.ctx.puppeteer;
|
|
236
|
+
if (!browser)
|
|
237
|
+
return null;
|
|
238
|
+
return (0, event_1.renderLatestResetImage)(browser, summary, now);
|
|
239
|
+
}
|
|
245
240
|
async fetchSnapshot(ignoreEtag = false) {
|
|
246
241
|
const controller = new AbortController();
|
|
247
242
|
const timeout = setTimeout(() => controller.abort(), this.config.requestTimeout);
|
|
248
243
|
try {
|
|
249
244
|
const headers = {
|
|
250
245
|
Accept: 'application/json',
|
|
251
|
-
'User-Agent': 'koishi-plugin-codex-reset/0.1.
|
|
246
|
+
'User-Agent': 'koishi-plugin-codex-reset/0.1.3',
|
|
252
247
|
};
|
|
253
248
|
if (!ignoreEtag && this.etag && this.snapshot)
|
|
254
249
|
headers['If-None-Match'] = this.etag;
|
|
@@ -313,8 +308,6 @@ function apply(ctx, config) {
|
|
|
313
308
|
ctx.command('codex.probability', '分析 Codex 重置事件的历史概率')
|
|
314
309
|
.alias('重置概率')
|
|
315
310
|
.action(async () => {
|
|
316
|
-
if (!config.enableProbabilityCommand)
|
|
317
|
-
return '重置概率命令已关闭。';
|
|
318
311
|
const snapshot = await monitor.ensureSnapshot();
|
|
319
312
|
if (!snapshot)
|
|
320
313
|
return '暂时无法获取 Codex 重置事件数据,请稍后再试。';
|
|
@@ -338,13 +331,26 @@ function apply(ctx, config) {
|
|
|
338
331
|
const snapshot = await monitor.ensureSnapshot();
|
|
339
332
|
if (!snapshot)
|
|
340
333
|
return '暂时无法获取 Codex 重置事件数据,请稍后再试。';
|
|
341
|
-
|
|
334
|
+
const now = new Date();
|
|
335
|
+
const latest = (0, stats_1.findLatestConfirmedReset)(snapshot, now);
|
|
336
|
+
if (!latest)
|
|
337
|
+
return (0, stats_1.formatLatestReset)(snapshot, now);
|
|
338
|
+
if (!monitor.hasPuppeteer)
|
|
339
|
+
return (0, stats_1.formatLatestReset)(snapshot, now);
|
|
340
|
+
try {
|
|
341
|
+
const image = await monitor.createLatestResetImage(latest, now);
|
|
342
|
+
if (!image)
|
|
343
|
+
return (0, stats_1.formatLatestReset)(snapshot, now);
|
|
344
|
+
return koishi_1.h.image(image, 'image/png');
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
ctx.logger('codex-reset').warn(`生成最近重置卡片失败:${error instanceof Error ? error.message : String(error)}`);
|
|
348
|
+
return (0, stats_1.formatLatestReset)(snapshot, now);
|
|
349
|
+
}
|
|
342
350
|
});
|
|
343
351
|
ctx.command('codex.calendar', '发送本月的 Codex 重置日历图片')
|
|
344
352
|
.alias('重置日历')
|
|
345
353
|
.action(async () => {
|
|
346
|
-
if (!config.enableCalendarCommand)
|
|
347
|
-
return '重置日历命令已关闭。';
|
|
348
354
|
if (!monitor.hasPuppeteer)
|
|
349
355
|
return '发送日历图片需要先安装并启用 koishi-plugin-puppeteer。';
|
|
350
356
|
try {
|
|
@@ -358,15 +364,22 @@ function apply(ctx, config) {
|
|
|
358
364
|
return '日历图片生成失败,请检查 Puppeteer 服务和中文字体。';
|
|
359
365
|
}
|
|
360
366
|
});
|
|
361
|
-
ctx.command('codex.history', '发送 Codex 重置历史时间线图片')
|
|
367
|
+
ctx.command('codex.history [range:number]', '发送 Codex 重置历史时间线图片')
|
|
362
368
|
.alias('重置历史')
|
|
363
|
-
.
|
|
364
|
-
|
|
365
|
-
return '重置历史命令已关闭。';
|
|
369
|
+
.option('all', '-a 查看全部历史')
|
|
370
|
+
.action(async (argv, range) => {
|
|
366
371
|
if (!monitor.hasPuppeteer)
|
|
367
372
|
return '发送历史时间线图片需要先安装并启用 koishi-plugin-puppeteer。';
|
|
368
373
|
try {
|
|
369
|
-
const
|
|
374
|
+
const snapshot = await monitor.ensureSnapshot();
|
|
375
|
+
if (!snapshot)
|
|
376
|
+
return '暂时无法获取 Codex 重置事件数据,请稍后再试。';
|
|
377
|
+
const now = new Date();
|
|
378
|
+
const historyRange = argv.options?.all
|
|
379
|
+
? 'all'
|
|
380
|
+
: Math.max(1, Math.min(120, Math.floor(Math.abs(range ?? 1))));
|
|
381
|
+
const events = (0, history_1.filterHistoryEvents)(snapshot.events, now, historyRange);
|
|
382
|
+
const image = await monitor.createHistoryImage(events, now);
|
|
370
383
|
if (!image)
|
|
371
384
|
return '暂时无法生成历史时间线图片,请稍后再试。';
|
|
372
385
|
return koishi_1.h.image(image, 'image/png');
|
package/lib/probability.js
CHANGED
|
@@ -34,10 +34,10 @@ function formatGeneratedAt(now) {
|
|
|
34
34
|
}).format(now);
|
|
35
35
|
}
|
|
36
36
|
function metricCard(label, value, note, tone) {
|
|
37
|
-
return `<div class="metric ${tone}">
|
|
38
|
-
<div class="metric-label">${escapeHtml(label)}</div>
|
|
39
|
-
<div class="metric-value">${escapeHtml(value)}</div>
|
|
40
|
-
<div class="metric-note">${escapeHtml(note)}</div>
|
|
37
|
+
return `<div class="metric ${tone}">
|
|
38
|
+
<div class="metric-label">${escapeHtml(label)}</div>
|
|
39
|
+
<div class="metric-value">${escapeHtml(value)}</div>
|
|
40
|
+
<div class="metric-note">${escapeHtml(note)}</div>
|
|
41
41
|
</div>`;
|
|
42
42
|
}
|
|
43
43
|
function renderProbabilityHtml(summary, now = new Date()) {
|
|
@@ -45,92 +45,92 @@ function renderProbabilityHtml(summary, now = new Date()) {
|
|
|
45
45
|
const directRatio = ratio(summary.directResetCount, confirmedTotal);
|
|
46
46
|
const creditRatio = ratio(summary.resetCreditCount, confirmedTotal);
|
|
47
47
|
const recentDate = summary.lastConfirmedDate ?? '暂无记录';
|
|
48
|
-
return `<!doctype html>
|
|
49
|
-
<html lang="zh-CN">
|
|
50
|
-
<head>
|
|
51
|
-
<meta charset="utf-8">
|
|
52
|
-
<style>
|
|
53
|
-
* { box-sizing: border-box; }
|
|
54
|
-
html, body { margin: 0; padding: 0; background: #f4f6f5; color: #27343c; font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif; }
|
|
55
|
-
body { padding: 26px; }
|
|
56
|
-
.card { position: relative; width: 1120px; min-height: 680px; overflow: hidden; padding: 38px 44px 30px; border: 1px solid #d2dadd; border-radius: 16px; background: radial-gradient(circle at 82% 12%, rgba(8, 118, 139, .07), transparent 30%), radial-gradient(circle at 10% 96%, rgba(184, 134, 59, .07), transparent 28%), #fff; box-shadow: 0 16px 38px rgba(42, 58, 64, .10); }
|
|
57
|
-
.card::before { content: ""; position: absolute; inset: 0; pointer-events: none; opacity: .32; background-image: linear-gradient(rgba(8, 118, 139, .05) 1px, transparent 1px), linear-gradient(90deg, rgba(8, 118, 139, .05) 1px, transparent 1px); background-size: 34px 34px; mask-image: linear-gradient(to bottom, rgba(0,0,0,.42), transparent 72%); }
|
|
58
|
-
.content { position: relative; z-index: 1; }
|
|
59
|
-
.topline { display: flex; align-items: flex-start; justify-content: space-between; }
|
|
60
|
-
.eyebrow { color: #08768b; font-size: 11px; font-weight: 700; letter-spacing: .22em; }
|
|
61
|
-
.source { color: #6f7d83; font-size: 11px; line-height: 1.6; text-align: right; }
|
|
62
|
-
h1 { margin: 22px 0 8px; color: #27343c; font-size: 34px; font-weight: 700; letter-spacing: -.04em; }
|
|
63
|
-
.subtitle { margin: 0; color: #6f7d83; font-size: 14px; }
|
|
64
|
-
.rule { width: 100%; height: 1px; margin: 26px 0 22px; background: linear-gradient(90deg, rgba(8, 118, 139, .36), rgba(8, 118, 139, .08), transparent); }
|
|
65
|
-
.metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
66
|
-
.metric { position: relative; min-height: 186px; padding: 22px 24px; overflow: hidden; border: 1px solid #c8dedd; border-radius: 13px; background: #eef7f6; }
|
|
67
|
-
.metric::after { content: ""; position: absolute; right: -28px; bottom: -58px; width: 180px; height: 180px; border: 1px solid rgba(8, 118, 139, .18); border-radius: 50%; box-shadow: 0 0 0 18px rgba(8, 118, 139, .035), 0 0 0 36px rgba(8, 118, 139, .018); }
|
|
68
|
-
.metric.seven { background: #faf6ee; border-color: #e2d8c5; }
|
|
69
|
-
.metric.seven::after { border-color: rgba(184, 134, 59, .22); box-shadow: 0 0 0 18px rgba(184, 134, 59, .035), 0 0 0 36px rgba(184, 134, 59, .018); }
|
|
70
|
-
.metric-label { color: #6f7d83; font-size: 13px; }
|
|
71
|
-
.metric-value { margin-top: 7px; color: #08768b; font-family: Georgia, "Times New Roman", serif; font-size: 66px; font-weight: 700; letter-spacing: -.07em; line-height: 1; }
|
|
72
|
-
.metric.seven .metric-value { color: #a27831; }
|
|
73
|
-
.metric-note { margin-top: 14px; color: #52646a; font-size: 12px; }
|
|
74
|
-
.lower { display: grid; grid-template-columns: 1.15fr .85fr; gap: 16px; margin-top: 16px; }
|
|
75
|
-
.panel { padding: 18px 20px; border: 1px solid #d2dadd; border-radius: 13px; background: #fbfcfc; }
|
|
76
|
-
.panel h2 { margin: 0 0 15px; color: #27343c; font-size: 14px; }
|
|
77
|
-
.sample-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
|
78
|
-
.sample { padding-right: 10px; border-right: 1px solid #e0e5e6; }
|
|
79
|
-
.sample:last-child { border-right: 0; }
|
|
80
|
-
.sample-value { color: #27343c; font-size: 22px; font-weight: 700; }
|
|
81
|
-
.sample-label { margin-top: 4px; color: #6f7d83; font-size: 11px; }
|
|
82
|
-
.bar-row { display: grid; grid-template-columns: 76px 1fr 42px; align-items: center; gap: 9px; margin: 12px 0; color: #52646a; font-size: 11px; }
|
|
83
|
-
.bar { height: 7px; overflow: hidden; border-radius: 99px; background: #e4ecec; }
|
|
84
|
-
.bar span { display: block; height: 100%; border-radius: inherit; background: #71c8bd; }
|
|
85
|
-
.bar-row.credit .bar span { background: #d8ad5f; }
|
|
86
|
-
.bar-percent { color: #27343c; text-align: right; }
|
|
87
|
-
.recent { display: flex; align-items: center; justify-content: space-between; padding-top: 15px; margin-top: 16px; border-top: 1px solid #e0e5e6; }
|
|
88
|
-
.recent-label { color: #6f7d83; font-size: 11px; }
|
|
89
|
-
.recent-date { color: #a27831; font-size: 15px; font-weight: 700; }
|
|
90
|
-
.footer { display: flex; align-items: flex-end; justify-content: space-between; gap: 30px; margin-top: 22px; color: #7a888d; font-size: 10px; line-height: 1.65; }
|
|
91
|
-
.footer strong { color: #52646a; font-weight: 600; }
|
|
92
|
-
.mark { color: #08768b; font-size: 11px; font-weight: 700; letter-spacing: .08em; white-space: nowrap; }
|
|
93
|
-
</style>
|
|
94
|
-
</head>
|
|
95
|
-
<body>
|
|
96
|
-
<main class="card">
|
|
97
|
-
<div class="content">
|
|
98
|
-
<header class="topline">
|
|
99
|
-
<div class="eyebrow">CODEX RESET / PUBLIC HISTORY</div>
|
|
100
|
-
<div class="source">AIHOT · ASIA/SHANGHAI<br>生成于 ${escapeHtml(formatGeneratedAt(now))}</div>
|
|
101
|
-
</header>
|
|
102
|
-
<h1>Codex 重置概率分析</h1>
|
|
103
|
-
<p class="subtitle">基于当前公开历史快照的经验统计,不是官方预测</p>
|
|
104
|
-
<div class="rule"></div>
|
|
105
|
-
|
|
106
|
-
<section class="metrics">
|
|
107
|
-
${metricCard('未来 24 小时', percentage(summary.next24hProbability), '至少出现一个公开重置事件', 'day')}
|
|
108
|
-
${metricCard('未来 7 天', percentage(summary.next7dProbability), '至少出现一个公开重置事件', 'seven')}
|
|
109
|
-
</section>
|
|
110
|
-
|
|
111
|
-
<section class="lower">
|
|
112
|
-
<div class="panel">
|
|
113
|
-
<h2>样本概览</h2>
|
|
114
|
-
<div class="sample-grid">
|
|
115
|
-
<div class="sample"><div class="sample-value">${summary.observedDays}</div><div class="sample-label">天观察样本</div></div>
|
|
116
|
-
<div class="sample"><div class="sample-value">${summary.confirmedCount}</div><div class="sample-label">条已确认</div></div>
|
|
117
|
-
<div class="sample"><div class="sample-value">${summary.activeDays}</div><div class="sample-label">个事件日</div></div>
|
|
118
|
-
<div class="sample"><div class="sample-value">${summary.announcedCount}</div><div class="sample-label">条未重置预告</div></div>
|
|
119
|
-
</div>
|
|
120
|
-
<div class="recent"><span class="recent-label">最近确认日期</span><span class="recent-date">${escapeHtml(recentDate)}</span></div>
|
|
121
|
-
</div>
|
|
122
|
-
<div class="panel">
|
|
123
|
-
<h2>已确认事件构成</h2>
|
|
124
|
-
<div class="bar-row"><span>全员重置</span><div class="bar"><span style="width:${directRatio}"></span></div><span class="bar-percent">${summary.directResetCount}</span></div>
|
|
125
|
-
<div class="bar-row credit"><span>重置卡</span><div class="bar"><span style="width:${creditRatio}"></span></div><span class="bar-percent">${summary.resetCreditCount}</span></div>
|
|
126
|
-
<div class="recent"><span class="recent-label">历史事件日率</span><span class="recent-date">${(summary.dailyEventDayRate * 100).toFixed(2)}% / 天</span></div>
|
|
127
|
-
</div>
|
|
128
|
-
</section>
|
|
129
|
-
|
|
130
|
-
<footer class="footer"><span><strong>说明:</strong>概率由历史事件日率进行经验估计,不代表你的账户一定会获得重置。</span><span class="mark">RESET MONITOR</span></footer>
|
|
131
|
-
</div>
|
|
132
|
-
</main>
|
|
133
|
-
</body>
|
|
48
|
+
return `<!doctype html>
|
|
49
|
+
<html lang="zh-CN">
|
|
50
|
+
<head>
|
|
51
|
+
<meta charset="utf-8">
|
|
52
|
+
<style>
|
|
53
|
+
* { box-sizing: border-box; }
|
|
54
|
+
html, body { margin: 0; padding: 0; background: #f4f6f5; color: #27343c; font-family: "Avenir Next", "PingFang SC", "Microsoft YaHei", sans-serif; }
|
|
55
|
+
body { padding: 26px; }
|
|
56
|
+
.card { position: relative; width: 1120px; min-height: 680px; overflow: hidden; padding: 38px 44px 30px; border: 1px solid #d2dadd; border-radius: 16px; background: radial-gradient(circle at 82% 12%, rgba(8, 118, 139, .07), transparent 30%), radial-gradient(circle at 10% 96%, rgba(184, 134, 59, .07), transparent 28%), #fff; box-shadow: 0 16px 38px rgba(42, 58, 64, .10); }
|
|
57
|
+
.card::before { content: ""; position: absolute; inset: 0; pointer-events: none; opacity: .32; background-image: linear-gradient(rgba(8, 118, 139, .05) 1px, transparent 1px), linear-gradient(90deg, rgba(8, 118, 139, .05) 1px, transparent 1px); background-size: 34px 34px; mask-image: linear-gradient(to bottom, rgba(0,0,0,.42), transparent 72%); }
|
|
58
|
+
.content { position: relative; z-index: 1; }
|
|
59
|
+
.topline { display: flex; align-items: flex-start; justify-content: space-between; }
|
|
60
|
+
.eyebrow { color: #08768b; font-size: 11px; font-weight: 700; letter-spacing: .22em; }
|
|
61
|
+
.source { color: #6f7d83; font-size: 11px; line-height: 1.6; text-align: right; }
|
|
62
|
+
h1 { margin: 22px 0 8px; color: #27343c; font-size: 34px; font-weight: 700; letter-spacing: -.04em; }
|
|
63
|
+
.subtitle { margin: 0; color: #6f7d83; font-size: 14px; }
|
|
64
|
+
.rule { width: 100%; height: 1px; margin: 26px 0 22px; background: linear-gradient(90deg, rgba(8, 118, 139, .36), rgba(8, 118, 139, .08), transparent); }
|
|
65
|
+
.metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
66
|
+
.metric { position: relative; min-height: 186px; padding: 22px 24px; overflow: hidden; border: 1px solid #c8dedd; border-radius: 13px; background: #eef7f6; }
|
|
67
|
+
.metric::after { content: ""; position: absolute; right: -28px; bottom: -58px; width: 180px; height: 180px; border: 1px solid rgba(8, 118, 139, .18); border-radius: 50%; box-shadow: 0 0 0 18px rgba(8, 118, 139, .035), 0 0 0 36px rgba(8, 118, 139, .018); }
|
|
68
|
+
.metric.seven { background: #faf6ee; border-color: #e2d8c5; }
|
|
69
|
+
.metric.seven::after { border-color: rgba(184, 134, 59, .22); box-shadow: 0 0 0 18px rgba(184, 134, 59, .035), 0 0 0 36px rgba(184, 134, 59, .018); }
|
|
70
|
+
.metric-label { color: #6f7d83; font-size: 13px; }
|
|
71
|
+
.metric-value { margin-top: 7px; color: #08768b; font-family: Georgia, "Times New Roman", serif; font-size: 66px; font-weight: 700; letter-spacing: -.07em; line-height: 1; }
|
|
72
|
+
.metric.seven .metric-value { color: #a27831; }
|
|
73
|
+
.metric-note { margin-top: 14px; color: #52646a; font-size: 12px; }
|
|
74
|
+
.lower { display: grid; grid-template-columns: 1.15fr .85fr; gap: 16px; margin-top: 16px; }
|
|
75
|
+
.panel { padding: 18px 20px; border: 1px solid #d2dadd; border-radius: 13px; background: #fbfcfc; }
|
|
76
|
+
.panel h2 { margin: 0 0 15px; color: #27343c; font-size: 14px; }
|
|
77
|
+
.sample-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
|
|
78
|
+
.sample { padding-right: 10px; border-right: 1px solid #e0e5e6; }
|
|
79
|
+
.sample:last-child { border-right: 0; }
|
|
80
|
+
.sample-value { color: #27343c; font-size: 22px; font-weight: 700; }
|
|
81
|
+
.sample-label { margin-top: 4px; color: #6f7d83; font-size: 11px; }
|
|
82
|
+
.bar-row { display: grid; grid-template-columns: 76px 1fr 42px; align-items: center; gap: 9px; margin: 12px 0; color: #52646a; font-size: 11px; }
|
|
83
|
+
.bar { height: 7px; overflow: hidden; border-radius: 99px; background: #e4ecec; }
|
|
84
|
+
.bar span { display: block; height: 100%; border-radius: inherit; background: #71c8bd; }
|
|
85
|
+
.bar-row.credit .bar span { background: #d8ad5f; }
|
|
86
|
+
.bar-percent { color: #27343c; text-align: right; }
|
|
87
|
+
.recent { display: flex; align-items: center; justify-content: space-between; padding-top: 15px; margin-top: 16px; border-top: 1px solid #e0e5e6; }
|
|
88
|
+
.recent-label { color: #6f7d83; font-size: 11px; }
|
|
89
|
+
.recent-date { color: #a27831; font-size: 15px; font-weight: 700; }
|
|
90
|
+
.footer { display: flex; align-items: flex-end; justify-content: space-between; gap: 30px; margin-top: 22px; color: #7a888d; font-size: 10px; line-height: 1.65; }
|
|
91
|
+
.footer strong { color: #52646a; font-weight: 600; }
|
|
92
|
+
.mark { color: #08768b; font-size: 11px; font-weight: 700; letter-spacing: .08em; white-space: nowrap; }
|
|
93
|
+
</style>
|
|
94
|
+
</head>
|
|
95
|
+
<body>
|
|
96
|
+
<main class="card">
|
|
97
|
+
<div class="content">
|
|
98
|
+
<header class="topline">
|
|
99
|
+
<div class="eyebrow">CODEX RESET / PUBLIC HISTORY</div>
|
|
100
|
+
<div class="source">AIHOT · ASIA/SHANGHAI<br>生成于 ${escapeHtml(formatGeneratedAt(now))}</div>
|
|
101
|
+
</header>
|
|
102
|
+
<h1>Codex 重置概率分析</h1>
|
|
103
|
+
<p class="subtitle">基于当前公开历史快照的经验统计,不是官方预测</p>
|
|
104
|
+
<div class="rule"></div>
|
|
105
|
+
|
|
106
|
+
<section class="metrics">
|
|
107
|
+
${metricCard('未来 24 小时', percentage(summary.next24hProbability), '至少出现一个公开重置事件', 'day')}
|
|
108
|
+
${metricCard('未来 7 天', percentage(summary.next7dProbability), '至少出现一个公开重置事件', 'seven')}
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<section class="lower">
|
|
112
|
+
<div class="panel">
|
|
113
|
+
<h2>样本概览</h2>
|
|
114
|
+
<div class="sample-grid">
|
|
115
|
+
<div class="sample"><div class="sample-value">${summary.observedDays}</div><div class="sample-label">天观察样本</div></div>
|
|
116
|
+
<div class="sample"><div class="sample-value">${summary.confirmedCount}</div><div class="sample-label">条已确认</div></div>
|
|
117
|
+
<div class="sample"><div class="sample-value">${summary.activeDays}</div><div class="sample-label">个事件日</div></div>
|
|
118
|
+
<div class="sample"><div class="sample-value">${summary.announcedCount}</div><div class="sample-label">条未重置预告</div></div>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="recent"><span class="recent-label">最近确认日期</span><span class="recent-date">${escapeHtml(recentDate)}</span></div>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="panel">
|
|
123
|
+
<h2>已确认事件构成</h2>
|
|
124
|
+
<div class="bar-row"><span>全员重置</span><div class="bar"><span style="width:${directRatio}"></span></div><span class="bar-percent">${summary.directResetCount}</span></div>
|
|
125
|
+
<div class="bar-row credit"><span>重置卡</span><div class="bar"><span style="width:${creditRatio}"></span></div><span class="bar-percent">${summary.resetCreditCount}</span></div>
|
|
126
|
+
<div class="recent"><span class="recent-label">历史事件日率</span><span class="recent-date">${(summary.dailyEventDayRate * 100).toFixed(2)}% / 天</span></div>
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
|
|
130
|
+
<footer class="footer"><span><strong>说明:</strong>概率由历史事件日率进行经验估计,不代表你的账户一定会获得重置。</span><span class="mark">RESET MONITOR</span></footer>
|
|
131
|
+
</div>
|
|
132
|
+
</main>
|
|
133
|
+
</body>
|
|
134
134
|
</html>`;
|
|
135
135
|
}
|
|
136
136
|
async function renderProbabilityImage(browser, summary, now = new Date()) {
|