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/lib/history.js CHANGED
@@ -20,8 +20,8 @@ function parseTimestamp(value) {
20
20
  }
21
21
  function eventTimestamp(event) {
22
22
  const values = [
23
- event.confirmedAt,
24
23
  event.occurredOn ? `${event.occurredOn}T12:00:00+08:00` : null,
24
+ event.confirmedAt,
25
25
  event.schedule?.from,
26
26
  event.createdAt,
27
27
  ];
@@ -43,7 +43,9 @@ function filterHistoryEvents(events, now = new Date(), range = 1) {
43
43
  day: 'numeric',
44
44
  }).formatToParts(now);
45
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);
46
+ const targetMonth = new Date(Date.UTC(Number(values.year), Number(values.month) - 1 - months, 1));
47
+ const lastDay = new Date(Date.UTC(targetMonth.getUTCFullYear(), targetMonth.getUTCMonth() + 1, 0)).getUTCDate();
48
+ const cutoff = Date.UTC(targetMonth.getUTCFullYear(), targetMonth.getUTCMonth(), Math.min(Number(values.day), lastDay), -8);
47
49
  const current = now.getTime();
48
50
  return events.filter((event) => {
49
51
  const timestamp = eventTimestamp(event);
@@ -89,32 +91,33 @@ function renderPostPreview(event) {
89
91
  const post = event.posts[0];
90
92
  if (!post)
91
93
  return '<div class="post-preview empty">暂无来源帖子内容</div>';
92
- const text = post.text || post.originalText;
94
+ const originalText = post.text || post.originalText;
95
+ const text = originalText.length > 500 ? `${originalText.slice(0, 500)}…` : originalText;
93
96
  const avatar = (0, calendar_1.getTiboAvatarDataUri)()
94
97
  ? `<img class="avatar-image" src="${(0, calendar_1.getTiboAvatarDataUri)()}" alt="">`
95
98
  : '<span class="avatar-fallback">T</span>';
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>
99
+ return `<div class="post-preview">
100
+ <div class="post-head">
101
+ <div class="avatar">${avatar}</div>
102
+ <div class="author"><strong>Tibo</strong><span>@thsottiaux</span></div>
103
+ <time>${escapeHtml(timeLabel(parseTimestamp(post.publishedAt) ?? eventTimestamp(event)))} 发布</time>
104
+ </div>
105
+ <p>${escapeHtml(text)}</p>
103
106
  </div>`;
104
107
  }
105
108
  function renderTimelineItem(event) {
106
109
  const timestamp = eventTimestamp(event);
107
110
  const statusClass = event.status === 'confirmed' ? 'confirmed' : 'announced';
108
- const schedule = event.schedule?.label ? `<div class="schedule">时间:${escapeHtml(event.schedule.label)}</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>
111
+ const schedule = event.schedule?.label ? `<div class="schedule">时间:${escapeHtml(event.schedule.label.slice(0, 150))}</div>` : '';
112
+ return `<article class="timeline-item">
113
+ <div class="timeline-date"><strong>${escapeHtml(dateLabel(timestamp))}</strong><span>${escapeHtml(timeLabel(timestamp))}</span></div>
114
+ <div class="timeline-dot ${statusClass}"></div>
115
+ <div class="event-card">
116
+ <div class="event-meta"><span class="type-label">${typeLabel(event)}</span><span class="status ${statusClass}">${statusLabel(event)}</span></div>
117
+ <h2>${escapeHtml(event.title.slice(0, 200))}</h2>
118
+ ${schedule}
119
+ ${renderPostPreview(event)}
120
+ </div>
118
121
  </article>`;
119
122
  }
120
123
  function renderHistoryHtml(events, now = new Date()) {
@@ -126,81 +129,81 @@ function renderHistoryHtml(events, now = new Date()) {
126
129
  const firstTimestamp = sorted.at(-1) ? eventTimestamp(sorted.at(-1)) : now.getTime();
127
130
  const lastTimestamp = sorted[0] ? eventTimestamp(sorted[0]) : now.getTime();
128
131
  const period = `${dateLabel(firstTimestamp)} 至 ${dateLabel(lastTimestamp)}`;
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>
132
+ return `<!doctype html>
133
+ <html lang="zh-CN">
134
+ <head>
135
+ <meta charset="utf-8">
136
+ <style>
137
+ * { box-sizing: border-box; }
138
+ html, body { margin: 0; padding: 0; background: #f4f6f5; color: #27343c; font-family: "Microsoft YaHei", "Noto Sans CJK SC", Arial, sans-serif; }
139
+ body { padding: 24px; }
140
+ .history-shell { max-width: 1280px; margin: 0 auto; padding: 28px 30px 34px; border: 1px solid #d2dadd; border-radius: 16px; background: #fff; }
141
+ .page-header { display: flex; align-items: flex-start; justify-content: space-between; padding-bottom: 20px; border-bottom: 1px solid #e0e5e6; }
142
+ .eyebrow { color: #08768b; font-size: 12px; letter-spacing: .08em; }
143
+ h1 { margin: 7px 0 8px; font-size: 28px; line-height: 1.2; }
144
+ .subtitle { margin: 0; color: #6f7d83; font-size: 13px; }
145
+ .generated { color: #6f7d83; font-size: 11px; text-align: right; line-height: 1.6; }
146
+ .layout { display: grid; grid-template-columns: minmax(0, 1fr) 286px; gap: 28px; padding-top: 26px; }
147
+ .timeline { position: relative; }
148
+ .timeline::before { content: ""; position: absolute; left: 112px; top: 16px; bottom: 16px; width: 1px; background: #cbd7d9; }
149
+ .timeline-item { position: relative; display: grid; grid-template-columns: 94px 36px minmax(0, 1fr); gap: 0; margin-bottom: 18px; }
150
+ .timeline-date { padding-top: 17px; padding-right: 14px; text-align: right; line-height: 1.5; }
151
+ .timeline-date strong { display: block; color: #27343c; font-size: 11px; white-space: nowrap; }
152
+ .timeline-date span { color: #89969a; font-size: 10px; }
153
+ .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; }
154
+ .timeline-dot.announced { background: #b8863b; box-shadow: 0 0 0 1px #b8863b; }
155
+ .event-card { padding: 15px 17px 16px; border: 1px solid #d2dadd; border-radius: 13px; background: #fff; }
156
+ .event-meta { display: flex; gap: 7px; align-items: center; margin-bottom: 7px; }
157
+ .type-label, .status { display: inline-block; padding: 3px 7px; border-radius: 999px; font-size: 10px; }
158
+ .type-label { color: #08768b; background: #edf7f8; }
159
+ .status.confirmed { color: #177255; background: #edf8f1; }
160
+ .status.announced { color: #8b6426; background: #fbf4e6; }
161
+ .event-card h2 { margin: 0 0 10px; font-size: 17px; line-height: 1.35; }
162
+ .schedule { margin-bottom: 10px; color: #08768b; font-size: 11px; }
163
+ .post-preview { padding: 11px 12px; border: 1px solid #e0e5e6; border-radius: 10px; background: #fbfcfc; }
164
+ .post-preview.empty { color: #89969a; font-size: 12px; }
165
+ .post-head { display: flex; align-items: center; min-width: 0; }
166
+ .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; }
167
+ .avatar-image { display: block; width: 100%; height: 100%; object-fit: cover; }
168
+ .avatar-fallback { display: block; }
169
+ .author { display: flex; flex-direction: column; margin-left: 8px; line-height: 1.2; }
170
+ .author strong { font-size: 11px; }
171
+ .author span { color: #89969a; font-size: 9px; }
172
+ .post-head time { margin-left: auto; color: #89969a; font-size: 10px; }
173
+ .post-preview p { margin: 10px 0 0; color: #27343c; font-size: 12px; line-height: 1.55; }
174
+ .summary { align-self: start; padding: 18px; border: 1px solid #d2dadd; border-radius: 13px; background: #fbfcfc; }
175
+ .summary h2 { margin: 0 0 15px; font-size: 17px; }
176
+ .summary-period { margin-bottom: 18px; color: #6f7d83; font-size: 11px; line-height: 1.5; }
177
+ .stat { display: flex; align-items: baseline; justify-content: space-between; padding: 11px 0; border-top: 1px solid #e0e5e6; }
178
+ .stat span { color: #6f7d83; font-size: 12px; }
179
+ .stat strong { color: #27343c; font-size: 22px; }
180
+ .summary-note { margin: 17px 0 0; color: #89969a; font-size: 10px; line-height: 1.6; }
181
+ .empty { padding: 54px 0; color: #89969a; text-align: center; }
182
+ </style>
183
+ </head>
184
+ <body>
185
+ <main class="history-shell">
186
+ <header class="page-header">
187
+ <div><div class="eyebrow">CODEX RESET MONITOR</div><h1>Codex 重置历史</h1><p class="subtitle">按北京时间排列的公开重置事件时间线</p></div>
188
+ <div class="generated">生成时间<br>${escapeHtml(formatGeneratedAt(now))}</div>
189
+ </header>
190
+ <div class="layout">
191
+ <section class="timeline">
192
+ ${sorted.length ? sorted.map(renderTimelineItem).join('') : '<div class="empty">暂无历史事件</div>'}
193
+ </section>
194
+ <aside class="summary">
195
+ <h2>历史概览</h2>
196
+ <div class="summary-period">统计区间<br>${escapeHtml(period)}</div>
197
+ <div class="stat"><span>事件总数</span><strong>${events.length}</strong></div>
198
+ <div class="stat"><span>已确认</span><strong>${confirmed.length}</strong></div>
199
+ <div class="stat"><span>全员重置</span><strong>${directResetCount}</strong></div>
200
+ <div class="stat"><span>重置卡</span><strong>${resetCreditCount}</strong></div>
201
+ <div class="stat"><span>未重置预告</span><strong>${announcedCount}</strong></div>
202
+ <p class="summary-note">数据来自 AIHOT 公开快照。时间线仅展示公开事件,不代表账户一定获得重置。</p>
203
+ </aside>
204
+ </div>
205
+ </main>
206
+ </body>
204
207
  </html>`;
205
208
  }
206
209
  async function renderHistoryImage(browser, events, now = new Date()) {