@aidget-community/weibo-hot 0.0.0-stage → 0.1.1

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/agents.md ADDED
@@ -0,0 +1,29 @@
1
+ # 微博热搜
2
+
3
+ ## 用途
4
+
5
+ 显示微博实时热搜前 10 条。组件加载后立即请求一次,此后每 20 分钟刷新;点击条目会用系统默认浏览器打开微博搜索结果。
6
+
7
+ ## 数据与权限
8
+
9
+ - 必须保留 `script` 权限。
10
+ - `network` 必须包含 `https://weibo.com` 与 `https://s.weibo.com`。
11
+ - 接口为 `https://weibo.com/ajax/side/hotSearch`,通过宿主 `window.aidget.fetch()` 读取。宿主不会自动注入 `Referer`,脚本必须显式发送 `Referer: https://weibo.com/`;缺少该请求头时接口返回 403。
12
+ - 过滤带 `is_ad` 的广告条目,优先显示 `label_name` 或 `small_icon_desc`,没有标签时显示压缩后的热度数字。
13
+ - 标题必须通过 `textContent` 渲染,不能注入 HTML。
14
+
15
+ ## 可改区域
16
+
17
+ - `hot-list` 是脚本管理的榜单容器,不应手工 patch;刷新后会整体重建。
18
+ - 可修改标题、副标题、平台色变量和页脚文案,但不要破坏 10 行紧凑网格。
19
+
20
+ ## 布局
21
+
22
+ - 固定尺寸为 360×340;页面声明 `aidget-surface=none` 并自行绘制完整卡面。
23
+ - 保留 `.rank-list` 的 10 行网格;标签与热度数字共用右侧指标位。
24
+ - 行本身使用 `<button>`,保持键盘焦点、鼠标点击和 Aidget 拖拽边界。
25
+
26
+ ## 刷新与失败
27
+
28
+ - 不做自动重试;首轮失败显示完整错误态,已有数据时保留旧行并在页脚显示“更新失败”和错误摘要。
29
+ - 成功刷新后更新页脚时间;异步初始化结束后调用 `window.aidget.ready()`。
Binary file
package/config.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "weibo-hot",
3
+ "title": "微博热搜",
4
+ "minHostVersion": "0.1.1",
5
+ "logo": null,
6
+ "screenshots": ["assets/screenshot.png"],
7
+ "platforms": ["macos", "windows", "linux"],
8
+ "size": { "width": 360, "height": 340 },
9
+ "dragMode": "all",
10
+ "permissions": ["script"],
11
+ "network": ["https://weibo.com", "https://s.weibo.com"]
12
+ }
package/index.html ADDED
@@ -0,0 +1,212 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="aidget-surface" content="none" />
7
+ <link rel="stylesheet" href="./style.css" />
8
+ </head>
9
+ <body class="theme-weibo">
10
+ <main class="card">
11
+ <header class="top">
12
+ <div class="brand">
13
+ <span class="seal" aria-hidden="true">微</span>
14
+ <div class="heading">
15
+ <h1>微博热搜</h1>
16
+ <p>全网正在热议</p>
17
+ </div>
18
+ </div>
19
+ <span class="pulse"><i aria-hidden="true"></i>LIVE</span>
20
+ </header>
21
+
22
+ <ol
23
+ class="rank-list is-loading"
24
+ id="rank-list"
25
+ data-aidget-region="hot-list"
26
+ aria-label="微博热搜"
27
+ >
28
+ <li class="skeleton-row"></li>
29
+ <li class="skeleton-row"></li>
30
+ <li class="skeleton-row"></li>
31
+ <li class="skeleton-row"></li>
32
+ <li class="skeleton-row"></li>
33
+ <li class="skeleton-row"></li>
34
+ <li class="skeleton-row"></li>
35
+ <li class="skeleton-row"></li>
36
+ <li class="skeleton-row"></li>
37
+ <li class="skeleton-row"></li>
38
+ </ol>
39
+
40
+ <footer class="foot">
41
+ <span id="status" role="status" aria-live="polite">正在刷新</span>
42
+ </footer>
43
+ </main>
44
+
45
+ <script>
46
+ (function () {
47
+ 'use strict';
48
+
49
+ const API_URL = 'https://weibo.com/ajax/side/hotSearch';
50
+ const REFRESH_MS = 20 * 60 * 1000;
51
+ const list = document.getElementById('rank-list');
52
+ const status = document.getElementById('status');
53
+ const clock = new Intl.DateTimeFormat('zh-CN', {
54
+ hour: '2-digit',
55
+ minute: '2-digit',
56
+ hour12: false,
57
+ });
58
+ let lastSuccessAt = null;
59
+ let loading = false;
60
+
61
+ function setStatus(message, state) {
62
+ status.textContent = message;
63
+ status.dataset.state = state;
64
+ }
65
+
66
+ function compactNumber(value) {
67
+ const number = Number(value);
68
+ if (!Number.isFinite(number) || number <= 0) {
69
+ return '';
70
+ }
71
+ if (number >= 10000) {
72
+ return (number / 10000).toFixed(number >= 100000 ? 0 : 1) + '万';
73
+ }
74
+ return String(number);
75
+ }
76
+
77
+ function normalize(payload) {
78
+ if (!payload || !payload.data || !Array.isArray(payload.data.realtime)) {
79
+ throw new Error('数据源返回格式异常');
80
+ }
81
+ return payload.data.realtime
82
+ .filter(function (item) {
83
+ return item && !item.is_ad && typeof item.word === 'string';
84
+ })
85
+ .map(function (item) {
86
+ const title = item.word.trim();
87
+ if (!title) {
88
+ return null;
89
+ }
90
+ const label =
91
+ (typeof item.label_name === 'string' && item.label_name.trim()) ||
92
+ (typeof item.small_icon_desc === 'string' && item.small_icon_desc.trim()) ||
93
+ '';
94
+ const query =
95
+ (typeof item.word_scheme === 'string' && item.word_scheme.trim()) || title;
96
+ return {
97
+ title: title,
98
+ metric: label || compactNumber(item.num),
99
+ metricStyle: label ? 'tag' : 'number',
100
+ url: 'https://s.weibo.com/weibo?q=' + encodeURIComponent(query),
101
+ };
102
+ })
103
+ .filter(Boolean)
104
+ .slice(0, 10);
105
+ }
106
+
107
+ function row(item, index) {
108
+ const element = document.createElement('li');
109
+ element.className = 'rank-row';
110
+ element.style.setProperty('--i', String(index));
111
+
112
+ const link = document.createElement('button');
113
+ link.className = 'rank-link';
114
+ link.type = 'button';
115
+ link.setAttribute('aria-label', '第 ' + String(index + 1) + ' 名,' + item.title);
116
+
117
+ const rank = document.createElement('span');
118
+ rank.className = 'rank-number';
119
+ rank.textContent = String(index + 1).padStart(2, '0');
120
+
121
+ const title = document.createElement('span');
122
+ title.className = 'rank-title';
123
+ title.textContent = item.title;
124
+
125
+ const metric = document.createElement('span');
126
+ metric.className = 'rank-metric';
127
+ if (item.metricStyle === 'tag') {
128
+ metric.classList.add('is-tag');
129
+ }
130
+ metric.textContent = item.metric;
131
+
132
+ link.append(rank, title, metric);
133
+ link.addEventListener('click', function () {
134
+ window.aidget.openUrl(item.url).catch(function (error) {
135
+ setStatus('打开失败 · ' + error.message, 'error');
136
+ });
137
+ });
138
+ element.append(link);
139
+ return element;
140
+ }
141
+
142
+ function render(items) {
143
+ list.classList.remove('is-loading');
144
+ list.replaceChildren.apply(
145
+ list,
146
+ items.map(function (item, index) {
147
+ return row(item, index);
148
+ }),
149
+ );
150
+ }
151
+
152
+ function renderError(message) {
153
+ list.classList.remove('is-loading');
154
+ const element = document.createElement('li');
155
+ element.className = 'error-state';
156
+ const mark = document.createElement('span');
157
+ mark.className = 'error-mark';
158
+ mark.textContent = '!';
159
+ const copy = document.createElement('span');
160
+ const heading = document.createElement('strong');
161
+ heading.textContent = '暂时无法获取热搜';
162
+ const detail = document.createElement('small');
163
+ detail.textContent = message;
164
+ copy.append(heading, detail);
165
+ element.append(mark, copy);
166
+ list.replaceChildren(element);
167
+ }
168
+
169
+ async function load() {
170
+ if (loading) {
171
+ return;
172
+ }
173
+ loading = true;
174
+ if (!lastSuccessAt) {
175
+ list.classList.add('is-loading');
176
+ setStatus('正在刷新', 'loading');
177
+ }
178
+ try {
179
+ if (!window.aidget || typeof window.aidget.fetch !== 'function') {
180
+ throw new Error('宿主网络能力不可用');
181
+ }
182
+ const items = normalize(
183
+ await window.aidget.fetch(API_URL, {
184
+ headers: { Referer: 'https://weibo.com/' },
185
+ }),
186
+ );
187
+ if (items.length === 0) {
188
+ throw new Error('热搜当前为空');
189
+ }
190
+ render(items);
191
+ lastSuccessAt = new Date();
192
+ setStatus('更新于 ' + clock.format(lastSuccessAt), 'ok');
193
+ } catch (error) {
194
+ const message = error && typeof error.message === 'string' ? error.message : '未知错误';
195
+ if (lastSuccessAt) {
196
+ setStatus('更新失败 · ' + message, 'error');
197
+ } else {
198
+ setStatus('更新失败', 'error');
199
+ renderError(message);
200
+ }
201
+ } finally {
202
+ loading = false;
203
+ window.aidget?.ready?.();
204
+ }
205
+ }
206
+
207
+ void load();
208
+ window.setInterval(load, REFRESH_MS);
209
+ })();
210
+ </script>
211
+ </body>
212
+ </html>
package/package.json CHANGED
@@ -1,6 +1,25 @@
1
1
  {
2
2
  "name": "@aidget-community/weibo-hot",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "微博实时热搜前十,过滤广告,每 20 分钟刷新。",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "micooz",
8
+ "url": "https://github.com/micooz"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/aidget-ai/widgets.git",
13
+ "directory": "packages/weibo-hot"
14
+ },
15
+ "files": [
16
+ "config.json",
17
+ "index.html",
18
+ "style.css",
19
+ "agents.md",
20
+ "assets"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ }
25
+ }
package/style.css ADDED
@@ -0,0 +1,354 @@
1
+ :root {
2
+ color-scheme: light;
3
+ --accent: #e84d2f;
4
+ --accent-deep: #a92c1c;
5
+ --accent-soft: #ffd8cc;
6
+ --ink: #171b22;
7
+ --muted: #737b87;
8
+ --danger: #bd3f32;
9
+ }
10
+
11
+ * {
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ html,
16
+ body {
17
+ width: 100%;
18
+ height: 100%;
19
+ margin: 0;
20
+ overflow: hidden;
21
+ background: transparent;
22
+ }
23
+
24
+ body {
25
+ padding: 7px;
26
+ color: var(--ink);
27
+ font-family: 'Avenir Next', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
28
+ -webkit-font-smoothing: antialiased;
29
+ user-select: none;
30
+ }
31
+
32
+ button {
33
+ font: inherit;
34
+ }
35
+
36
+ .card {
37
+ position: relative;
38
+ display: flex;
39
+ flex-direction: column;
40
+ width: 100%;
41
+ height: 100%;
42
+ padding: 15px 16px 12px;
43
+ overflow: hidden;
44
+ border: 1px solid rgba(255, 255, 255, 0.88);
45
+ border-radius: 24px;
46
+ background:
47
+ radial-gradient(95% 75% at 100% 0%, rgba(232, 77, 47, 0.14), transparent 58%),
48
+ radial-gradient(70% 60% at 0% 100%, rgba(230, 174, 85, 0.1), transparent 64%),
49
+ linear-gradient(145deg, rgba(255, 255, 252, 0.97), rgba(241, 243, 240, 0.91));
50
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.94);
51
+ }
52
+
53
+ .card::before {
54
+ position: absolute;
55
+ inset: 0;
56
+ background:
57
+ linear-gradient(90deg, transparent 0 15px, rgba(17, 24, 35, 0.035) 15px 16px, transparent 16px),
58
+ repeating-linear-gradient(
59
+ 0deg,
60
+ rgba(16, 25, 38, 0.018) 0,
61
+ rgba(16, 25, 38, 0.018) 1px,
62
+ transparent 1px,
63
+ transparent 7px
64
+ );
65
+ content: '';
66
+ pointer-events: none;
67
+ }
68
+
69
+ .top,
70
+ .rank-list,
71
+ .foot {
72
+ position: relative;
73
+ z-index: 1;
74
+ }
75
+
76
+ .top {
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: space-between;
80
+ min-height: 46px;
81
+ padding-bottom: 7px;
82
+ border-bottom: 1px solid rgba(27, 35, 47, 0.08);
83
+ }
84
+
85
+ .brand {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 9px;
89
+ min-width: 0;
90
+ }
91
+
92
+ .seal {
93
+ display: grid;
94
+ width: 31px;
95
+ height: 31px;
96
+ flex: 0 0 31px;
97
+ place-items: center;
98
+ border-radius: 10px 10px 10px 4px;
99
+ background: linear-gradient(145deg, var(--accent), var(--accent-deep));
100
+ box-shadow: 0 6px 14px rgba(232, 77, 47, 0.24);
101
+ color: #fff;
102
+ font-family: 'Songti SC', 'STSong', serif;
103
+ font-size: 18px;
104
+ font-weight: 700;
105
+ }
106
+
107
+ .heading {
108
+ min-width: 0;
109
+ }
110
+
111
+ .heading h1 {
112
+ margin: 0;
113
+ font-size: 15px;
114
+ font-weight: 750;
115
+ letter-spacing: 0.04em;
116
+ line-height: 1.2;
117
+ }
118
+
119
+ .heading p {
120
+ margin: 2px 0 0;
121
+ color: var(--muted);
122
+ font-size: 9px;
123
+ font-weight: 600;
124
+ letter-spacing: 0.12em;
125
+ }
126
+
127
+ .pulse {
128
+ display: inline-flex;
129
+ align-items: center;
130
+ gap: 4px;
131
+ flex: 0 0 auto;
132
+ padding: 4px 7px;
133
+ border: 1px solid rgba(232, 77, 47, 0.16);
134
+ border-radius: 999px;
135
+ background: rgba(255, 255, 255, 0.56);
136
+ color: var(--accent-deep);
137
+ font-size: 8px;
138
+ font-weight: 800;
139
+ letter-spacing: 0.1em;
140
+ }
141
+
142
+ .pulse i {
143
+ width: 5px;
144
+ height: 5px;
145
+ border-radius: 50%;
146
+ background: var(--accent);
147
+ box-shadow: 0 0 0 3px rgba(232, 77, 47, 0.12);
148
+ }
149
+
150
+ .rank-list {
151
+ display: grid;
152
+ flex: 1;
153
+ min-height: 0;
154
+ grid-template-rows: repeat(10, minmax(0, 1fr));
155
+ gap: 1px;
156
+ margin: 5px 0 3px;
157
+ padding: 0;
158
+ list-style: none;
159
+ }
160
+
161
+ .rank-row {
162
+ min-height: 0;
163
+ animation: row-in 360ms cubic-bezier(0.22, 0.8, 0.24, 1) both;
164
+ animation-delay: calc(var(--i) * 18ms);
165
+ }
166
+
167
+ .rank-link {
168
+ display: grid;
169
+ width: 100%;
170
+ height: 100%;
171
+ grid-template-columns: 23px minmax(0, 1fr) auto;
172
+ align-items: center;
173
+ gap: 7px;
174
+ padding: 0 5px;
175
+ border: 0;
176
+ border-radius: 8px;
177
+ outline: none;
178
+ background: transparent;
179
+ color: inherit;
180
+ cursor: pointer;
181
+ text-align: left;
182
+ transition: background-color 150ms ease;
183
+ }
184
+
185
+ .rank-link:hover,
186
+ .rank-link:focus-visible {
187
+ background: rgba(255, 255, 255, 0.68);
188
+ }
189
+
190
+ .rank-link:focus-visible {
191
+ box-shadow: 0 0 0 2px rgba(232, 77, 47, 0.22);
192
+ }
193
+
194
+ .rank-number {
195
+ display: grid;
196
+ width: 21px;
197
+ height: 18px;
198
+ place-items: center;
199
+ border-radius: 6px;
200
+ background: rgba(27, 35, 47, 0.055);
201
+ color: #737b87;
202
+ font-size: 9px;
203
+ font-weight: 800;
204
+ font-variant-numeric: tabular-nums;
205
+ }
206
+
207
+ .rank-row:nth-child(-n + 3) .rank-number {
208
+ background: linear-gradient(145deg, var(--accent), var(--accent-deep));
209
+ box-shadow: 0 3px 8px rgba(232, 77, 47, 0.2);
210
+ color: #fff;
211
+ }
212
+
213
+ .rank-title {
214
+ min-width: 0;
215
+ overflow: hidden;
216
+ font-size: 12px;
217
+ font-weight: 630;
218
+ letter-spacing: 0.01em;
219
+ line-height: 1.25;
220
+ text-overflow: ellipsis;
221
+ white-space: nowrap;
222
+ }
223
+
224
+ .rank-metric {
225
+ max-width: 76px;
226
+ overflow: hidden;
227
+ color: var(--accent-deep);
228
+ font-size: 9px;
229
+ font-weight: 700;
230
+ font-variant-numeric: tabular-nums;
231
+ text-overflow: ellipsis;
232
+ white-space: nowrap;
233
+ }
234
+
235
+ .rank-metric.is-tag {
236
+ min-width: 18px;
237
+ padding: 2px 5px;
238
+ border-radius: 999px;
239
+ background: var(--accent-soft);
240
+ color: var(--accent-deep);
241
+ text-align: center;
242
+ }
243
+
244
+ .skeleton-row {
245
+ position: relative;
246
+ overflow: hidden;
247
+ border-radius: 8px;
248
+ background: rgba(31, 39, 51, 0.055);
249
+ }
250
+
251
+ .skeleton-row::after {
252
+ position: absolute;
253
+ inset: 0;
254
+ background: linear-gradient(
255
+ 100deg,
256
+ transparent 15%,
257
+ rgba(255, 255, 255, 0.68) 48%,
258
+ transparent 82%
259
+ );
260
+ content: '';
261
+ transform: translateX(-100%);
262
+ animation: shimmer 1.3s ease-in-out infinite;
263
+ }
264
+
265
+ .error-state {
266
+ display: flex;
267
+ grid-row: 1 / -1;
268
+ align-items: center;
269
+ justify-content: center;
270
+ gap: 10px;
271
+ border: 1px dashed rgba(189, 63, 50, 0.22);
272
+ border-radius: 14px;
273
+ background: rgba(255, 248, 244, 0.62);
274
+ }
275
+
276
+ .error-mark {
277
+ display: grid;
278
+ width: 28px;
279
+ height: 28px;
280
+ place-items: center;
281
+ border-radius: 9px;
282
+ background: var(--danger);
283
+ color: white;
284
+ font-size: 15px;
285
+ font-weight: 800;
286
+ }
287
+
288
+ .error-state strong,
289
+ .error-state small {
290
+ display: block;
291
+ }
292
+
293
+ .error-state strong {
294
+ font-size: 12px;
295
+ }
296
+
297
+ .error-state small {
298
+ max-width: 220px;
299
+ margin-top: 3px;
300
+ overflow: hidden;
301
+ color: var(--muted);
302
+ font-size: 9px;
303
+ text-overflow: ellipsis;
304
+ white-space: nowrap;
305
+ }
306
+
307
+ .foot {
308
+ display: flex;
309
+ align-items: center;
310
+ justify-content: flex-start;
311
+ min-height: 17px;
312
+ padding-top: 4px;
313
+ color: var(--muted);
314
+ font-size: 8.5px;
315
+ font-weight: 650;
316
+ letter-spacing: 0.04em;
317
+ }
318
+
319
+ #status {
320
+ min-width: 0;
321
+ overflow: hidden;
322
+ text-overflow: ellipsis;
323
+ white-space: nowrap;
324
+ }
325
+
326
+ #status[data-state='ok'] {
327
+ color: var(--accent-deep);
328
+ }
329
+
330
+ #status[data-state='error'] {
331
+ color: var(--danger);
332
+ }
333
+
334
+ @keyframes row-in {
335
+ from {
336
+ transform: translateY(4px);
337
+ }
338
+ to {
339
+ transform: translateY(0);
340
+ }
341
+ }
342
+
343
+ @keyframes shimmer {
344
+ to {
345
+ transform: translateX(100%);
346
+ }
347
+ }
348
+
349
+ @media (prefers-reduced-motion: reduce) {
350
+ .rank-row,
351
+ .skeleton-row::after {
352
+ animation: none;
353
+ }
354
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.