@zhin.js/plugin-60s 1.0.1 → 1.1.0
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/README.md +1 -1
- package/agent/tools/60s_news.ts +13 -0
- package/agent/tools/bing_image.ts +13 -0
- package/agent/tools/douyin_hot.ts +13 -0
- package/agent/tools/duanzi.ts +13 -0
- package/agent/tools/exchange_rate.ts +13 -0
- package/agent/tools/fuel_price.ts +13 -0
- package/agent/tools/gold_price.ts +13 -0
- package/agent/tools/history_today.ts +13 -0
- package/agent/tools/hitokoto.ts +13 -0
- package/agent/tools/ip_query.ts +13 -0
- package/agent/tools/kfc.ts +13 -0
- package/agent/tools/moyu.ts +13 -0
- package/agent/tools/toutiao_hot.ts +13 -0
- package/agent/tools/translate_60s.ts +13 -0
- package/agent/tools/weather.ts +13 -0
- package/agent/tools/weibo_hot.ts +13 -0
- package/agent/tools/zhihu_hot.ts +13 -0
- package/commands/60s.js +7 -0
- package/commands/60s.ts +7 -0
- package/commands/bing.js +7 -0
- package/commands/bing.ts +7 -0
- package/commands/douyin/[[limit]].js +8 -0
- package/commands/douyin/[[limit]].ts +8 -0
- package/commands/duanzi.js +7 -0
- package/commands/duanzi.ts +7 -0
- package/commands/exchange.js +10 -0
- package/commands/exchange.ts +10 -0
- package/commands/fanyi/[text].js +8 -0
- package/commands/fanyi/[text].ts +8 -0
- package/commands/fuel/[province].js +8 -0
- package/commands/fuel/[province].ts +8 -0
- package/commands/gold.js +7 -0
- package/commands/gold.ts +7 -0
- package/commands/history.js +7 -0
- package/commands/history.ts +7 -0
- package/commands/hitokoto/[type].js +8 -0
- package/commands/hitokoto/[type].ts +8 -0
- package/commands/ip/[ip].js +8 -0
- package/commands/ip/[ip].ts +8 -0
- package/commands/kfc.js +7 -0
- package/commands/kfc.ts +7 -0
- package/commands/moyu.js +7 -0
- package/commands/moyu.ts +7 -0
- package/commands/toutiao/[[limit]].js +8 -0
- package/commands/toutiao/[[limit]].ts +8 -0
- package/commands/weather/[city].js +8 -0
- package/commands/weather/[city].ts +8 -0
- package/commands/weibo/[[limit]].js +8 -0
- package/commands/weibo/[[limit]].ts +8 -0
- package/commands/zhihu/[[limit]].js +8 -0
- package/commands/zhihu/[[limit]].ts +8 -0
- package/lib/api.js +59 -0
- package/lib/api.js.map +1 -0
- package/lib/handlers/60s-news.js +12 -0
- package/lib/handlers/60s-news.js.map +1 -0
- package/lib/handlers/bing-image.js +18 -0
- package/lib/handlers/bing-image.js.map +1 -0
- package/lib/handlers/douyin-hot.js +6 -0
- package/lib/handlers/douyin-hot.js.map +1 -0
- package/lib/handlers/duanzi.js +6 -0
- package/lib/handlers/duanzi.js.map +1 -0
- package/lib/handlers/exchange-rate.js +28 -0
- package/lib/handlers/exchange-rate.js.map +1 -0
- package/lib/handlers/fuel-price.js +21 -0
- package/lib/handlers/fuel-price.js.map +1 -0
- package/lib/handlers/gold-price.js +24 -0
- package/lib/handlers/gold-price.js.map +1 -0
- package/lib/handlers/history-today.js +16 -0
- package/lib/handlers/history-today.js.map +1 -0
- package/lib/handlers/hitokoto.js +16 -0
- package/lib/handlers/hitokoto.js.map +1 -0
- package/lib/handlers/ip-query.js +17 -0
- package/lib/handlers/ip-query.js.map +1 -0
- package/lib/handlers/kfc.js +6 -0
- package/lib/handlers/kfc.js.map +1 -0
- package/lib/handlers/moyu.js +39 -0
- package/lib/handlers/moyu.js.map +1 -0
- package/lib/handlers/toutiao-hot.js +6 -0
- package/lib/handlers/toutiao-hot.js.map +1 -0
- package/lib/handlers/translate.js +26 -0
- package/lib/handlers/translate.js.map +1 -0
- package/lib/handlers/weather.js +22 -0
- package/lib/handlers/weather.js.map +1 -0
- package/lib/handlers/weibo-hot.js +6 -0
- package/lib/handlers/weibo-hot.js.map +1 -0
- package/lib/handlers/zhihu-hot.js +6 -0
- package/lib/handlers/zhihu-hot.js.map +1 -0
- package/lib/index.js +1 -34
- package/lib/index.js.map +1 -1
- package/lib/runtime-deps.js +22 -0
- package/lib/runtime-deps.js.map +1 -0
- package/package.json +52 -12
- package/plugin.js +13 -0
- package/schema.json +12 -0
- package/src/api.ts +79 -0
- package/{tools/60s-news/handler.ts → src/handlers/60s-news.ts} +1 -1
- package/{tools/bing-image/handler.ts → src/handlers/bing-image.ts} +4 -4
- package/{tools/douyin-hot/handler.ts → src/handlers/douyin-hot.ts} +2 -2
- package/{tools/duanzi/handler.ts → src/handlers/duanzi.ts} +1 -1
- package/{tools/exchange-rate/handler.ts → src/handlers/exchange-rate.ts} +8 -6
- package/{tools/fuel-price/handler.ts → src/handlers/fuel-price.ts} +5 -4
- package/{tools/gold-price/handler.ts → src/handlers/gold-price.ts} +8 -7
- package/src/handlers/history-today.ts +16 -0
- package/{tools/hitokoto/handler.ts → src/handlers/hitokoto.ts} +1 -1
- package/{tools/ip-query/handler.ts → src/handlers/ip-query.ts} +1 -1
- package/{tools/kfc/handler.ts → src/handlers/kfc.ts} +1 -1
- package/src/handlers/moyu.ts +35 -0
- package/{tools/toutiao-hot/handler.ts → src/handlers/toutiao-hot.ts} +2 -2
- package/src/handlers/translate.ts +25 -0
- package/src/handlers/weather.ts +22 -0
- package/{tools/weibo-hot/handler.ts → src/handlers/weibo-hot.ts} +2 -2
- package/{tools/zhihu-hot/handler.ts → src/handlers/zhihu-hot.ts} +2 -2
- package/src/index.ts +8 -44
- package/src/runtime-deps.ts +26 -0
- package/lib/index.d.ts +0 -2
- package/lib/index.d.ts.map +0 -1
- package/plugin.yml +0 -3
- package/tools/60s-news/60s-news.tool.md +0 -8
- package/tools/api.ts +0 -34
- package/tools/bing-image/bing-image.tool.md +0 -8
- package/tools/douyin-hot/douyin-hot.tool.md +0 -12
- package/tools/duanzi/duanzi.tool.md +0 -8
- package/tools/exchange-rate/exchange-rate.tool.md +0 -14
- package/tools/fuel-price/fuel-price.tool.md +0 -11
- package/tools/gold-price/gold-price.tool.md +0 -8
- package/tools/history-today/handler.ts +0 -15
- package/tools/history-today/history-today.tool.md +0 -8
- package/tools/hitokoto/hitokoto.tool.md +0 -12
- package/tools/ip-query/ip-query.tool.md +0 -12
- package/tools/kfc/kfc.tool.md +0 -8
- package/tools/moyu/handler.ts +0 -28
- package/tools/moyu/moyu.tool.md +0 -8
- package/tools/toutiao-hot/toutiao-hot.tool.md +0 -12
- package/tools/translate/handler.ts +0 -19
- package/tools/translate/translate.tool.md +0 -15
- package/tools/weather/handler.ts +0 -22
- package/tools/weather/weather.tool.md +0 -12
- package/tools/weibo-hot/weibo-hot.tool.md +0 -12
- package/tools/zhihu-hot/zhihu-hot.tool.md +0 -12
- /package/{skills/60s/SKILL.md → agent/skills/60s.md} +0 -0
package/plugin.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { definePlugin } from 'zhin.js';
|
|
3
|
+
import { DEFAULT_API_BASE, registerSixtySApiBase } from "./lib/runtime-deps.js";
|
|
4
|
+
export default definePlugin({
|
|
5
|
+
name: 'sixty-s',
|
|
6
|
+
metadata: {
|
|
7
|
+
displayName: '60s API',
|
|
8
|
+
},
|
|
9
|
+
setup(context) {
|
|
10
|
+
// 运行时读取配置:config patch 即时生效;卸载时 lifecycle 反注册,无 process.env 残留
|
|
11
|
+
context.lifecycle.add(registerSixtySApiBase(() => context.config.get().apiBase?.trim() || DEFAULT_API_BASE));
|
|
12
|
+
},
|
|
13
|
+
});
|
package/schema.json
ADDED
package/src/api.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 60s API 共享模块 — 供 *.tool.md handler 使用
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { resolveApiBase } from './runtime-deps.js';
|
|
6
|
+
|
|
7
|
+
export type ListItem = string | Record<string, unknown>;
|
|
8
|
+
|
|
9
|
+
/** Untyped 60s API JSON payload (handlers narrow fields at runtime). */
|
|
10
|
+
export type ApiPayload = Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
export async function fetchApi<T = ApiPayload>(
|
|
13
|
+
endpoint: string,
|
|
14
|
+
params?: Record<string, string>,
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
const url = new URL(`${resolveApiBase()}/v2${endpoint}`);
|
|
17
|
+
if (params) {
|
|
18
|
+
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
19
|
+
}
|
|
20
|
+
let res: Response;
|
|
21
|
+
try {
|
|
22
|
+
res = await fetch(url.toString(), { signal: AbortSignal.timeout(30_000) });
|
|
23
|
+
} catch (e) {
|
|
24
|
+
const err = e as Error;
|
|
25
|
+
throw new Error(
|
|
26
|
+
err.name === 'TimeoutError' ? '请求超时(30s)' : `请求失败: ${err.message}`,
|
|
27
|
+
{ cause: e },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
throw new Error(`API 请求失败: ${res.status} ${res.statusText}`);
|
|
32
|
+
}
|
|
33
|
+
const contentType = res.headers.get('content-type') ?? '';
|
|
34
|
+
if (!contentType.includes('application/json')) {
|
|
35
|
+
throw new Error(`API 返回非 JSON 响应 (content-type: ${contentType || 'unknown'})`);
|
|
36
|
+
}
|
|
37
|
+
const data = (await res.json()) as {
|
|
38
|
+
error?: string;
|
|
39
|
+
code?: number;
|
|
40
|
+
message?: string;
|
|
41
|
+
msg?: string;
|
|
42
|
+
data?: T;
|
|
43
|
+
};
|
|
44
|
+
if (data.error) throw new Error(data.error);
|
|
45
|
+
if (data.code !== undefined && data.code !== 200 && data.code !== 0) {
|
|
46
|
+
throw new Error(data.message || data.msg || `API 错误: ${data.code}`);
|
|
47
|
+
}
|
|
48
|
+
return (data.data ?? data) as T;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Coerce nested API JSON objects for property access in handlers. */
|
|
52
|
+
export function asRecord(value: unknown): ApiPayload {
|
|
53
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
54
|
+
? (value as ApiPayload)
|
|
55
|
+
: {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function asString(value: unknown): string {
|
|
59
|
+
return value == null ? '' : String(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function asArray(value: unknown): unknown[] {
|
|
63
|
+
return Array.isArray(value) ? value : [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function formatList(items: ListItem[], limit = 10): string {
|
|
67
|
+
return items
|
|
68
|
+
.slice(0, limit)
|
|
69
|
+
.map((item, i) => {
|
|
70
|
+
const obj = typeof item === 'string' ? null : item;
|
|
71
|
+
const title = obj
|
|
72
|
+
? String(obj.title ?? obj.name ?? obj.word ?? item)
|
|
73
|
+
: item;
|
|
74
|
+
const hot = obj ? obj.hot_value ?? obj.hot : undefined;
|
|
75
|
+
const hotStr = hot ? ` 🔥${hot}` : '';
|
|
76
|
+
return `${i + 1}. ${title}${hotStr}`;
|
|
77
|
+
})
|
|
78
|
+
.join('\n');
|
|
79
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fetchApi } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function () {
|
|
4
|
-
const data = await fetchApi
|
|
4
|
+
const data = await fetchApi('/60s');
|
|
5
5
|
const lines = [`📰 今日60秒新闻 (${data.date || ''})`, ''];
|
|
6
6
|
if (data.news && Array.isArray(data.news)) {
|
|
7
7
|
lines.push(...data.news.map((n: string, i: number) => `${i + 1}. ${n}`));
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { fetchApi } from '../api.js';
|
|
1
|
+
import { asString, fetchApi } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function () {
|
|
4
|
-
const data = await fetchApi
|
|
4
|
+
const data = await fetchApi('/bing');
|
|
5
5
|
const lines = ['🖼️ Bing 每日壁纸', ''];
|
|
6
6
|
if (data.title) lines.push(`📌 ${data.title}`);
|
|
7
7
|
if (data.headline) lines.push(`💡 ${data.headline}`);
|
|
8
8
|
if (data.copyright) lines.push(`📝 ${data.copyright}`);
|
|
9
|
-
if (data.description) lines.push('', data.description);
|
|
9
|
+
if (data.description) lines.push('', asString(data.description));
|
|
10
10
|
const imgUrl = data.cover || data.cover_4k || data.url || data.image;
|
|
11
|
-
if (imgUrl) lines.push('', imgUrl);
|
|
11
|
+
if (imgUrl) lines.push('', asString(imgUrl));
|
|
12
12
|
return lines.join('\n');
|
|
13
13
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fetchApi, formatList } from '../api.js';
|
|
1
|
+
import { fetchApi, formatList, type ListItem } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function (args: { limit?: number }) {
|
|
4
|
-
const data = await fetchApi<
|
|
4
|
+
const data = await fetchApi<ListItem[]>('/douyin');
|
|
5
5
|
return ['🔥 抖音热搜', '', formatList(data, args.limit || 10)].join('\n');
|
|
6
6
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchApi } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function () {
|
|
4
|
-
const data = await fetchApi
|
|
4
|
+
const data = await fetchApi('/duanzi');
|
|
5
5
|
return `😂 段子\n\n${data.duanzi || data.content || data.text || (typeof data === 'string' ? data : '')}`;
|
|
6
6
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { fetchApi } from '../api.js';
|
|
1
|
+
import { asArray, asRecord, asString, fetchApi } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function (args: { from?: string; to?: string }) {
|
|
4
4
|
const params: Record<string, string> = {};
|
|
5
5
|
if (args.from) params.from = args.from.toUpperCase();
|
|
6
6
|
if (args.to) params.to = args.to.toUpperCase();
|
|
7
|
-
const data = await fetchApi
|
|
7
|
+
const data = await fetchApi(
|
|
8
8
|
'/exchange-rate',
|
|
9
9
|
Object.keys(params).length ? params : undefined,
|
|
10
10
|
);
|
|
@@ -13,13 +13,15 @@ export default async function (args: { from?: string; to?: string }) {
|
|
|
13
13
|
lines.push(`基准货币: ${base}`);
|
|
14
14
|
if (data.updated) lines.push(`更新时间: ${data.updated}`);
|
|
15
15
|
lines.push('');
|
|
16
|
-
const rates = data.rates
|
|
16
|
+
const rates = asArray(data.rates);
|
|
17
17
|
const targetCurrencies = args.to
|
|
18
18
|
? [args.to.toUpperCase()]
|
|
19
19
|
: ['USD', 'EUR', 'JPY', 'GBP', 'HKD', 'KRW', 'AUD', 'CAD', 'SGD', 'CHF'];
|
|
20
|
-
rates.forEach((item
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
rates.forEach((item) => {
|
|
21
|
+
const row = asRecord(item);
|
|
22
|
+
const currency = asString(row.currency);
|
|
23
|
+
if (targetCurrencies.includes(currency) && currency !== base) {
|
|
24
|
+
lines.push(`${base} → ${currency}: ${row.rate}`);
|
|
23
25
|
}
|
|
24
26
|
});
|
|
25
27
|
return lines.join('\n');
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { fetchApi } from '../api.js';
|
|
1
|
+
import { asRecord, asString, fetchApi } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function (args: { province?: string }) {
|
|
4
4
|
const params: Record<string, string> | undefined = args.province
|
|
5
5
|
? { province: args.province }
|
|
6
6
|
: undefined;
|
|
7
|
-
const data = await fetchApi
|
|
7
|
+
const data = await fetchApi('/fuel-price', params);
|
|
8
8
|
const lines = ['⛽ 今日油价', ''];
|
|
9
9
|
if (data.region) lines.push(`📍 ${data.region}`);
|
|
10
10
|
if (data.items && Array.isArray(data.items)) {
|
|
11
|
-
data.items.forEach((item:
|
|
11
|
+
data.items.forEach((item: Record<string, unknown>) => {
|
|
12
12
|
lines.push(`${item.name}: ${item.price_desc || `¥${item.price}/升`}`);
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
15
|
if (data.trend) {
|
|
16
|
-
|
|
16
|
+
const trend = asRecord(data.trend);
|
|
17
|
+
lines.push('', `📊 ${asString(trend.description)}`);
|
|
17
18
|
}
|
|
18
19
|
return lines.join('\n');
|
|
19
20
|
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { fetchApi } from '../api.js';
|
|
1
|
+
import { asArray, asRecord, fetchApi } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function () {
|
|
4
|
-
const data = await fetchApi
|
|
4
|
+
const data = await fetchApi('/gold-price');
|
|
5
5
|
const lines = ['💰 今日金价', ''];
|
|
6
6
|
if (data.date) lines.push(`📅 ${data.date}`);
|
|
7
|
-
const metals = data.metals
|
|
7
|
+
const metals = asArray(data.metals);
|
|
8
8
|
if (metals.length > 0) {
|
|
9
9
|
lines.push('');
|
|
10
|
-
metals.forEach((item
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
10
|
+
metals.forEach((item) => {
|
|
11
|
+
const row = asRecord(item);
|
|
12
|
+
const name = row.name || '黄金';
|
|
13
|
+
const price = row.today_price || row.sell_price || row.price || 'N/A';
|
|
14
|
+
const unit = row.unit || '元/克';
|
|
14
15
|
if (price !== 'N/A') lines.push(`${name}: ¥${price} ${unit}`);
|
|
15
16
|
});
|
|
16
17
|
} else if (data.price) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { asArray, asRecord, fetchApi } from '../api.js';
|
|
2
|
+
|
|
3
|
+
export default async function () {
|
|
4
|
+
const data = await fetchApi('/today-in-history');
|
|
5
|
+
const dateStr = data.date || `${data.month || ''}月${data.day || ''}日`;
|
|
6
|
+
const lines = [`📅 历史上的今天 (${dateStr})`, ''];
|
|
7
|
+
const items = asArray(data.items);
|
|
8
|
+
items.slice(0, 10).forEach((item, i: number) => {
|
|
9
|
+
const row = asRecord(item);
|
|
10
|
+
const year = row.year || '';
|
|
11
|
+
const title = row.title || row.event || row.content || item;
|
|
12
|
+
const typeIcon = row.event_type === 'birth' ? '👶' : row.event_type === 'death' ? '🕊️' : '📌';
|
|
13
|
+
lines.push(`${typeIcon} ${i + 1}. ${year ? `[${year}] ` : ''}${title}`);
|
|
14
|
+
});
|
|
15
|
+
return lines.join('\n');
|
|
16
|
+
}
|
|
@@ -4,7 +4,7 @@ export default async function (args: { type?: string }) {
|
|
|
4
4
|
const params: Record<string, string> | undefined = args.type
|
|
5
5
|
? { c: args.type }
|
|
6
6
|
: undefined;
|
|
7
|
-
const data = await fetchApi
|
|
7
|
+
const data = await fetchApi('/hitokoto', params);
|
|
8
8
|
const text = data.hitokoto || data.content || (typeof data === 'string' ? data : '');
|
|
9
9
|
const lines = ['💬 一言', '', `「${text}」`];
|
|
10
10
|
if (data.from || data.source || data.from_who || data.author) {
|
|
@@ -4,7 +4,7 @@ export default async function (args: { ip?: string }) {
|
|
|
4
4
|
const params: Record<string, string> | undefined = args.ip
|
|
5
5
|
? { ip: args.ip }
|
|
6
6
|
: undefined;
|
|
7
|
-
const data = await fetchApi
|
|
7
|
+
const data = await fetchApi('/ip', params);
|
|
8
8
|
const lines = ['🌐 IP 查询', '', `IP: ${data.ip || args.ip || '当前 IP'}`];
|
|
9
9
|
if (data.country || data.region || data.city) {
|
|
10
10
|
lines.push(`位置: ${[data.country, data.region, data.city].filter(Boolean).join(' ')}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchApi } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function () {
|
|
4
|
-
const data = await fetchApi
|
|
4
|
+
const data = await fetchApi('/kfc');
|
|
5
5
|
return `🍗 疯狂星期四\n\n${data.kfc || data.content || data.text || (typeof data === 'string' ? data : '')}`;
|
|
6
6
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { asArray, asRecord, asString, fetchApi, type ApiPayload } from '../api.js';
|
|
2
|
+
|
|
3
|
+
export default async function () {
|
|
4
|
+
const data = await fetchApi<ApiPayload | string>('/moyu');
|
|
5
|
+
if (typeof data === 'string') return `🐟 摸鱼日历\n\n${data}`;
|
|
6
|
+
const lines = ['🐟 摸鱼日历', ''];
|
|
7
|
+
const date = asRecord(data.date);
|
|
8
|
+
if (date.gregorian) lines.push(`📅 ${date.gregorian} ${asString(date.weekday)}`);
|
|
9
|
+
const lunar = asRecord(date.lunar);
|
|
10
|
+
if (lunar.monthCN || lunar.dayCN) {
|
|
11
|
+
lines.push(`🌙 农历${asString(lunar.monthCN)}${asString(lunar.dayCN)} ${asString(lunar.yearGanZhi)}年`);
|
|
12
|
+
}
|
|
13
|
+
const today = asRecord(data.today);
|
|
14
|
+
if (today.holidayName) lines.push(`🎉 ${today.holidayName}`);
|
|
15
|
+
if (today.solarTerm) lines.push(`🌿 ${today.solarTerm}`);
|
|
16
|
+
if (today.isWorkday !== undefined) {
|
|
17
|
+
lines.push(today.isWorkday ? '💼 今天是工作日' : '🎮 今天不上班');
|
|
18
|
+
}
|
|
19
|
+
if (Array.isArray(data.festivals)) {
|
|
20
|
+
data.festivals.forEach((f: unknown) => {
|
|
21
|
+
lines.push(`🎊 ${typeof f === 'string' ? f : asString(asRecord(f).name ?? f)}`);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const countdown = asArray(data.countdown);
|
|
25
|
+
if (countdown.length > 0) {
|
|
26
|
+
lines.push('');
|
|
27
|
+
countdown.forEach((c: unknown) => {
|
|
28
|
+
const row = asRecord(c);
|
|
29
|
+
if (typeof row.name === 'string' && typeof row.days === 'number') {
|
|
30
|
+
lines.push(`⏳ 距 ${row.name} 还有 ${row.days} 天`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return lines.join('\n');
|
|
35
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fetchApi, formatList } from '../api.js';
|
|
1
|
+
import { fetchApi, formatList, type ListItem } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function (args: { limit?: number }) {
|
|
4
|
-
const data = await fetchApi<
|
|
4
|
+
const data = await fetchApi<ListItem[]>('/toutiao');
|
|
5
5
|
return ['🔥 头条热搜', '', formatList(data, args.limit || 10)].join('\n');
|
|
6
6
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { asRecord, asString, fetchApi } from '../api.js';
|
|
2
|
+
|
|
3
|
+
export default async function (args: { text: string; to?: string }) {
|
|
4
|
+
const params: Record<string, string> = { text: args.text };
|
|
5
|
+
if (args.to) params.to = args.to;
|
|
6
|
+
const data = await fetchApi('/fanyi', params);
|
|
7
|
+
const source = asRecord(data.source);
|
|
8
|
+
const target = asRecord(data.target);
|
|
9
|
+
const srcText = asString(source.text) || args.text;
|
|
10
|
+
const srcLang = asString(source.type_desc) || asString(source.type);
|
|
11
|
+
const tgtText =
|
|
12
|
+
asString(target.text) ||
|
|
13
|
+
asString(data.result) ||
|
|
14
|
+
asString(data.translation) ||
|
|
15
|
+
(typeof data === 'string' ? data : '');
|
|
16
|
+
const tgtLang = asString(target.type_desc) || asString(target.type);
|
|
17
|
+
const lines = [
|
|
18
|
+
'🌐 翻译结果',
|
|
19
|
+
'',
|
|
20
|
+
`原文${srcLang ? ` (${srcLang})` : ''}: ${srcText}`,
|
|
21
|
+
`译文${tgtLang ? ` (${tgtLang})` : ''}: ${tgtText}`,
|
|
22
|
+
];
|
|
23
|
+
if (target.pronounce) lines.push(`发音: ${asString(target.pronounce)}`);
|
|
24
|
+
return lines.join('\n');
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { asRecord, asString, fetchApi } from '../api.js';
|
|
2
|
+
|
|
3
|
+
export default async function (args: { city: string }) {
|
|
4
|
+
const data = await fetchApi('/weather', { query: args.city });
|
|
5
|
+
const w = asRecord(data.weather);
|
|
6
|
+
const aq = asRecord(data.air_quality);
|
|
7
|
+
const loc = asRecord(data.location);
|
|
8
|
+
const cityName = asString(loc.city) || asString(loc.name) || args.city;
|
|
9
|
+
const lines = [
|
|
10
|
+
`🌤️ ${cityName} 天气`,
|
|
11
|
+
'',
|
|
12
|
+
`🌡️ 温度: ${asString(w.temperature)}°C`,
|
|
13
|
+
`☁️ 天气: ${asString(w.condition)}`,
|
|
14
|
+
`💧 湿度: ${asString(w.humidity)}%`,
|
|
15
|
+
`💨 风: ${asString(w.wind_direction)} ${asString(w.wind_power)}`,
|
|
16
|
+
];
|
|
17
|
+
if (Object.keys(aq).length > 0) {
|
|
18
|
+
const quality = asString(aq.quality);
|
|
19
|
+
lines.push(`🌬️ 空气: ${quality}${aq.aqi ? ` (AQI ${asString(aq.aqi)})` : ''}`);
|
|
20
|
+
}
|
|
21
|
+
return lines.join('\n');
|
|
22
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fetchApi, formatList } from '../api.js';
|
|
1
|
+
import { fetchApi, formatList, type ListItem } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function (args: { limit?: number }) {
|
|
4
|
-
const data = await fetchApi<
|
|
4
|
+
const data = await fetchApi<ListItem[]>('/weibo');
|
|
5
5
|
return ['🔥 微博热搜', '', formatList(data, args.limit || 10)].join('\n');
|
|
6
6
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fetchApi, formatList } from '../api.js';
|
|
1
|
+
import { fetchApi, formatList, type ListItem } from '../api.js';
|
|
2
2
|
|
|
3
3
|
export default async function (args: { limit?: number }) {
|
|
4
|
-
const data = await fetchApi<
|
|
4
|
+
const data = await fetchApi<ListItem[]>('/zhihu');
|
|
5
5
|
return ['🔥 知乎热榜', '', formatList(data, args.limit || 10)].join('\n');
|
|
6
6
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,44 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
* 功能列表:
|
|
11
|
-
* 60s新闻 · 天气 · 微博热搜 · 知乎热榜 · 抖音热搜 · 头条热搜
|
|
12
|
-
* 一言 · 摸鱼日历 · IP查询 · Bing壁纸 · 金价 · 油价
|
|
13
|
-
* 汇率 · 翻译 · 历史上的今天 · KFC文案 · 段子
|
|
14
|
-
*
|
|
15
|
-
* 所有工具定义在 tools/ 目录下,框架自动发现注册。
|
|
16
|
-
*
|
|
17
|
-
* 配置方式(zhin.config.yml):
|
|
18
|
-
* ```yaml
|
|
19
|
-
* plugins:
|
|
20
|
-
* 60s:
|
|
21
|
-
* apiBase: https://60s.viki.moe
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
import { usePlugin } from 'zhin.js';
|
|
25
|
-
|
|
26
|
-
const plugin = usePlugin();
|
|
27
|
-
const { logger, root } = plugin;
|
|
28
|
-
|
|
29
|
-
// ─── 配置 ────────────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
interface Config {
|
|
32
|
-
apiBase?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const configService = root.inject('config');
|
|
36
|
-
const appConfig = configService?.getPrimary<{ '60s'?: Config }>() || {};
|
|
37
|
-
const config: Config = {
|
|
38
|
-
apiBase: 'https://60s.viki.moe',
|
|
39
|
-
...appConfig['60s'],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// 将 API 地址写入环境变量,供 tool handler 使用
|
|
43
|
-
process.env.ZHIN_60S_API = config.apiBase || 'https://60s.viki.moe';
|
|
44
|
-
|
|
1
|
+
export {
|
|
2
|
+
asArray,
|
|
3
|
+
asRecord,
|
|
4
|
+
asString,
|
|
5
|
+
fetchApi,
|
|
6
|
+
formatList,
|
|
7
|
+
} from './api.js';
|
|
8
|
+
export type { ApiPayload, ListItem } from './api.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 60s apiBase 运行时注入 — plugin setup 注册 getter,fetchApi 每次调用时求值。
|
|
3
|
+
* 参考 lottery 的 registerLotteryAgentDeps:registration 模式,卸载时反注册,无 process.env 残留。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_API_BASE = 'https://60s.viki.moe';
|
|
7
|
+
|
|
8
|
+
export type SixtySApiBaseGetter = () => string;
|
|
9
|
+
|
|
10
|
+
const registrations: Array<{ readonly value: SixtySApiBaseGetter }> = [];
|
|
11
|
+
|
|
12
|
+
/** Generation-owned apiBase binding used by Plugin Runtime setup(). Returns unregister. */
|
|
13
|
+
export function registerSixtySApiBase(getter: SixtySApiBaseGetter): () => void {
|
|
14
|
+
const registration = Object.freeze({ value: getter });
|
|
15
|
+
registrations.push(registration);
|
|
16
|
+
return () => {
|
|
17
|
+
const index = registrations.lastIndexOf(registration);
|
|
18
|
+
if (index >= 0) registrations.splice(index, 1);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Resolve the apiBase at call time so config patches take effect without reload. */
|
|
23
|
+
export function resolveApiBase(): string {
|
|
24
|
+
const getter = registrations[registrations.length - 1]?.value;
|
|
25
|
+
return getter?.() || DEFAULT_API_BASE;
|
|
26
|
+
}
|
package/lib/index.d.ts
DELETED
package/lib/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/plugin.yml
DELETED
package/tools/api.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 60s API 共享模块 — 供 *.tool.md handler 使用
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const API_BASE = process.env.ZHIN_60S_API || 'https://60s.viki.moe';
|
|
6
|
-
|
|
7
|
-
export async function fetchApi<T = any>(
|
|
8
|
-
endpoint: string,
|
|
9
|
-
params?: Record<string, string>,
|
|
10
|
-
): Promise<T> {
|
|
11
|
-
const url = new URL(`${API_BASE}/v2${endpoint}`);
|
|
12
|
-
if (params) {
|
|
13
|
-
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
14
|
-
}
|
|
15
|
-
const res = await fetch(url.toString());
|
|
16
|
-
const data = (await res.json()) as any;
|
|
17
|
-
if (data.error) throw new Error(data.error);
|
|
18
|
-
if (data.code !== undefined && data.code !== 200 && data.code !== 0) {
|
|
19
|
-
throw new Error(data.message || data.msg || `API 错误: ${data.code}`);
|
|
20
|
-
}
|
|
21
|
-
return data.data ?? data;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function formatList(items: any[], limit = 10): string {
|
|
25
|
-
return items
|
|
26
|
-
.slice(0, limit)
|
|
27
|
-
.map((item, i) => {
|
|
28
|
-
const title = item.title || item.name || item.word || item;
|
|
29
|
-
const hot = item.hot_value || item.hot;
|
|
30
|
-
const hotStr = hot ? ` 🔥${hot}` : '';
|
|
31
|
-
return `${i + 1}. ${title}${hotStr}`;
|
|
32
|
-
})
|
|
33
|
-
.join('\n');
|
|
34
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: exchange_rate
|
|
3
|
-
description: 查询货币汇率
|
|
4
|
-
tags: [金融, 汇率, 查询]
|
|
5
|
-
keywords: [汇率, 兑换, 外汇, exchange, rate]
|
|
6
|
-
parameters:
|
|
7
|
-
from:
|
|
8
|
-
type: string
|
|
9
|
-
description: 源货币,如 USD, EUR, JPY
|
|
10
|
-
to:
|
|
11
|
-
type: string
|
|
12
|
-
description: 目标货币,如 CNY
|
|
13
|
-
handler: ./handler.ts
|
|
14
|
-
---
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { fetchApi } from '../api.js';
|
|
2
|
-
|
|
3
|
-
export default async function () {
|
|
4
|
-
const data = await fetchApi<any>('/today-in-history');
|
|
5
|
-
const dateStr = data.date || `${data.month || ''}月${data.day || ''}日`;
|
|
6
|
-
const lines = [`📅 历史上的今天 (${dateStr})`, ''];
|
|
7
|
-
const items = data.items || (Array.isArray(data) ? data : []);
|
|
8
|
-
items.slice(0, 10).forEach((item: any, i: number) => {
|
|
9
|
-
const year = item.year || '';
|
|
10
|
-
const title = item.title || item.event || item.content || item;
|
|
11
|
-
const typeIcon = item.event_type === 'birth' ? '👶' : item.event_type === 'death' ? '🕊️' : '📌';
|
|
12
|
-
lines.push(`${typeIcon} ${i + 1}. ${year ? `[${year}] ` : ''}${title}`);
|
|
13
|
-
});
|
|
14
|
-
return lines.join('\n');
|
|
15
|
-
}
|