koishi-plugin-anime-schedule 1.0.2 → 1.0.3

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/config.js CHANGED
@@ -70,9 +70,7 @@ exports.Config = koishi_1.Schema.intersect([
70
70
  .default(0),
71
71
  longRunningThreshold: koishi_1.Schema.number()
72
72
  .min(0).step(1)
73
- .description('剔除长期连载番的话数阈值,0 为不过滤。'
74
- + '展示话数达到该值的作品整条不显示——海螺小姐、宝可梦 地平线这类周更多年的作品'
75
- + '话数会一直涨,而一季 12~24 话的当季番永远达不到,所以填 64 这类值即可')
73
+ .description('剔除长期连载番的话数阈值,0 为不过滤')
76
74
  .default(0),
77
75
  }),
78
76
  }).description('推送内容'),
package/lib/handlers.js CHANGED
@@ -406,16 +406,18 @@ async function commandNext(deps, session, daysText) {
406
406
  const airings = await digest.upcomingAirings(rows.map((row) => row.tid), days);
407
407
  if (!airings.length)
408
408
  return `未来 ${days} 天内订阅的作品没有播出计划。`;
409
- const weekdayNames = (0, constants_1.getMessages)(config.locale).weekdayNames;
409
+ const messages = (0, constants_1.getMessages)(config.locale);
410
410
  const lines = [];
411
411
  let currentDate = '';
412
412
  for (const item of airings) {
413
413
  if (item.date !== currentDate) {
414
414
  currentDate = item.date;
415
- const weekday = weekdayNames[(0, time_1.getLocalWeekday)((0, time_1.getDayStartMs)(item.date, digest.timeZone), digest.timeZone)] ?? '';
416
- lines.push(`【${item.date} ${weekday}】`.trimEnd());
415
+ const weekday = messages.weekdayNames[(0, time_1.getLocalWeekday)((0, time_1.getDayStartMs)(item.date, digest.timeZone), digest.timeZone)] ?? '';
416
+ // 分组标题前留一个空行,与推送表的分组观感一致
417
+ lines.push(`${lines.length ? '\n' : ''}【${item.date} ${weekday}】`.trimEnd());
417
418
  }
418
- lines.push(`· ${item.time} ${(0, utils_1.truncate)(item.name, 60)} (${item.episode})`);
419
+ const episode = item.recurring && item.episodeCount > 0 ? messages.episodeUnit(item.episodeCount) : '';
420
+ lines.push(`${episode ? `${item.time} ${episode}` : item.time}\n ${(0, utils_1.truncate)(item.name, 60)}`);
419
421
  }
420
422
  return [`未来 ${days} 天内订阅作品的播出计划:`, ...lines].join('\n');
421
423
  }
@@ -1,3 +1,42 @@
1
+ /**
2
+ * 纯文本形态的渲染。
3
+ *
4
+ * 与图片形态共用同一份 `Digest`,因此两者列出的作品、时刻、集数完全一致;
5
+ * 差别只在**信息的排布**上——图片能靠颜色与留白分区,文本只能靠符号与缩进。
6
+ *
7
+ * ## 版式约定
8
+ *
9
+ * ```
10
+ * 今日番剧更新 (10/1周四)
11
+ * 共 7 部作品,其中 3 部为深夜档
12
+ *
13
+ * 【今日播出】
14
+ * 00:00 第 53 话
15
+ * 考拉绘日记
16
+ * ID:564980 评分 5.0
17
+ *
18
+ * 00:15 第 12 话
19
+ * 盗墓王
20
+ * ID:621835 评分 4.5
21
+ * ```
22
+ *
23
+ * 四条规则:
24
+ *
25
+ * 1. **一行放一个语义块**。时刻与集数留在首行(信息最密、最需要对齐),标题单独占一行
26
+ * 并缩进两格——中文标题动辄二三十字,和时刻挤在一行必然折行,折行后时刻就淹没在
27
+ * 文字里了。ID 与评分再缩进一层,便于扫读时跳过。
28
+ * 2. **集数走 `episodeUnit`**(`第 53 话`),不再用 `#53`。整个界面的其它地方都是中文,
29
+ * `#` 是英文记法,混在一起不好读。
30
+ * 3. **一次性放送不显示集数**:剧场版、特别篇没有「第几话」的概念,`episodeCount` 恒为 1,
31
+ * 写出来是噪声。这与图片卡片的处理保持一致。
32
+ * 4. **条目之间空一行**。三条一组的块状结构如果首尾相连,读者分不清哪一行属于哪一部;
33
+ * 空行是最省字符的分隔手段,一屏仍能放下四五部。
34
+ *
35
+ * 季表用同一套版式,只把首行的时刻换成**完整日期**(`7/7周二 23:00`)——它是一览,
36
+ * 读者最先想知道「什么时候开播」,而条目散在整个季度里,光有时刻没法定位。相应地季表
37
+ * **不套分组标题**:日期已经在首行,再套一层 `【10/11周日】` 是重复信息,还会出现
38
+ * 「首播在 9/27 的条目落在 `【10/2周五】` 组里」(因为它下次播出在那天)这种对不上的情况。
39
+ */
1
40
  import { h } from 'koishi';
2
41
  import type { Config } from '../config';
3
42
  import type { Digest } from '../types';
@@ -5,7 +44,7 @@ import type { Digest } from '../types';
5
44
  * 文本形态的推送渲染。
6
45
  *
7
46
  * 返回消息元素数组,调用方按平台能力选择直接发送或合并转发:
8
- * 第一项是列表头 + 统计行与数据来源,其余每一项是一个分组。
47
+ * 第一项是列表头 + 统计行,之后每一项是一个分组,最后是数据来源。
9
48
  * 相邻元素之间插入换行,否则平台会把它们首尾拼在同一行。
10
49
  */
11
50
  export declare function renderText(config: Config, digest: Digest): h[];
@@ -2,25 +2,102 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderText = renderText;
4
4
  exports.buildForward = buildForward;
5
+ /**
6
+ * 纯文本形态的渲染。
7
+ *
8
+ * 与图片形态共用同一份 `Digest`,因此两者列出的作品、时刻、集数完全一致;
9
+ * 差别只在**信息的排布**上——图片能靠颜色与留白分区,文本只能靠符号与缩进。
10
+ *
11
+ * ## 版式约定
12
+ *
13
+ * ```
14
+ * 今日番剧更新 (10/1周四)
15
+ * 共 7 部作品,其中 3 部为深夜档
16
+ *
17
+ * 【今日播出】
18
+ * 00:00 第 53 话
19
+ * 考拉绘日记
20
+ * ID:564980 评分 5.0
21
+ *
22
+ * 00:15 第 12 话
23
+ * 盗墓王
24
+ * ID:621835 评分 4.5
25
+ * ```
26
+ *
27
+ * 四条规则:
28
+ *
29
+ * 1. **一行放一个语义块**。时刻与集数留在首行(信息最密、最需要对齐),标题单独占一行
30
+ * 并缩进两格——中文标题动辄二三十字,和时刻挤在一行必然折行,折行后时刻就淹没在
31
+ * 文字里了。ID 与评分再缩进一层,便于扫读时跳过。
32
+ * 2. **集数走 `episodeUnit`**(`第 53 话`),不再用 `#53`。整个界面的其它地方都是中文,
33
+ * `#` 是英文记法,混在一起不好读。
34
+ * 3. **一次性放送不显示集数**:剧场版、特别篇没有「第几话」的概念,`episodeCount` 恒为 1,
35
+ * 写出来是噪声。这与图片卡片的处理保持一致。
36
+ * 4. **条目之间空一行**。三条一组的块状结构如果首尾相连,读者分不清哪一行属于哪一部;
37
+ * 空行是最省字符的分隔手段,一屏仍能放下四五部。
38
+ *
39
+ * 季表用同一套版式,只把首行的时刻换成**完整日期**(`7/7周二 23:00`)——它是一览,
40
+ * 读者最先想知道「什么时候开播」,而条目散在整个季度里,光有时刻没法定位。相应地季表
41
+ * **不套分组标题**:日期已经在首行,再套一层 `【10/11周日】` 是重复信息,还会出现
42
+ * 「首播在 9/27 的条目落在 `【10/2周五】` 组里」(因为它下次播出在那天)这种对不上的情况。
43
+ */
5
44
  const koishi_1 = require("koishi");
6
45
  const constants_1 = require("../constants");
7
46
  const utils_1 = require("../utils");
47
+ /** 标题最多显示多少个字,超出补省略号(与命令里 `anime.next` 的宽度保持一致) */
48
+ const TITLE_MAX = 60;
49
+ function formatItem(item, season, config) {
50
+ const messages = (0, constants_1.getMessages)(config.locale);
51
+ const episode = item.recurring && item.episodeCount > 0 ? messages.episodeUnit(item.episodeCount) : '';
52
+ // 季表是一览,读者最先想知道「什么时候开播」,因此首行给完整日期;其余三张表按天分组,
53
+ // 日期已经在分组标题上,首行只留时刻。
54
+ const lead = season ? (item.airLabel || item.time) : item.time;
55
+ const lines = [episode ? `${lead} ${episode}` : lead, ` ${(0, utils_1.truncate)(item.name, TITLE_MAX)}`];
56
+ const parts = [];
57
+ if (item.bgmId !== null)
58
+ parts.push(`ID:${item.bgmId}`);
59
+ if (item.score)
60
+ parts.push(`评分 ${item.score}`);
61
+ if (item.subscribed)
62
+ parts.push(messages.subscribedBadge);
63
+ if (parts.length)
64
+ lines.push(` ${parts.join(' ')}`);
65
+ return lines.join('\n');
66
+ }
67
+ /**
68
+ * 分组。
69
+ *
70
+ * 季表**不再按天分组**:它的条目本来就散在整个季度里,而首行已经带了完整日期,
71
+ * 再套一层 `【10/11周日】` 是重复信息,还会让标签与条目日期对不上(如首播在 9/27 的
72
+ * 条目落在 `【10/2周五】` 组里,因为它「下次播出」在那天)。
73
+ */
74
+ function formatGroup(group, config, season) {
75
+ const body = group.items.map((item) => formatItem(item, season, config)).join('\n\n');
76
+ if (season || !group.label)
77
+ return body;
78
+ return `【${group.label}】\n${body}`;
79
+ }
8
80
  /**
9
81
  * 文本形态的推送渲染。
10
82
  *
11
83
  * 返回消息元素数组,调用方按平台能力选择直接发送或合并转发:
12
- * 第一项是列表头 + 统计行与数据来源,其余每一项是一个分组。
84
+ * 第一项是列表头 + 统计行,之后每一项是一个分组,最后是数据来源。
13
85
  * 相邻元素之间插入换行,否则平台会把它们首尾拼在同一行。
14
86
  */
15
87
  function renderText(config, digest) {
16
- const footer = (0, constants_1.getMessages)(config.locale).footer;
17
- const head = [digest.header, digest.summary, footer].filter(Boolean).join('\n');
18
- if (!digest.groups.length)
19
- return [koishi_1.h.text(head)];
20
- const elements = [koishi_1.h.text(head)];
88
+ const messages = (0, constants_1.getMessages)(config.locale);
89
+ const season = digest.kind === 'season';
90
+ if (!digest.groups.length) {
91
+ return [koishi_1.h.text([digest.header, digest.summary, messages.footer].filter(Boolean).join('\n'))];
92
+ }
93
+ const elements = [
94
+ koishi_1.h.text([digest.header, digest.summary].filter(Boolean).join('\n')),
95
+ ];
21
96
  for (const group of digest.groups) {
22
- elements.push(koishi_1.h.text(`\n${formatGroup(group, config)}`));
97
+ elements.push(koishi_1.h.text(`\n${formatGroup(group, config, season)}`));
23
98
  }
99
+ if (messages.footer)
100
+ elements.push(koishi_1.h.text(`\n${messages.footer}`));
24
101
  return elements;
25
102
  }
26
103
  /**
@@ -30,28 +107,15 @@ function renderText(config, digest) {
30
107
  * 平台或适配器不支持 `message.forward` 时由调用方退回普通文本。
31
108
  */
32
109
  function buildForward(config, digest) {
33
- const messages = [
110
+ const messages = (0, constants_1.getMessages)(config.locale);
111
+ const season = digest.kind === 'season';
112
+ const nodes = [
34
113
  (0, koishi_1.h)('message', { nickname: constants_1.DIGEST_LABELS[digest.kind] }, koishi_1.h.text([digest.header, digest.summary].filter(Boolean).join('\n'))),
35
114
  ];
36
115
  for (const group of digest.groups) {
37
- messages.push((0, koishi_1.h)('message', { nickname: group.label || digest.header }, koishi_1.h.text(formatGroup(group, config))));
116
+ nodes.push((0, koishi_1.h)('message', { nickname: group.label || digest.header }, koishi_1.h.text(formatGroup(group, config, season))));
38
117
  }
39
- const footer = (0, constants_1.getMessages)(config.locale).footer;
40
- if (footer)
41
- messages.push((0, koishi_1.h)('message', { nickname: '数据来源' }, koishi_1.h.text(footer)));
42
- return (0, koishi_1.h)('figure', messages);
43
- }
44
- function formatGroup(group, config) {
45
- const lines = [];
46
- if (group.label)
47
- lines.push(`【${group.label}】`);
48
- for (const item of group.items)
49
- lines.push(formatItem(item, config));
50
- return lines.join('\n');
51
- }
52
- function formatItem(item, config) {
53
- void config;
54
- const score = item.score ? ` [${item.score}]` : '';
55
- const id = item.bgmId === null ? '' : ` ID:${item.bgmId}`;
56
- return `· ${(0, utils_1.truncate)(`${item.time} ${item.name} (${item.episode})${score}${id}`, 140)}`;
118
+ if (messages.footer)
119
+ nodes.push((0, koishi_1.h)('message', { nickname: '数据来源' }, koishi_1.h.text(messages.footer)));
120
+ return (0, koishi_1.h)('figure', nodes);
57
121
  }
@@ -253,14 +253,19 @@ class Scheduler {
253
253
  continue;
254
254
  }
255
255
  try {
256
+ // 与推送表同一套版式:首行时刻 + 集数,标题与提示各占一行
256
257
  const lines = fresh.map((item) => {
257
258
  const inMinutes = Math.max(0, Math.round((item.atMs - now) / 60000));
258
- return `· ${item.time} ${item.name} (${item.episode})\n 还有约 ${inMinutes} 分钟开播`;
259
+ const episode = item.recurring && item.episodeCount > 0
260
+ ? (0, constants_1.getMessages)(this.config.locale).episodeUnit(item.episodeCount)
261
+ : '';
262
+ const lead = episode ? `${item.time} ${episode}` : item.time;
263
+ return `${lead}\n ${item.name}\n 还有约 ${inMinutes} 分钟开播`;
259
264
  });
260
265
  const message = koishi_1.h.text([
261
266
  `即将开播 (${(0, time_1.formatDate)(now, this.config.timeZone)})`,
262
267
  ...lines,
263
- ].join('\n'));
268
+ ].join('\n\n'));
264
269
  const bot = this.ctx.bots.find((entry) => entry.selfId === channel.botId);
265
270
  if (bot)
266
271
  await bot.sendMessage(channel.channelId, message);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-anime-schedule",
3
3
  "description": "番剧更新表与订阅提醒:每日 / 深夜档 / 本周 / 本季四张表,支持单部订阅",
4
- "version": "1.0.2",
4
+ "version": "1.0.3",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -52,7 +52,7 @@ npm i koishi-plugin-anime-schedule
52
52
  | `push.dailyTime` 等 | 见配置页 | 四种推送各自的开关与时刻 |
53
53
  | `push.updateLookaheadMinutes` | `30` | 订阅作品提前多少分钟提醒 |
54
54
  | `content.maxItems` | `0` | 单次推送最多显示多少部,`0` 为不限制 |
55
- | `content.longRunningThreshold` | `0` | 剔除长期连载番的话数阈值,`0` 为不过滤。展示话数达到该值的作品不显示;填 `64` 可滤掉海螺小姐、宝可梦 地平线这类周更多年的作品 |
55
+ | `content.longRunningThreshold` | `0` | 剔除长期连载番的话数阈值,`0` 为不过滤 |
56
56
  | `output.format` | `image` | `image` / `text` |
57
57
  | `output.layout` | `table` | 图片排版:`table` / `list` |
58
58
  | `localize.titleStyle` | `localized` | `localized` 仅中文 / `both` 中文(日文原名)/ `original` 仅日文原名 |