koishi-plugin-anime-schedule 1.0.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/LICENSE +21 -0
- package/lib/config.d.ts +64 -0
- package/lib/config.js +222 -0
- package/lib/constants.d.ts +270 -0
- package/lib/constants.js +355 -0
- package/lib/data/anilist-match.d.ts +71 -0
- package/lib/data/anilist-match.js +85 -0
- package/lib/data/bangumi-data-loader.d.ts +23 -0
- package/lib/data/bangumi-data-loader.js +110 -0
- package/lib/data/bangumi-data-source.d.ts +73 -0
- package/lib/data/bangumi-data-source.js +79 -0
- package/lib/data/bangumi-data.d.ts +139 -0
- package/lib/data/bangumi-data.js +276 -0
- package/lib/handlers.d.ts +59 -0
- package/lib/handlers.js +515 -0
- package/lib/index.d.ts +9 -0
- package/lib/index.js +302 -0
- package/lib/render/canvas.d.ts +67 -0
- package/lib/render/canvas.js +128 -0
- package/lib/render/covers.d.ts +42 -0
- package/lib/render/covers.js +304 -0
- package/lib/render/fonts.d.ts +40 -0
- package/lib/render/fonts.js +74 -0
- package/lib/render/image.d.ts +168 -0
- package/lib/render/image.js +746 -0
- package/lib/render/text.d.ts +18 -0
- package/lib/render/text.js +53 -0
- package/lib/schedule/anilist-ids.d.ts +37 -0
- package/lib/schedule/anilist-ids.js +78 -0
- package/lib/services/airing.d.ts +100 -0
- package/lib/services/airing.js +293 -0
- package/lib/services/digest.d.ts +401 -0
- package/lib/services/digest.js +1408 -0
- package/lib/services/dispatch.d.ts +60 -0
- package/lib/services/dispatch.js +163 -0
- package/lib/services/localizer.d.ts +82 -0
- package/lib/services/localizer.js +326 -0
- package/lib/services/network.d.ts +63 -0
- package/lib/services/network.js +84 -0
- package/lib/services/scheduler.d.ts +78 -0
- package/lib/services/scheduler.js +281 -0
- package/lib/types.d.ts +241 -0
- package/lib/types.js +7 -0
- package/lib/utils/fold.d.ts +12 -0
- package/lib/utils/fold.js +62 -0
- package/lib/utils/index.d.ts +36 -0
- package/lib/utils/index.js +139 -0
- package/lib/utils/time.d.ts +44 -0
- package/lib/utils/time.js +176 -0
- package/package.json +46 -0
- package/readme.md +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 YuzuharaYuka
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Schema } from 'koishi';
|
|
2
|
+
import type { DigestFormat, ImageLayout, PluginLocale, TimeZone, TitleStyle } from './types';
|
|
3
|
+
export interface Config {
|
|
4
|
+
timeZone: TimeZone;
|
|
5
|
+
locale: PluginLocale;
|
|
6
|
+
push: {
|
|
7
|
+
dailyEnabled: boolean;
|
|
8
|
+
dailyTime: string;
|
|
9
|
+
dailyOffsetDays: number;
|
|
10
|
+
nightEnabled: boolean;
|
|
11
|
+
nightTime: string;
|
|
12
|
+
weeklyEnabled: boolean;
|
|
13
|
+
weeklyWeekday: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
14
|
+
weeklyTime: string;
|
|
15
|
+
seasonEnabled: boolean;
|
|
16
|
+
seasonTime: string;
|
|
17
|
+
updateReminderEnabled: boolean;
|
|
18
|
+
updateLookaheadMinutes: number;
|
|
19
|
+
updatePollMinutes: number;
|
|
20
|
+
};
|
|
21
|
+
content: {
|
|
22
|
+
/** 单次推送最多展示的作品数,0 表示不限制 */
|
|
23
|
+
maxItems: number;
|
|
24
|
+
/** 超长期连载番的话数阈值,0 表示不过滤 */
|
|
25
|
+
longRunningThreshold: number;
|
|
26
|
+
};
|
|
27
|
+
output: {
|
|
28
|
+
format: DigestFormat;
|
|
29
|
+
layout: ImageLayout;
|
|
30
|
+
coverLimit: number;
|
|
31
|
+
showCover: boolean;
|
|
32
|
+
showScore: boolean;
|
|
33
|
+
useForward: boolean;
|
|
34
|
+
};
|
|
35
|
+
localize: {
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
titleStyle: TitleStyle;
|
|
38
|
+
cacheTtlDays: number;
|
|
39
|
+
/** 播出状态(是否已完结)的缓存有效期,单位小时 */
|
|
40
|
+
airingTtlHours: number;
|
|
41
|
+
};
|
|
42
|
+
render: {
|
|
43
|
+
fontFamily: string;
|
|
44
|
+
coverConcurrency: number;
|
|
45
|
+
coverCacheDays: number;
|
|
46
|
+
/** JPEG 编码质量(0-100)。图片以文字与线条为主,压缩伪影很明显,因此默认取高值 */
|
|
47
|
+
jpegQuality: number;
|
|
48
|
+
};
|
|
49
|
+
subscription: {
|
|
50
|
+
/** 单个群最多订阅的番剧数量,0 表示不限制 */
|
|
51
|
+
maxPerChannel: number;
|
|
52
|
+
};
|
|
53
|
+
advanced: {
|
|
54
|
+
requestTimeout: number;
|
|
55
|
+
retries: number;
|
|
56
|
+
debug: boolean;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export declare const Config: Schema<Config>;
|
|
60
|
+
/**
|
|
61
|
+
* 把配置夹到安全区间。
|
|
62
|
+
* 只保证「类型正确」与「不小于最小值」,不设人为上限。
|
|
63
|
+
*/
|
|
64
|
+
export declare function normalizeConfig(config: Config): Config;
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { Schema } from 'koishi';
|
|
2
|
+
import { COVER_CONCURRENCY, DEFAULT_FONT_FAMILY, DEFAULT_JPEG_QUALITY } from './constants';
|
|
3
|
+
import { resolveTimeZone } from './utils/time';
|
|
4
|
+
const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
5
|
+
const WEEKDAYS = [
|
|
6
|
+
{ value: 0, label: '周日' },
|
|
7
|
+
{ value: 1, label: '周一' },
|
|
8
|
+
{ value: 2, label: '周二' },
|
|
9
|
+
{ value: 3, label: '周三' },
|
|
10
|
+
{ value: 4, label: '周四' },
|
|
11
|
+
{ value: 5, label: '周五' },
|
|
12
|
+
{ value: 6, label: '周六' },
|
|
13
|
+
];
|
|
14
|
+
export const Config = Schema.intersect([
|
|
15
|
+
Schema.object({
|
|
16
|
+
timeZone: Schema.union([
|
|
17
|
+
Schema.const('Asia/Shanghai').description('Asia/Shanghai (UTC+8)'),
|
|
18
|
+
Schema.const('Asia/Tokyo').description('Asia/Tokyo (UTC+9)'),
|
|
19
|
+
Schema.const('Asia/Hong_Kong').description('Asia/Hong_Kong (UTC+8)'),
|
|
20
|
+
Schema.const('Asia/Taipei').description('Asia/Taipei (UTC+8)'),
|
|
21
|
+
Schema.const('UTC').description('UTC'),
|
|
22
|
+
])
|
|
23
|
+
.description('时刻与日期按此时区换算')
|
|
24
|
+
.default('Asia/Shanghai'),
|
|
25
|
+
locale: Schema.union([
|
|
26
|
+
Schema.const('zh-CN').description('简体中文'),
|
|
27
|
+
Schema.const('ja-JP').description('日本語'),
|
|
28
|
+
Schema.const('en-US').description('English'),
|
|
29
|
+
])
|
|
30
|
+
.description('界面语言')
|
|
31
|
+
.default('zh-CN'),
|
|
32
|
+
}),
|
|
33
|
+
Schema.object({
|
|
34
|
+
push: Schema.object({
|
|
35
|
+
dailyEnabled: Schema.boolean().description('每日更新表').default(true),
|
|
36
|
+
dailyTime: Schema.string().pattern(TIME_PATTERN).description('推送时刻').default('08:00'),
|
|
37
|
+
dailyOffsetDays: Schema.number()
|
|
38
|
+
.min(-1).max(1).step(1)
|
|
39
|
+
.description('日期偏移:0 当天,-1 前一天,1 后一天')
|
|
40
|
+
.default(0),
|
|
41
|
+
nightEnabled: Schema.boolean().description('深夜档(当晚 21:00 至次日 06:00)').default(true),
|
|
42
|
+
nightTime: Schema.string().pattern(TIME_PATTERN).description('推送时刻').default('21:00'),
|
|
43
|
+
weeklyEnabled: Schema.boolean().description('本周更新表').default(true),
|
|
44
|
+
weeklyWeekday: Schema.union(WEEKDAYS.map((item) => Schema.const(item.value).description(item.label)))
|
|
45
|
+
.description('周表的推送星期')
|
|
46
|
+
.default(1),
|
|
47
|
+
weeklyTime: Schema.string().pattern(TIME_PATTERN).description('推送时刻').default('08:00'),
|
|
48
|
+
seasonEnabled: Schema.boolean().description('本季新番表').default(true),
|
|
49
|
+
seasonTime: Schema.string().pattern(TIME_PATTERN).description('季度首日的推送时刻').default('08:00'),
|
|
50
|
+
updateReminderEnabled: Schema.boolean().description('订阅作品的开播提醒').default(true),
|
|
51
|
+
updateLookaheadMinutes: Schema.number()
|
|
52
|
+
.min(1).step(1)
|
|
53
|
+
.description('提前多少分钟提醒')
|
|
54
|
+
.default(30),
|
|
55
|
+
updatePollMinutes: Schema.number()
|
|
56
|
+
.min(1).step(1)
|
|
57
|
+
.description('检查间隔(分钟)')
|
|
58
|
+
.default(10),
|
|
59
|
+
}),
|
|
60
|
+
}).description('推送设置'),
|
|
61
|
+
Schema.object({
|
|
62
|
+
content: Schema.object({
|
|
63
|
+
maxItems: Schema.number()
|
|
64
|
+
.min(0).step(10)
|
|
65
|
+
.description('单次推送最多显示多少部,0 为不限制')
|
|
66
|
+
.default(0),
|
|
67
|
+
longRunningThreshold: Schema.number()
|
|
68
|
+
.min(0).step(1)
|
|
69
|
+
.description('剔除长期连载番的话数阈值,0 为不过滤')
|
|
70
|
+
.default(0),
|
|
71
|
+
}),
|
|
72
|
+
}).description('推送内容'),
|
|
73
|
+
Schema.object({
|
|
74
|
+
output: Schema.object({
|
|
75
|
+
format: Schema.union([
|
|
76
|
+
Schema.const('image').description('风格化图片'),
|
|
77
|
+
Schema.const('text').description('纯文本'),
|
|
78
|
+
])
|
|
79
|
+
.description('推送形态;图片渲染失败时自动改用文本')
|
|
80
|
+
.default('image'),
|
|
81
|
+
layout: Schema.union([
|
|
82
|
+
Schema.const('table').description('时间表'),
|
|
83
|
+
Schema.const('list').description('卡片列表'),
|
|
84
|
+
])
|
|
85
|
+
.description('图片排版')
|
|
86
|
+
.default('table'),
|
|
87
|
+
coverLimit: Schema.number()
|
|
88
|
+
.min(0)
|
|
89
|
+
.description('单张图片最多加载多少张封面,0 为不限制')
|
|
90
|
+
.default(0),
|
|
91
|
+
showCover: Schema.boolean().description('显示封面').default(true),
|
|
92
|
+
showScore: Schema.boolean().description('显示评分').default(true),
|
|
93
|
+
useForward: Schema.boolean()
|
|
94
|
+
.description('纯文本模式使用合并转发,避免刷屏')
|
|
95
|
+
.default(true),
|
|
96
|
+
}),
|
|
97
|
+
}).description('输出设置'),
|
|
98
|
+
Schema.object({
|
|
99
|
+
localize: Schema.object({
|
|
100
|
+
enabled: Schema.boolean()
|
|
101
|
+
.description('使用中文标题与评分')
|
|
102
|
+
.default(true),
|
|
103
|
+
titleStyle: Schema.union([
|
|
104
|
+
Schema.const('localized').description('仅中文'),
|
|
105
|
+
Schema.const('both').description('中文(日文原名)'),
|
|
106
|
+
Schema.const('original').description('仅日文原名'),
|
|
107
|
+
])
|
|
108
|
+
.description('标题展示方式')
|
|
109
|
+
.default('localized'),
|
|
110
|
+
cacheTtlDays: Schema.number()
|
|
111
|
+
.min(1).step(1)
|
|
112
|
+
.description('标题、封面与评分的缓存天数')
|
|
113
|
+
.default(30),
|
|
114
|
+
airingTtlHours: Schema.number()
|
|
115
|
+
.min(1).step(1)
|
|
116
|
+
.description('播出状态(是否已播完)的缓存小时数')
|
|
117
|
+
.default(6),
|
|
118
|
+
}),
|
|
119
|
+
}).description('本地化'),
|
|
120
|
+
Schema.object({
|
|
121
|
+
render: Schema.object({
|
|
122
|
+
fontFamily: Schema.string()
|
|
123
|
+
.description('绘制图片时使用的字体族;留空则自动寻找系统中文字体')
|
|
124
|
+
.default(DEFAULT_FONT_FAMILY),
|
|
125
|
+
coverConcurrency: Schema.number()
|
|
126
|
+
.min(1)
|
|
127
|
+
.description('封面下载并发数')
|
|
128
|
+
.default(COVER_CONCURRENCY),
|
|
129
|
+
coverCacheDays: Schema.number()
|
|
130
|
+
.min(0)
|
|
131
|
+
.description('封面缓存天数,0 为不写磁盘')
|
|
132
|
+
.default(21),
|
|
133
|
+
jpegQuality: Schema.number()
|
|
134
|
+
.min(1).max(100).step(1)
|
|
135
|
+
.description('图片编码质量')
|
|
136
|
+
.default(DEFAULT_JPEG_QUALITY),
|
|
137
|
+
}),
|
|
138
|
+
}).description('图片'),
|
|
139
|
+
Schema.object({
|
|
140
|
+
subscription: Schema.object({
|
|
141
|
+
maxPerChannel: Schema.number()
|
|
142
|
+
.min(0).step(1)
|
|
143
|
+
.description('单个群最多订阅多少部,0 为不限制')
|
|
144
|
+
.default(0),
|
|
145
|
+
}),
|
|
146
|
+
}).description('订阅'),
|
|
147
|
+
Schema.object({
|
|
148
|
+
advanced: Schema.object({
|
|
149
|
+
requestTimeout: Schema.number()
|
|
150
|
+
.min(1)
|
|
151
|
+
.description('单次请求超时(秒)')
|
|
152
|
+
.default(20),
|
|
153
|
+
retries: Schema.number()
|
|
154
|
+
.min(0)
|
|
155
|
+
.description('请求失败重试次数')
|
|
156
|
+
.default(2),
|
|
157
|
+
debug: Schema.boolean()
|
|
158
|
+
.description('输出调试日志')
|
|
159
|
+
.default(false),
|
|
160
|
+
}),
|
|
161
|
+
}).description('其他'),
|
|
162
|
+
]);
|
|
163
|
+
/**
|
|
164
|
+
* 把配置夹到安全区间。
|
|
165
|
+
* 只保证「类型正确」与「不小于最小值」,不设人为上限。
|
|
166
|
+
*/
|
|
167
|
+
export function normalizeConfig(config) {
|
|
168
|
+
const int = (value, fallback, min = 0) => {
|
|
169
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
170
|
+
return fallback;
|
|
171
|
+
return Math.max(min, Math.round(value));
|
|
172
|
+
};
|
|
173
|
+
const time = (value, fallback) => (typeof value === 'string' && TIME_PATTERN.test(value) ? value : fallback);
|
|
174
|
+
// 旧版本存在 `both` 形态,已移除,统一按图片处理
|
|
175
|
+
const format = config.output?.format === 'text' ? 'text' : 'image';
|
|
176
|
+
return {
|
|
177
|
+
...config,
|
|
178
|
+
timeZone: resolveTimeZone(config.timeZone, 'Asia/Shanghai'),
|
|
179
|
+
push: {
|
|
180
|
+
...config.push,
|
|
181
|
+
dailyTime: time(config.push?.dailyTime, '08:00'),
|
|
182
|
+
dailyOffsetDays: Math.max(-1, Math.min(1, int(config.push?.dailyOffsetDays, 0))),
|
|
183
|
+
weeklyWeekday: Math.max(0, Math.min(6, int(config.push?.weeklyWeekday, 1))),
|
|
184
|
+
nightTime: time(config.push?.nightTime, '21:00'),
|
|
185
|
+
weeklyTime: time(config.push?.weeklyTime, '08:00'),
|
|
186
|
+
seasonTime: time(config.push?.seasonTime, '08:00'),
|
|
187
|
+
updateLookaheadMinutes: int(config.push?.updateLookaheadMinutes, 30, 1),
|
|
188
|
+
updatePollMinutes: int(config.push?.updatePollMinutes, 10, 1),
|
|
189
|
+
},
|
|
190
|
+
content: {
|
|
191
|
+
maxItems: int(config.content?.maxItems, 0),
|
|
192
|
+
longRunningThreshold: int(config.content?.longRunningThreshold, 0),
|
|
193
|
+
},
|
|
194
|
+
output: {
|
|
195
|
+
...config.output,
|
|
196
|
+
format,
|
|
197
|
+
layout: config.output?.layout === 'list' ? 'list' : 'table',
|
|
198
|
+
coverLimit: int(config.output?.coverLimit, 0),
|
|
199
|
+
},
|
|
200
|
+
localize: {
|
|
201
|
+
...config.localize,
|
|
202
|
+
cacheTtlDays: int(config.localize?.cacheTtlDays, 30, 1),
|
|
203
|
+
airingTtlHours: int(config.localize?.airingTtlHours, 6, 1),
|
|
204
|
+
},
|
|
205
|
+
render: {
|
|
206
|
+
...config.render,
|
|
207
|
+
fontFamily: config.render?.fontFamily?.trim() || DEFAULT_FONT_FAMILY,
|
|
208
|
+
coverConcurrency: int(config.render?.coverConcurrency, COVER_CONCURRENCY, 1),
|
|
209
|
+
coverCacheDays: int(config.render?.coverCacheDays, 21),
|
|
210
|
+
jpegQuality: Math.min(100, int(config.render?.jpegQuality, DEFAULT_JPEG_QUALITY, 1)),
|
|
211
|
+
},
|
|
212
|
+
subscription: {
|
|
213
|
+
...config.subscription,
|
|
214
|
+
maxPerChannel: int(config.subscription?.maxPerChannel, 0),
|
|
215
|
+
},
|
|
216
|
+
advanced: {
|
|
217
|
+
...config.advanced,
|
|
218
|
+
requestTimeout: int(config.advanced?.requestTimeout, 20, 1),
|
|
219
|
+
retries: int(config.advanced?.retries, 2),
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import type { DigestKind, PluginLocale } from './types';
|
|
2
|
+
/** 插件名,同时作为日志前缀与 User-Agent 主体 */
|
|
3
|
+
export declare const PLUGIN_NAME = "anime-schedule";
|
|
4
|
+
/** Bangumi API 主机 */
|
|
5
|
+
export declare const BANGUMI_API_HOST = "https://api.bgm.tv";
|
|
6
|
+
/**
|
|
7
|
+
* AniList GraphQL 接口。
|
|
8
|
+
*
|
|
9
|
+
* 用于查询「这部番是否还在播」:它直接给出 `status`(FINISHED / RELEASING)与 `endDate`,
|
|
10
|
+
* 而 bangumi-data 自带 `aniList` 的条目 ID,可以按 ID 精确查询、避开标题匹配歧义。
|
|
11
|
+
*/
|
|
12
|
+
export declare const ANILIST_API_URL = "https://graphql.anilist.co";
|
|
13
|
+
/** 单次 GraphQL 请求最多查多少个条目 */
|
|
14
|
+
export declare const ANILIST_BATCH_SIZE = 50;
|
|
15
|
+
/**
|
|
16
|
+
* 两次 AniList 请求之间的最小间隔(毫秒)。
|
|
17
|
+
*
|
|
18
|
+
* 官方限制 90 次/分钟(实测响应头为 30),另有突发限流。留出 1.2 秒间隔后
|
|
19
|
+
* 相当于最多 50 次/分钟,既不会触发突发限流,也远低于上限。
|
|
20
|
+
*/
|
|
21
|
+
export declare const ANILIST_MIN_INTERVAL_MS = 1200;
|
|
22
|
+
/**
|
|
23
|
+
* bangumi-data 索引。
|
|
24
|
+
*
|
|
25
|
+
* 数据默认来自 npm 依赖包 `bangumi-data`(`main` 指向 `dist/data.json`),
|
|
26
|
+
* 随插件一起安装、无需联网;下面这些地址只在依赖包缺失时才作为回退。
|
|
27
|
+
* 仓库: https://github.com/bangumi-data/bangumi-data
|
|
28
|
+
*/
|
|
29
|
+
export declare const BANGUMI_DATA_FALLBACK_URLS: string[];
|
|
30
|
+
export declare const BANGUMI_DATA_FILE = "bangumi-data.json";
|
|
31
|
+
/** bangumi-data 回退缓存的目录(相对于 ctx.baseDir),与本地化缓存放在一起 */
|
|
32
|
+
export declare const DATA_DIR: string[];
|
|
33
|
+
/** 插件版本与主页,请求 Bangumi 时用于自报身份 */
|
|
34
|
+
export declare const PLUGIN_VERSION = "1.0.0";
|
|
35
|
+
export declare const PLUGIN_HOMEPAGE = "https://github.com/YuzuharaYuka/koishi-plugin-anime-schedule";
|
|
36
|
+
/**
|
|
37
|
+
* 请求 Bangumi 时使用的 User-Agent。
|
|
38
|
+
* Bangumi 要求客户端自报身份,形如 `AppName/version (+主页)`。
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildUserAgent(): string;
|
|
41
|
+
/** 各类推送的展示名,用于日志、合并转发标题与开关回复 */
|
|
42
|
+
export declare const DIGEST_LABELS: Record<DigestKind, string>;
|
|
43
|
+
/** 卡片列表排版绘制的卡片数上限,避免条目异常多时图片长度失控 */
|
|
44
|
+
export declare const IMAGE_MAX_CARDS = 200;
|
|
45
|
+
/** 列表排版超过这个数量就折成两栏,避免长图过长 */
|
|
46
|
+
export declare const IMAGE_LIST_TWO_COLUMN_AT = 26;
|
|
47
|
+
/**
|
|
48
|
+
* 「深夜番」的判定:本地时间 0 点到 6 点算作前一日的深夜档。
|
|
49
|
+
*
|
|
50
|
+
* 与 `DigestService` 的深夜档窗口终点(次日 06:00)保持一致——两处都表示「凌晨这一段的结束时刻」,
|
|
51
|
+
* 改一处时另一处也要跟着改。这里的用途是统计与文案(`finish()` 里的「其中 N 部为深夜档」、
|
|
52
|
+
* 时间轴的 24 小时制续写),不是窗口本身。
|
|
53
|
+
*/
|
|
54
|
+
export declare const LATE_NIGHT_END_HOUR = 6;
|
|
55
|
+
/** 本地化缓存的落盘文件名 */
|
|
56
|
+
export declare const LOCALIZE_CACHE_FILE = "localize-cache.json";
|
|
57
|
+
/**
|
|
58
|
+
* 本地化缓存的结构版本。
|
|
59
|
+
*
|
|
60
|
+
* 条目字段变化时必须递增:版本不匹配会整份重建,避免旧条目缺少新字段
|
|
61
|
+
* (例如判断完结所需的 `lastAirDate`)导致逻辑静默失效。
|
|
62
|
+
*
|
|
63
|
+
* v2:新增 `totalEpisodes` 与 `airDate`;旧缓存缺这两个字段,不丢弃会一直重复请求。
|
|
64
|
+
* v6:新增 `hasEpisodeTimes`。逐集档期不再是每条必取,需要靠它区分
|
|
65
|
+
* 「没取过逐集档期」与「取过但没有集数日期」。
|
|
66
|
+
*/
|
|
67
|
+
export declare const LOCALIZE_CACHE_VERSION = 6;
|
|
68
|
+
/** 本地化缓存最大条目数,超出后按写入时间淘汰最旧的一半 */
|
|
69
|
+
export declare const LOCALIZE_CACHE_MAX_ENTRIES = 5000;
|
|
70
|
+
/** 取逐集档期时的单页集数上限;超过这个数的长期番不靠集数判断完结 */
|
|
71
|
+
export declare const EPISODE_QUERY_LIMIT = 100;
|
|
72
|
+
/**
|
|
73
|
+
* 并发检索 Bangumi 的条数上限。
|
|
74
|
+
* Bangumi 没有公开的严格限流,但一次本季表可能有上百部作品,
|
|
75
|
+
* 4 路并发已能把原本串行的等待时间压到 1/4,同时不至于触发风控。
|
|
76
|
+
*/
|
|
77
|
+
export declare const LOCALIZE_CONCURRENCY = 4;
|
|
78
|
+
/** 并发拉取封面时的并发上限 */
|
|
79
|
+
export declare const COVER_CONCURRENCY = 8;
|
|
80
|
+
/** 封面下载超时与体积上限,避免个别大图拖垮整次渲染 */
|
|
81
|
+
export declare const COVER_TIMEOUT_MS = 8000;
|
|
82
|
+
export declare const COVER_MAX_BYTES: number;
|
|
83
|
+
/** 封面磁盘缓存目录(相对于 ctx.baseDir)与有效期 */
|
|
84
|
+
export declare const COVER_CACHE_DIR: string[];
|
|
85
|
+
export declare const COVER_CACHE_TTL_MS: number;
|
|
86
|
+
export declare const COVER_CACHE_MAX_FILES = 1200;
|
|
87
|
+
/** 图片渲染用的字体候选,按平台常见路径排列,找不到时回退到运行环境默认字体 */
|
|
88
|
+
export declare const FONT_CANDIDATES: string[];
|
|
89
|
+
/** 加粗字体候选,找不到时由绘制层用描边模拟粗体 */
|
|
90
|
+
export declare const BOLD_FONT_CANDIDATES: string[];
|
|
91
|
+
/**
|
|
92
|
+
* JPEG 编码质量。
|
|
93
|
+
*
|
|
94
|
+
* 时间表以文字与细线条为主,JPEG 的块状伪影在这种内容上比照片里明显得多。取 100
|
|
95
|
+
* 是**无损**编码,文字边缘最锐利;体积比 92 大约多三成,但一张周表也就一兆出头,
|
|
96
|
+
* 不值得为省这点流量牺牲清晰度。想压体积可以调低。
|
|
97
|
+
*/
|
|
98
|
+
export declare const DEFAULT_JPEG_QUALITY = 100;
|
|
99
|
+
/** 注册到 canvas 的字体族名,与 config.render.fontFamily 的默认值保持一致 */
|
|
100
|
+
export declare const DEFAULT_FONT_FAMILY = "Anime Schedule Sans";
|
|
101
|
+
/**
|
|
102
|
+
* 图片版式常量。
|
|
103
|
+
*
|
|
104
|
+
* 版式遵循同一套规则,所有版面共享它,避免各处各写一套数字:
|
|
105
|
+
* - **页眉**:左侧强调条 + 标题,其下是副标题,右上角是统计与时区,固定高度 120。
|
|
106
|
+
* - **页脚**:清一色 bottom 对齐,只写数据来源。
|
|
107
|
+
* - **卡片**:内容一律**居左**排列;封面、标题、次要信息的竖直位置固定,
|
|
108
|
+
* 与文字长短无关,因此整张图的每一行都对齐在同样的基线上。
|
|
109
|
+
* - **封面**:统一 `媒体高 × 1.4` 的竖版比例,等比完整显示,不裁切。
|
|
110
|
+
*/
|
|
111
|
+
export declare const IMAGE_LAYOUT: {
|
|
112
|
+
/** 统一外边距 */
|
|
113
|
+
padding: number;
|
|
114
|
+
/** 页眉 / 页脚固定高度 */
|
|
115
|
+
headerHeight: number;
|
|
116
|
+
footerHeight: number;
|
|
117
|
+
/** 周历表:七天并排的画布宽度 */
|
|
118
|
+
weekWidth: number;
|
|
119
|
+
/** 单日时间轴(每日 / 深夜档)的画布宽度 */
|
|
120
|
+
timelineWidth: number;
|
|
121
|
+
/** 卡片列表(list 排版)的画布宽度 */
|
|
122
|
+
listWidth: number;
|
|
123
|
+
/** 本季封面网格的画布宽度,要放得下 10 列 */
|
|
124
|
+
seasonWidth: number;
|
|
125
|
+
/** 行卡片高度(每日 / 列表排版)。容纳「标题 + 时刻 + 集数 + ID + 评分」五行 */
|
|
126
|
+
rowHeight: number;
|
|
127
|
+
/** 行卡片之间的间距 */
|
|
128
|
+
rowGap: number;
|
|
129
|
+
/** 行卡片圆角与内边距 */
|
|
130
|
+
rowRadius: number;
|
|
131
|
+
rowPadding: number;
|
|
132
|
+
/** 行卡片封面高度,宽度按 1 : 1.4 推出 */
|
|
133
|
+
mediaHeight: number;
|
|
134
|
+
/** 封面与文字之间的间距 */
|
|
135
|
+
mediaGap: number;
|
|
136
|
+
/** 卡片文字行间距。五行内容靠它撑开节奏,太小会挤成一团 */
|
|
137
|
+
cardLineGap: number;
|
|
138
|
+
/** 封面圆角 */
|
|
139
|
+
mediaRadius: number;
|
|
140
|
+
/** 时间轴轨道相对左边距的位置 */
|
|
141
|
+
timelineSpine: number;
|
|
142
|
+
/** 轨道圆点半径 */
|
|
143
|
+
timelineDot: number;
|
|
144
|
+
/** 时刻文字与轨道之间的间距 */
|
|
145
|
+
timelineTimeGap: number;
|
|
146
|
+
/** 时刻文字与封面之间的间距 */
|
|
147
|
+
timelineCardGap: number;
|
|
148
|
+
/** 日期节点高度(深夜档的「今晚 / 次日凌晨」) */
|
|
149
|
+
timelineDayHeaderHeight: number;
|
|
150
|
+
/** 时间轴主线颜色 */
|
|
151
|
+
timelineLine: string;
|
|
152
|
+
/** 七日列之间的间距与整张卡的内边距 */
|
|
153
|
+
weekColumnGap: number;
|
|
154
|
+
weekCardPadding: number;
|
|
155
|
+
/** 周历表列头高度 */
|
|
156
|
+
weekHeaderHeight: number;
|
|
157
|
+
/** 周历表条目高度、行距与标题行数 */
|
|
158
|
+
weekRowHeight: number;
|
|
159
|
+
weekRowGap: number;
|
|
160
|
+
weekTitleLines: number;
|
|
161
|
+
weekTitleLineHeight: number;
|
|
162
|
+
/** 周历表条目内的封面高度(与三行文字齐高,保持 1 : 1.4 比例不再被压扁) */
|
|
163
|
+
weekMediaHeight: number;
|
|
164
|
+
/** 列表卡片高度(与行卡片一致,两种排版观感统一) */
|
|
165
|
+
cardHeight: number;
|
|
166
|
+
cardGap: number;
|
|
167
|
+
/** 两栏之间的间距 */
|
|
168
|
+
cardColumnGap: number;
|
|
169
|
+
/** 分组标题占用的高度 */
|
|
170
|
+
sectionHeaderHeight: number;
|
|
171
|
+
/** 每行的番剧数量 */
|
|
172
|
+
seasonColumns: number;
|
|
173
|
+
/** 网格列间距与行间距 */
|
|
174
|
+
seasonColumnGap: number;
|
|
175
|
+
seasonRowGap: number;
|
|
176
|
+
/** 网格卡片内边距与卡片高度。封面盒高即卡片高减去上下内边距与元信息高度 */
|
|
177
|
+
seasonPadding: number;
|
|
178
|
+
seasonCardHeight: number;
|
|
179
|
+
/** 标题行数与行高(固定两行,保证首播时间、ID 与评分在同一基线) */
|
|
180
|
+
seasonTitleLines: number;
|
|
181
|
+
seasonTitleLineHeight: number;
|
|
182
|
+
/** 首播时间 / 条目 ID / 评分三行共用的行高 */
|
|
183
|
+
gridMetaLineHeight: number;
|
|
184
|
+
/** 封面宽高比(1 : 1.4 的竖版,与 Bangumi 原图一致) */
|
|
185
|
+
seasonCoverRatio: number;
|
|
186
|
+
};
|
|
187
|
+
/** 时间表配色,全插件共用一套色板 */
|
|
188
|
+
export declare const IMAGE_THEME: {
|
|
189
|
+
background: string;
|
|
190
|
+
surface: string;
|
|
191
|
+
cardBorder: string;
|
|
192
|
+
divider: string;
|
|
193
|
+
/** 强调色:时刻、日期与「今天」高亮 */
|
|
194
|
+
accent: string;
|
|
195
|
+
/** 封面的底色(等比完整显示时留白的那一圈) */
|
|
196
|
+
badge: string;
|
|
197
|
+
title: string;
|
|
198
|
+
body: string;
|
|
199
|
+
muted: string;
|
|
200
|
+
onAccent: string;
|
|
201
|
+
score: string;
|
|
202
|
+
/** 卡片投影:略深一点,白色卡片才从浅灰背景上立起来 */
|
|
203
|
+
shadow: string;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* 面向用户的文本。默认中文,`ja-JP` / `en-US` 覆盖展示层最常读到的句子,
|
|
207
|
+
* 未覆盖的键回退到中文,保证任何语言下都不会出现空文案。
|
|
208
|
+
*
|
|
209
|
+
* 日期文案由 `formatDayLabel` 按语言格式化,不再硬编码在表格里。
|
|
210
|
+
*/
|
|
211
|
+
export interface Messages {
|
|
212
|
+
weekdayNames: string[];
|
|
213
|
+
dailyHeader: (date: string) => string;
|
|
214
|
+
nightHeader: (date: string) => string;
|
|
215
|
+
weeklyHeader: (start: string, end: string) => string;
|
|
216
|
+
seasonHeader: (label: string) => string;
|
|
217
|
+
subHeader: (count: number) => string;
|
|
218
|
+
emptyDaily: string;
|
|
219
|
+
emptyNight: string;
|
|
220
|
+
emptyWeekly: string;
|
|
221
|
+
emptySeason: string;
|
|
222
|
+
emptySub: string;
|
|
223
|
+
/** 有订阅、但今天都没有播出时的提示 */
|
|
224
|
+
noAiringSub: (count: number) => string;
|
|
225
|
+
/** 每日表与订阅表里「当天」那一段的标题 */
|
|
226
|
+
daytimeGroup: string;
|
|
227
|
+
/** 深夜档推送里「今晚 21:00 起」那一段的标题 */
|
|
228
|
+
tonightGroup: (date: string) => string;
|
|
229
|
+
/** 订阅表里「次日凌晨」那一段的标题 */
|
|
230
|
+
tomorrowDawnGroup: (date: string) => string;
|
|
231
|
+
seasonLabel: (year: number, month: number) => string;
|
|
232
|
+
countUnit: string;
|
|
233
|
+
/** 完整集数文案,例如 `第 5 话` */
|
|
234
|
+
episodeUnit: (count: number) => string;
|
|
235
|
+
/** 时间表排版里的短集数文本,例如 `#5` */
|
|
236
|
+
episodeShort: (count: number) => string;
|
|
237
|
+
summaryOf: (total: number, lateNight: number) => string;
|
|
238
|
+
truncated: (shown: number, total: number) => string;
|
|
239
|
+
footer: string;
|
|
240
|
+
/** 周历表表头的日期区间,例如 `9月21日 - 9月27日`;两端已由 formatDayLabel 本地化 */
|
|
241
|
+
rangeLabel: (start: string, end: string) => string;
|
|
242
|
+
/** 今天的日期列角标 */
|
|
243
|
+
todayBadge: string;
|
|
244
|
+
/** 已在订阅列表里的作品,条目上的气泡 */
|
|
245
|
+
subscribedBadge: string;
|
|
246
|
+
/** 日期列超出上限时的提示 */
|
|
247
|
+
moreItems: (count: number) => string;
|
|
248
|
+
}
|
|
249
|
+
export declare const MESSAGES: Record<PluginLocale, Messages>;
|
|
250
|
+
export declare function getMessages(locale: PluginLocale): Messages;
|
|
251
|
+
/** 交互回复:完整句,结尾带句号 */
|
|
252
|
+
export declare const RESPONSES: {
|
|
253
|
+
readonly needGuild: "请在群聊中使用此指令。";
|
|
254
|
+
readonly invalidKind: (value: string) => string;
|
|
255
|
+
readonly invalidDigest: (value: string) => string;
|
|
256
|
+
readonly invalidTid: (value: string) => string;
|
|
257
|
+
readonly invalidIndex: (value: string) => string;
|
|
258
|
+
readonly noSubscription: "本群还没有订阅任何番剧,先使用 `anime.search <关键词>` 找到作品,再用 `anime.sub <ID>` 订阅。";
|
|
259
|
+
readonly subscribeLimit: (limit: number) => string;
|
|
260
|
+
readonly alreadySubscribed: (title: string) => string;
|
|
261
|
+
readonly subscribeFailed: "订阅失败,请稍后重试或检查后台日志。";
|
|
262
|
+
readonly databaseFailed: "数据库操作失败,请检查后台日志。";
|
|
263
|
+
readonly renderFailed: "图片渲染失败,已改用文本发送。";
|
|
264
|
+
readonly emptySearch: (keyword: string) => string;
|
|
265
|
+
readonly notFound: "未找到该作品,它可能不在 bangumi-data 索引里。";
|
|
266
|
+
};
|
|
267
|
+
/** 日志里出现的固定短语 */
|
|
268
|
+
export declare const LOG_MESSAGES: {
|
|
269
|
+
readonly cleanupFailed: "清理过期推送记录失败:";
|
|
270
|
+
};
|