@zhin.js/plugin-link-poster 1.1.14 → 1.1.16
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/i18n.d.ts +2 -0
- package/lib/i18n.d.ts.map +1 -0
- package/lib/i18n.js +12 -0
- package/lib/i18n.js.map +1 -0
- package/lib/platforms.d.ts +2 -1
- package/lib/platforms.d.ts.map +1 -1
- package/lib/platforms.js +21 -17
- package/lib/platforms.js.map +1 -1
- package/lib/render.d.ts +2 -1
- package/lib/render.d.ts.map +1 -1
- package/lib/render.js +4 -2
- package/lib/render.js.map +1 -1
- package/middlewares/link-poster/index.js +4 -2
- package/middlewares/link-poster/index.ts +4 -2
- package/package.json +6 -6
- package/src/i18n.ts +12 -0
- package/src/platforms.ts +21 -18
- package/src/render.ts +5 -2
package/lib/i18n.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,wMASnB,CAAC"}
|
package/lib/i18n.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createMessages } from 'zhin.js/i18n';
|
|
2
|
+
export const messages = createMessages({
|
|
3
|
+
views: { 'zh-CN': '播放', en: 'Views' },
|
|
4
|
+
danmaku: { 'zh-CN': '弹幕', en: 'Danmaku' },
|
|
5
|
+
likes: { 'zh-CN': '点赞', en: 'Likes' },
|
|
6
|
+
status: { 'zh-CN': '状态', en: 'Status' },
|
|
7
|
+
comments: { 'zh-CN': '评论', en: 'Comments' },
|
|
8
|
+
language: { 'zh-CN': '语言', en: 'Language' },
|
|
9
|
+
douyin: { 'zh-CN': '抖音', en: 'Douyin' },
|
|
10
|
+
xiaohongshu: { 'zh-CN': '小红书', en: 'Xiaohongshu' },
|
|
11
|
+
});
|
|
12
|
+
//# sourceMappingURL=i18n.js.map
|
package/lib/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,CAAC,MAAM,QAAQ,GAAG,cAAc,CAAC;IACrC,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE;IACrC,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE;IACzC,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE;IACrC,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE;IACvC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE;IAC3C,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE;IAC3C,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE;IACvC,WAAW,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE;CACnD,CAAC,CAAC"}
|
package/lib/platforms.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { LinkMeta } from './types.js';
|
|
2
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
2
3
|
/**
|
|
3
4
|
* 从消息文本中检测第一个匹配的平台链接并解析元数据。
|
|
4
5
|
* 仅返回第一个命中结果,避免单条消息产生过多海报。
|
|
5
6
|
*/
|
|
6
|
-
export declare function detectAndParse(text: string): Promise<LinkMeta | null>;
|
|
7
|
+
export declare function detectAndParse(text: string, locale?: Locale): Promise<LinkMeta | null>;
|
|
7
8
|
//# sourceMappingURL=platforms.d.ts.map
|
package/lib/platforms.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platforms.d.ts","sourceRoot":"","sources":["../src/platforms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"platforms.d.ts","sourceRoot":"","sources":["../src/platforms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AAiOzD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAc5F"}
|
package/lib/platforms.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveLocale } from 'zhin.js/i18n';
|
|
2
|
+
import { messages as t } from './i18n.js';
|
|
1
3
|
// ── 工具函数 ──────────────────────────────────────────────────────────────────
|
|
2
4
|
const FETCH_TIMEOUT = 10_000;
|
|
3
5
|
async function fetchWithTimeout(url, init) {
|
|
@@ -32,9 +34,11 @@ async function imageToDataUri(url) {
|
|
|
32
34
|
return undefined;
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
|
-
function formatNumber(n) {
|
|
37
|
+
function formatNumber(n, locale) {
|
|
36
38
|
if (n == null)
|
|
37
39
|
return '-';
|
|
40
|
+
if (resolveLocale(locale) === 'en')
|
|
41
|
+
return new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(n);
|
|
38
42
|
if (n >= 100_000_000)
|
|
39
43
|
return (n / 100_000_000).toFixed(1) + '亿';
|
|
40
44
|
if (n >= 10_000)
|
|
@@ -53,7 +57,7 @@ function extractMeta(html, property) {
|
|
|
53
57
|
}
|
|
54
58
|
// ── Bilibili ──────────────────────────────────────────────────────────────────
|
|
55
59
|
const BILIBILI_RE = /(?:bilibili\.com\/video\/|b23\.tv\/)/i;
|
|
56
|
-
async function parseBilibili(url) {
|
|
60
|
+
async function parseBilibili(url, locale) {
|
|
57
61
|
// 短链跳转
|
|
58
62
|
if (url.includes('b23.tv')) {
|
|
59
63
|
const res = await fetchWithTimeout(url, { redirect: 'follow' });
|
|
@@ -79,15 +83,15 @@ async function parseBilibili(url) {
|
|
|
79
83
|
cover: await imageToDataUri(d.pic),
|
|
80
84
|
url: `https://www.bilibili.com/video/${bvMatch[0]}`,
|
|
81
85
|
stats: {
|
|
82
|
-
'
|
|
83
|
-
'
|
|
84
|
-
'
|
|
86
|
+
[t(locale, 'views')]: formatNumber(d.stat?.view, locale),
|
|
87
|
+
[t(locale, 'danmaku')]: formatNumber(d.stat?.danmaku, locale),
|
|
88
|
+
[t(locale, 'likes')]: formatNumber(d.stat?.like, locale),
|
|
85
89
|
},
|
|
86
90
|
};
|
|
87
91
|
}
|
|
88
92
|
// ── GitHub ────────────────────────────────────────────────────────────────────
|
|
89
93
|
const GITHUB_RE = /github\.com\/([\w.-]+)\/([\w.-]+)/i;
|
|
90
|
-
async function parseGithub(url) {
|
|
94
|
+
async function parseGithub(url, locale) {
|
|
91
95
|
const repoMatch = url.match(GITHUB_RE);
|
|
92
96
|
if (!repoMatch)
|
|
93
97
|
return null;
|
|
@@ -110,8 +114,8 @@ async function parseGithub(url) {
|
|
|
110
114
|
authorAvatar: await imageToDataUri(d.user?.avatar_url),
|
|
111
115
|
url,
|
|
112
116
|
stats: {
|
|
113
|
-
'
|
|
114
|
-
'
|
|
117
|
+
[t(locale, 'status')]: d.state === 'open' ? '🟢 Open' : '🟣 Closed',
|
|
118
|
+
[t(locale, 'comments')]: String(d.comments ?? 0),
|
|
115
119
|
},
|
|
116
120
|
};
|
|
117
121
|
}
|
|
@@ -129,15 +133,15 @@ async function parseGithub(url) {
|
|
|
129
133
|
cover: await imageToDataUri(`https://opengraph.githubassets.com/1/${owner}/${repo}`),
|
|
130
134
|
url,
|
|
131
135
|
stats: {
|
|
132
|
-
'Star': formatNumber(d.stargazers_count),
|
|
133
|
-
'Fork': formatNumber(d.forks_count),
|
|
134
|
-
'
|
|
136
|
+
'Star': formatNumber(d.stargazers_count, locale),
|
|
137
|
+
'Fork': formatNumber(d.forks_count, locale),
|
|
138
|
+
[t(locale, 'language')]: d.language || '-',
|
|
135
139
|
},
|
|
136
140
|
};
|
|
137
141
|
}
|
|
138
142
|
// ── 抖音 ──────────────────────────────────────────────────────────────────────
|
|
139
143
|
const DOUYIN_RE = /(?:douyin\.com\/video\/|v\.douyin\.com\/)/i;
|
|
140
|
-
async function parseDouyin(url) {
|
|
144
|
+
async function parseDouyin(url, locale) {
|
|
141
145
|
try {
|
|
142
146
|
const res = await fetchWithTimeout(url, {
|
|
143
147
|
headers: {
|
|
@@ -155,7 +159,7 @@ async function parseDouyin(url) {
|
|
|
155
159
|
description: extractMeta(html, 'og:description'),
|
|
156
160
|
cover: await imageToDataUri(extractMeta(html, 'og:image')),
|
|
157
161
|
url: res.url || url,
|
|
158
|
-
author: extractMeta(html, 'og:site_name') || '
|
|
162
|
+
author: extractMeta(html, 'og:site_name') || t(locale, 'douyin'),
|
|
159
163
|
};
|
|
160
164
|
}
|
|
161
165
|
catch {
|
|
@@ -164,7 +168,7 @@ async function parseDouyin(url) {
|
|
|
164
168
|
}
|
|
165
169
|
// ── 小红书 ────────────────────────────────────────────────────────────────────
|
|
166
170
|
const XIAOHONGSHU_RE = /(?:xiaohongshu\.com\/(?:explore|discovery)|xhslink\.com\/)/i;
|
|
167
|
-
async function parseXiaohongshu(url) {
|
|
171
|
+
async function parseXiaohongshu(url, locale) {
|
|
168
172
|
try {
|
|
169
173
|
const res = await fetchWithTimeout(url, {
|
|
170
174
|
headers: {
|
|
@@ -182,7 +186,7 @@ async function parseXiaohongshu(url) {
|
|
|
182
186
|
description: extractMeta(html, 'og:description'),
|
|
183
187
|
cover: await imageToDataUri(extractMeta(html, 'og:image')),
|
|
184
188
|
url: res.url || url,
|
|
185
|
-
author: extractMeta(html, 'author') || extractMeta(html, 'og:site_name') || '
|
|
189
|
+
author: extractMeta(html, 'author') || extractMeta(html, 'og:site_name') || t(locale, 'xiaohongshu'),
|
|
186
190
|
};
|
|
187
191
|
}
|
|
188
192
|
catch {
|
|
@@ -199,7 +203,7 @@ const PARSERS = [
|
|
|
199
203
|
* 从消息文本中检测第一个匹配的平台链接并解析元数据。
|
|
200
204
|
* 仅返回第一个命中结果,避免单条消息产生过多海报。
|
|
201
205
|
*/
|
|
202
|
-
export async function detectAndParse(text) {
|
|
206
|
+
export async function detectAndParse(text, locale) {
|
|
203
207
|
// 提取所有 URL
|
|
204
208
|
const urls = text.match(/https?:\/\/[^\s<>)"']+/gi);
|
|
205
209
|
if (!urls)
|
|
@@ -207,7 +211,7 @@ export async function detectAndParse(text) {
|
|
|
207
211
|
for (const url of urls) {
|
|
208
212
|
for (const { test, parse } of PARSERS) {
|
|
209
213
|
if (test.test(url)) {
|
|
210
|
-
const meta = await parse(url);
|
|
214
|
+
const meta = await parse(url, locale);
|
|
211
215
|
if (meta)
|
|
212
216
|
return meta;
|
|
213
217
|
}
|
package/lib/platforms.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platforms.js","sourceRoot":"","sources":["../src/platforms.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"platforms.js","sourceRoot":"","sources":["../src/platforms.ts"],"names":[],"mappings":"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"}
|
package/lib/render.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LinkMeta } from './types.js';
|
|
2
|
+
import type { Locale } from 'zhin.js/i18n';
|
|
2
3
|
/**
|
|
3
4
|
* 生成 satori 兼容的海报 HTML
|
|
4
5
|
*/
|
|
5
|
-
export declare function renderPoster(meta: LinkMeta): string;
|
|
6
|
+
export declare function renderPoster(meta: LinkMeta, locale?: Locale): string;
|
|
6
7
|
//# sourceMappingURL=render.d.ts.map
|
package/lib/render.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,YAAY,CAAA;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAqC1C;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAgEpE"}
|
package/lib/render.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { messages as t } from './i18n.js';
|
|
1
2
|
const THEMES = {
|
|
2
3
|
bilibili: { name: 'Bilibili', color: '#00A1D6', bg: '#E3F6FC' },
|
|
3
4
|
github: { name: 'GitHub', color: '#24292e', bg: '#F0F0F0' },
|
|
@@ -30,8 +31,9 @@ function renderStats(stats, color) {
|
|
|
30
31
|
/**
|
|
31
32
|
* 生成 satori 兼容的海报 HTML
|
|
32
33
|
*/
|
|
33
|
-
export function renderPoster(meta) {
|
|
34
|
-
const theme = THEMES[meta.platform]
|
|
34
|
+
export function renderPoster(meta, locale) {
|
|
35
|
+
const theme = { ...THEMES[meta.platform], ...(meta.platform === 'douyin' || meta.platform === 'xiaohongshu'
|
|
36
|
+
? { name: t(locale, meta.platform) } : {}) };
|
|
35
37
|
// ── 封面区域 ──
|
|
36
38
|
const coverHtml = meta.cover
|
|
37
39
|
? `<img src="${meta.cover}" style="width:480px;height:252px;object-fit:cover;" />`
|
package/lib/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAgD;IAC1D,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE;IAC/D,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE;IAC3D,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE;IACvD,WAAW,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE;CAC9D,CAAA;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC;SACL,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AAC5B,CAAC;AAED,SAAS,QAAQ,CAAC,CAAqB,EAAE,GAAW;IAClD,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IACjB,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;AACnD,CAAC;AAED,SAAS,WAAW,CAAC,KAAyC,EAAE,KAAa;IAC3E,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IACrB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;SAChC,GAAG,CACF,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CACT,6FAA6F;UAC3F,sBAAsB,KAAK,uCAAuC,UAAU,CAAC,CAAC,CAAC,SAAS;UACxF,SAAS,UAAU,CAAC,CAAC,CAAC,SAAS;UAC/B,SAAS,CACd;SACA,IAAI,CAAC,EAAE,CAAC,CAAA;IACX,OAAO,6DAA6D,KAAK,QAAQ,CAAA;AACnF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,IAAc;
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { defineMiddleware } from 'zhin.js/middleware';
|
|
|
3
3
|
import { detectAndParse } from "../../lib/platforms.js";
|
|
4
4
|
import { renderPoster } from "../../lib/render.js";
|
|
5
5
|
import { linkPosterRendererToken } from "../../lib/runtime.js";
|
|
6
|
+
import { resolveLocale } from 'zhin.js/i18n';
|
|
6
7
|
/**
|
|
7
8
|
* Detect Bilibili / GitHub / Douyin / Xiaohongshu links and reply with a poster image.
|
|
8
9
|
* Uses Runtime Message.content (legacy used $raw).
|
|
@@ -18,9 +19,10 @@ export default defineMiddleware({
|
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
21
|
try {
|
|
21
|
-
const
|
|
22
|
+
const locale = resolveLocale(context.input.locale);
|
|
23
|
+
const meta = await detectAndParse(text, locale);
|
|
22
24
|
if (meta) {
|
|
23
|
-
const html = renderPoster(meta);
|
|
25
|
+
const html = renderPoster(meta, locale);
|
|
24
26
|
const result = await context.use(linkPosterRendererToken).render(html, { width: 480 });
|
|
25
27
|
if (result.format === 'png' && typeof result.data === 'object') {
|
|
26
28
|
const base64 = Buffer.from(result.data).toString('base64');
|
|
@@ -3,6 +3,7 @@ import type { Message } from '@zhin.js/core/runtime';
|
|
|
3
3
|
import { detectAndParse } from '../../src/platforms.js';
|
|
4
4
|
import { renderPoster } from '../../src/render.js';
|
|
5
5
|
import { linkPosterRendererToken } from '../../src/runtime.js';
|
|
6
|
+
import { resolveLocale } from 'zhin.js/i18n';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Detect Bilibili / GitHub / Douyin / Xiaohongshu links and reply with a poster image.
|
|
@@ -21,9 +22,10 @@ export default defineMiddleware<Message>({
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
try {
|
|
24
|
-
const
|
|
25
|
+
const locale = resolveLocale(context.input.locale);
|
|
26
|
+
const meta = await detectAndParse(text, locale);
|
|
25
27
|
if (meta) {
|
|
26
|
-
const html = renderPoster(meta);
|
|
28
|
+
const html = renderPoster(meta, locale);
|
|
27
29
|
const result = await context.use(linkPosterRendererToken).render(html, { width: 480 });
|
|
28
30
|
if (result.format === 'png' && typeof result.data === 'object') {
|
|
29
31
|
const base64 = Buffer.from(result.data as Buffer).toString('base64');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-link-poster",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.16",
|
|
4
4
|
"description": "Auto-detect link posters for Zhin.js (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
},
|
|
40
40
|
"license": "MIT",
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@zhin.js/core": "1.1.
|
|
42
|
+
"@zhin.js/core": "1.1.51",
|
|
43
43
|
"@zhin.js/html-renderer": "1.1.3",
|
|
44
|
-
"@zhin.js/skill": "1.1.
|
|
44
|
+
"@zhin.js/skill": "1.1.10"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"typescript": "^6.0.3",
|
|
48
48
|
"vitest": "^4.1.10",
|
|
49
|
-
"zhin.js": "1.1.
|
|
49
|
+
"zhin.js": "1.1.16"
|
|
50
50
|
},
|
|
51
51
|
"repository": {
|
|
52
52
|
"type": "git",
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"plugins": []
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"@zhin.js/middleware": "^1.1.
|
|
83
|
-
"zhin.js": "^1.1.
|
|
82
|
+
"@zhin.js/middleware": "^1.1.10",
|
|
83
|
+
"zhin.js": "^1.1.16"
|
|
84
84
|
},
|
|
85
85
|
"peerDependenciesMeta": {
|
|
86
86
|
"@zhin.js/middleware": {
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createMessages } from 'zhin.js/i18n';
|
|
2
|
+
|
|
3
|
+
export const messages = createMessages({
|
|
4
|
+
views: { 'zh-CN': '播放', en: 'Views' },
|
|
5
|
+
danmaku: { 'zh-CN': '弹幕', en: 'Danmaku' },
|
|
6
|
+
likes: { 'zh-CN': '点赞', en: 'Likes' },
|
|
7
|
+
status: { 'zh-CN': '状态', en: 'Status' },
|
|
8
|
+
comments: { 'zh-CN': '评论', en: 'Comments' },
|
|
9
|
+
language: { 'zh-CN': '语言', en: 'Language' },
|
|
10
|
+
douyin: { 'zh-CN': '抖音', en: 'Douyin' },
|
|
11
|
+
xiaohongshu: { 'zh-CN': '小红书', en: 'Xiaohongshu' },
|
|
12
|
+
});
|
package/src/platforms.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { LinkMeta } from './types.js'
|
|
2
|
+
import { resolveLocale, type Locale } from 'zhin.js/i18n'
|
|
3
|
+
import { messages as t } from './i18n.js'
|
|
2
4
|
|
|
3
5
|
// ── 工具函数 ──────────────────────────────────────────────────────────────────
|
|
4
6
|
|
|
@@ -32,8 +34,9 @@ async function imageToDataUri(url: string | undefined): Promise<string | undefin
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
function formatNumber(n: number | undefined): string {
|
|
37
|
+
function formatNumber(n: number | undefined, locale?: Locale): string {
|
|
36
38
|
if (n == null) return '-'
|
|
39
|
+
if (resolveLocale(locale) === 'en') return new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(n)
|
|
37
40
|
if (n >= 100_000_000) return (n / 100_000_000).toFixed(1) + '亿'
|
|
38
41
|
if (n >= 10_000) return (n / 10_000).toFixed(1) + '万'
|
|
39
42
|
return String(n)
|
|
@@ -59,7 +62,7 @@ function extractMeta(html: string, property: string): string | undefined {
|
|
|
59
62
|
|
|
60
63
|
const BILIBILI_RE = /(?:bilibili\.com\/video\/|b23\.tv\/)/i
|
|
61
64
|
|
|
62
|
-
async function parseBilibili(url: string): Promise<LinkMeta | null> {
|
|
65
|
+
async function parseBilibili(url: string, locale?: Locale): Promise<LinkMeta | null> {
|
|
63
66
|
// 短链跳转
|
|
64
67
|
if (url.includes('b23.tv')) {
|
|
65
68
|
const res = await fetchWithTimeout(url, { redirect: 'follow' })
|
|
@@ -85,9 +88,9 @@ async function parseBilibili(url: string): Promise<LinkMeta | null> {
|
|
|
85
88
|
cover: await imageToDataUri(d.pic),
|
|
86
89
|
url: `https://www.bilibili.com/video/${bvMatch[0]}`,
|
|
87
90
|
stats: {
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
+
[t(locale, 'views')]: formatNumber(d.stat?.view, locale),
|
|
92
|
+
[t(locale, 'danmaku')]: formatNumber(d.stat?.danmaku, locale),
|
|
93
|
+
[t(locale, 'likes')]: formatNumber(d.stat?.like, locale),
|
|
91
94
|
},
|
|
92
95
|
}
|
|
93
96
|
}
|
|
@@ -96,7 +99,7 @@ async function parseBilibili(url: string): Promise<LinkMeta | null> {
|
|
|
96
99
|
|
|
97
100
|
const GITHUB_RE = /github\.com\/([\w.-]+)\/([\w.-]+)/i
|
|
98
101
|
|
|
99
|
-
async function parseGithub(url: string): Promise<LinkMeta | null> {
|
|
102
|
+
async function parseGithub(url: string, locale?: Locale): Promise<LinkMeta | null> {
|
|
100
103
|
const repoMatch = url.match(GITHUB_RE)
|
|
101
104
|
if (!repoMatch) return null
|
|
102
105
|
const [, owner, repo] = repoMatch
|
|
@@ -121,8 +124,8 @@ async function parseGithub(url: string): Promise<LinkMeta | null> {
|
|
|
121
124
|
authorAvatar: await imageToDataUri(d.user?.avatar_url),
|
|
122
125
|
url,
|
|
123
126
|
stats: {
|
|
124
|
-
'
|
|
125
|
-
'
|
|
127
|
+
[t(locale, 'status')]: d.state === 'open' ? '🟢 Open' : '🟣 Closed',
|
|
128
|
+
[t(locale, 'comments')]: String(d.comments ?? 0),
|
|
126
129
|
},
|
|
127
130
|
}
|
|
128
131
|
}
|
|
@@ -140,9 +143,9 @@ async function parseGithub(url: string): Promise<LinkMeta | null> {
|
|
|
140
143
|
cover: await imageToDataUri(`https://opengraph.githubassets.com/1/${owner}/${repo}`),
|
|
141
144
|
url,
|
|
142
145
|
stats: {
|
|
143
|
-
'Star': formatNumber(d.stargazers_count),
|
|
144
|
-
'Fork': formatNumber(d.forks_count),
|
|
145
|
-
'
|
|
146
|
+
'Star': formatNumber(d.stargazers_count, locale),
|
|
147
|
+
'Fork': formatNumber(d.forks_count, locale),
|
|
148
|
+
[t(locale, 'language')]: d.language || '-',
|
|
146
149
|
},
|
|
147
150
|
}
|
|
148
151
|
}
|
|
@@ -151,7 +154,7 @@ async function parseGithub(url: string): Promise<LinkMeta | null> {
|
|
|
151
154
|
|
|
152
155
|
const DOUYIN_RE = /(?:douyin\.com\/video\/|v\.douyin\.com\/)/i
|
|
153
156
|
|
|
154
|
-
async function parseDouyin(url: string): Promise<LinkMeta | null> {
|
|
157
|
+
async function parseDouyin(url: string, locale?: Locale): Promise<LinkMeta | null> {
|
|
155
158
|
try {
|
|
156
159
|
const res = await fetchWithTimeout(url, {
|
|
157
160
|
headers: {
|
|
@@ -170,7 +173,7 @@ async function parseDouyin(url: string): Promise<LinkMeta | null> {
|
|
|
170
173
|
description: extractMeta(html, 'og:description'),
|
|
171
174
|
cover: await imageToDataUri(extractMeta(html, 'og:image')),
|
|
172
175
|
url: res.url || url,
|
|
173
|
-
author: extractMeta(html, 'og:site_name') || '
|
|
176
|
+
author: extractMeta(html, 'og:site_name') || t(locale, 'douyin'),
|
|
174
177
|
}
|
|
175
178
|
} catch {
|
|
176
179
|
return null
|
|
@@ -181,7 +184,7 @@ async function parseDouyin(url: string): Promise<LinkMeta | null> {
|
|
|
181
184
|
|
|
182
185
|
const XIAOHONGSHU_RE = /(?:xiaohongshu\.com\/(?:explore|discovery)|xhslink\.com\/)/i
|
|
183
186
|
|
|
184
|
-
async function parseXiaohongshu(url: string): Promise<LinkMeta | null> {
|
|
187
|
+
async function parseXiaohongshu(url: string, locale?: Locale): Promise<LinkMeta | null> {
|
|
185
188
|
try {
|
|
186
189
|
const res = await fetchWithTimeout(url, {
|
|
187
190
|
headers: {
|
|
@@ -200,7 +203,7 @@ async function parseXiaohongshu(url: string): Promise<LinkMeta | null> {
|
|
|
200
203
|
description: extractMeta(html, 'og:description'),
|
|
201
204
|
cover: await imageToDataUri(extractMeta(html, 'og:image')),
|
|
202
205
|
url: res.url || url,
|
|
203
|
-
author: extractMeta(html, 'author') || extractMeta(html, 'og:site_name') || '
|
|
206
|
+
author: extractMeta(html, 'author') || extractMeta(html, 'og:site_name') || t(locale, 'xiaohongshu'),
|
|
204
207
|
}
|
|
205
208
|
} catch {
|
|
206
209
|
return null
|
|
@@ -211,7 +214,7 @@ async function parseXiaohongshu(url: string): Promise<LinkMeta | null> {
|
|
|
211
214
|
|
|
212
215
|
interface PlatformParser {
|
|
213
216
|
test: RegExp
|
|
214
|
-
parse: (url: string) => Promise<LinkMeta | null>
|
|
217
|
+
parse: (url: string, locale?: Locale) => Promise<LinkMeta | null>
|
|
215
218
|
}
|
|
216
219
|
|
|
217
220
|
const PARSERS: PlatformParser[] = [
|
|
@@ -225,7 +228,7 @@ const PARSERS: PlatformParser[] = [
|
|
|
225
228
|
* 从消息文本中检测第一个匹配的平台链接并解析元数据。
|
|
226
229
|
* 仅返回第一个命中结果,避免单条消息产生过多海报。
|
|
227
230
|
*/
|
|
228
|
-
export async function detectAndParse(text: string): Promise<LinkMeta | null> {
|
|
231
|
+
export async function detectAndParse(text: string, locale?: Locale): Promise<LinkMeta | null> {
|
|
229
232
|
// 提取所有 URL
|
|
230
233
|
const urls = text.match(/https?:\/\/[^\s<>)"']+/gi)
|
|
231
234
|
if (!urls) return null
|
|
@@ -233,7 +236,7 @@ export async function detectAndParse(text: string): Promise<LinkMeta | null> {
|
|
|
233
236
|
for (const url of urls) {
|
|
234
237
|
for (const { test, parse } of PARSERS) {
|
|
235
238
|
if (test.test(url)) {
|
|
236
|
-
const meta = await parse(url)
|
|
239
|
+
const meta = await parse(url, locale)
|
|
237
240
|
if (meta) return meta
|
|
238
241
|
}
|
|
239
242
|
}
|
package/src/render.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { LinkMeta, PlatformTheme } from './types.js'
|
|
2
|
+
import type { Locale } from 'zhin.js/i18n'
|
|
3
|
+
import { messages as t } from './i18n.js'
|
|
2
4
|
|
|
3
5
|
const THEMES: Record<LinkMeta['platform'], PlatformTheme> = {
|
|
4
6
|
bilibili: { name: 'Bilibili', color: '#00A1D6', bg: '#E3F6FC' },
|
|
@@ -37,8 +39,9 @@ function renderStats(stats: Record<string, string> | undefined, color: string):
|
|
|
37
39
|
/**
|
|
38
40
|
* 生成 satori 兼容的海报 HTML
|
|
39
41
|
*/
|
|
40
|
-
export function renderPoster(meta: LinkMeta): string {
|
|
41
|
-
const theme = THEMES[meta.platform]
|
|
42
|
+
export function renderPoster(meta: LinkMeta, locale?: Locale): string {
|
|
43
|
+
const theme = { ...THEMES[meta.platform], ...(meta.platform === 'douyin' || meta.platform === 'xiaohongshu'
|
|
44
|
+
? { name: t(locale, meta.platform) } : {}) }
|
|
42
45
|
|
|
43
46
|
// ── 封面区域 ──
|
|
44
47
|
const coverHtml = meta.cover
|