koishi-plugin-anime-schedule 1.0.5 → 1.0.7

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
@@ -93,7 +93,7 @@ exports.Config = koishi_1.Schema.intersect([
93
93
  .description('单张图片最多加载多少张封面,0 为不限制')
94
94
  .default(0),
95
95
  showCover: koishi_1.Schema.boolean().description('显示封面').default(true),
96
- showScore: koishi_1.Schema.boolean().description('显示评分').default(true),
96
+ showScore: koishi_1.Schema.boolean().description('显示评分,评价不足 10 人时不显示').default(true),
97
97
  useForward: koishi_1.Schema.boolean()
98
98
  .description('纯文本模式使用合并转发,避免刷屏')
99
99
  .default(true),
@@ -113,8 +113,7 @@ exports.Config = koishi_1.Schema.intersect([
113
113
  .default('localized'),
114
114
  scoreTtlHours: koishi_1.Schema.number()
115
115
  .min(1).step(1)
116
- .description('评分与评价人数的缓存小时数。新番开播头几周评分天天在变,'
117
- + '因此单独给一个比标题封面短得多的保质期')
116
+ .description('评分缓存小时数')
118
117
  .default(constants_1.DEFAULT_SCORE_TTL_HOURS),
119
118
  cacheTtlDays: koishi_1.Schema.number()
120
119
  .min(1).step(1)
@@ -71,6 +71,14 @@ export declare const LOCALIZE_CACHE_FILE = "localize-cache.json";
71
71
  * 涨到几百,用 30 天缓存会一直显示「只有十几个人投票」时的噪声值。
72
72
  */
73
73
  export declare const DEFAULT_SCORE_TTL_HOURS = 24;
74
+ /**
75
+ * 显示评分所需的最少评价人数。
76
+ *
77
+ * 低于这个数就不显示评分:新番刚开播时可能只有一两个人投票,那个数字会随着样本增加
78
+ * 剧烈变化(实测至高之力 1 票时是 7.0,156 票时是 6.1)。宁可留空,也不要给读者一个
79
+ * 会误导人的数字。
80
+ */
81
+ export declare const MIN_SCORE_VOTES = 10;
74
82
  export declare const LOCALIZE_CACHE_VERSION = 6;
75
83
  /** 本地化缓存最大条目数,超出后按写入时间淘汰最旧的一半 */
76
84
  export declare const LOCALIZE_CACHE_MAX_ENTRIES = 5000;
package/lib/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LOG_MESSAGES = exports.RESPONSES = exports.MESSAGES = exports.IMAGE_THEME = exports.IMAGE_LAYOUT = exports.DEFAULT_FONT_FAMILY = exports.DEFAULT_JPEG_QUALITY = exports.BOLD_FONT_CANDIDATES = exports.FONT_CANDIDATES = exports.COVER_CACHE_MAX_FILES = exports.COVER_CACHE_TTL_MS = exports.COVER_CACHE_DIR = exports.COVER_MAX_BYTES = exports.COVER_TIMEOUT_MS = exports.COVER_CONCURRENCY = exports.LOCALIZE_CONCURRENCY = exports.EPISODE_QUERY_LIMIT = exports.LOCALIZE_CACHE_MAX_ENTRIES = exports.LOCALIZE_CACHE_VERSION = exports.DEFAULT_SCORE_TTL_HOURS = exports.LOCALIZE_CACHE_FILE = exports.LATE_NIGHT_END_HOUR = exports.IMAGE_LIST_TWO_COLUMN_AT = exports.IMAGE_MAX_CARDS = exports.DIGEST_LABELS = exports.PLUGIN_HOMEPAGE = exports.PLUGIN_VERSION = exports.DATA_DIR = exports.BANGUMI_DATA_FILE = exports.BANGUMI_DATA_FALLBACK_URLS = exports.ANILIST_MIN_INTERVAL_MS = exports.ANILIST_BATCH_SIZE = exports.ANILIST_API_URL = exports.BANGUMI_API_HOST = exports.PLUGIN_NAME = void 0;
3
+ exports.LOG_MESSAGES = exports.RESPONSES = exports.MESSAGES = exports.IMAGE_THEME = exports.IMAGE_LAYOUT = exports.DEFAULT_FONT_FAMILY = exports.DEFAULT_JPEG_QUALITY = exports.BOLD_FONT_CANDIDATES = exports.FONT_CANDIDATES = exports.COVER_CACHE_MAX_FILES = exports.COVER_CACHE_TTL_MS = exports.COVER_CACHE_DIR = exports.COVER_MAX_BYTES = exports.COVER_TIMEOUT_MS = exports.COVER_CONCURRENCY = exports.LOCALIZE_CONCURRENCY = exports.EPISODE_QUERY_LIMIT = exports.LOCALIZE_CACHE_MAX_ENTRIES = exports.LOCALIZE_CACHE_VERSION = exports.MIN_SCORE_VOTES = exports.DEFAULT_SCORE_TTL_HOURS = exports.LOCALIZE_CACHE_FILE = exports.LATE_NIGHT_END_HOUR = exports.IMAGE_LIST_TWO_COLUMN_AT = exports.IMAGE_MAX_CARDS = exports.DIGEST_LABELS = exports.PLUGIN_HOMEPAGE = exports.PLUGIN_VERSION = exports.DATA_DIR = exports.BANGUMI_DATA_FILE = exports.BANGUMI_DATA_FALLBACK_URLS = exports.ANILIST_MIN_INTERVAL_MS = exports.ANILIST_BATCH_SIZE = exports.ANILIST_API_URL = exports.BANGUMI_API_HOST = exports.PLUGIN_NAME = void 0;
4
4
  exports.buildUserAgent = buildUserAgent;
5
5
  exports.getMessages = getMessages;
6
6
  /** 插件名,同时作为日志前缀与 User-Agent 主体 */
@@ -87,6 +87,14 @@ exports.LOCALIZE_CACHE_FILE = 'localize-cache.json';
87
87
  * 涨到几百,用 30 天缓存会一直显示「只有十几个人投票」时的噪声值。
88
88
  */
89
89
  exports.DEFAULT_SCORE_TTL_HOURS = 24;
90
+ /**
91
+ * 显示评分所需的最少评价人数。
92
+ *
93
+ * 低于这个数就不显示评分:新番刚开播时可能只有一两个人投票,那个数字会随着样本增加
94
+ * 剧烈变化(实测至高之力 1 票时是 7.0,156 票时是 6.1)。宁可留空,也不要给读者一个
95
+ * 会误导人的数字。
96
+ */
97
+ exports.MIN_SCORE_VOTES = 10;
90
98
  exports.LOCALIZE_CACHE_VERSION = 6;
91
99
  /** 本地化缓存最大条目数,超出后按写入时间淘汰最旧的一半 */
92
100
  exports.LOCALIZE_CACHE_MAX_ENTRIES = 5000;
@@ -221,6 +221,16 @@ export declare class DigestService {
221
221
  private hasEnded;
222
222
  /** 按 Bangumi 条目 ID 批量取详情 */
223
223
  private subjectsOf;
224
+ /**
225
+ * 只为「闸门 3 真的要判」的作品取详情。
226
+ *
227
+ * 闸门 3 的第一件事就是跳过 AniList 已确认同季的条目,因此那部分详情取来也用不上。
228
+ * 这里用**同一套短路条件**先筛一遍,避免为它们白跑 `/v0/episodes`。
229
+ *
230
+ * 条件必须与 `dropFinishedByEpisodeDates` 里的短路保持一致,否则会出现
231
+ * 「这里不取、那里要用」的空判定。
232
+ */
233
+ private subjectsForEpisodeDates;
224
234
  /**
225
235
  * 推算某部作品在 `fromMs` 之后的下一场播出。
226
236
  *
@@ -710,6 +710,27 @@ class DigestService {
710
710
  .filter((id) => id !== null);
711
711
  return this.localizer.localizeByIds(ids);
712
712
  }
713
+ /**
714
+ * 只为「闸门 3 真的要判」的作品取详情。
715
+ *
716
+ * 闸门 3 的第一件事就是跳过 AniList 已确认同季的条目,因此那部分详情取来也用不上。
717
+ * 这里用**同一套短路条件**先筛一遍,避免为它们白跑 `/v0/episodes`。
718
+ *
719
+ * 条件必须与 `dropFinishedByEpisodeDates` 里的短路保持一致,否则会出现
720
+ * 「这里不取、那里要用」的空判定。
721
+ */
722
+ async subjectsForEpisodeDates(candidates, airing) {
723
+ const wanted = candidates.filter((candidate) => {
724
+ const state = candidate.entry.aniListId === null ? undefined : airing?.get(candidate.entry.aniListId);
725
+ // AniList 已表态(同一季)→ 闸门 3 不参与,详情不必取
726
+ return !(state && this.isSameAniListSeason(state, candidate.entry.broadcastMs));
727
+ });
728
+ if (wanted.length === candidates.length)
729
+ return this.subjectsOf(candidates);
730
+ const subjects = await this.subjectsOf(wanted);
731
+ (0, utils_1.debugLog)('[digest] 闸门 3:%d 部幸存者中只有 %d 部需要 Bangumi 逐集档期(其余由 AniList 定论)', candidates.length, wanted.length);
732
+ return subjects;
733
+ }
713
734
  /**
714
735
  * 推算某部作品在 `fromMs` 之后的下一场播出。
715
736
  *
@@ -759,8 +780,13 @@ class DigestService {
759
780
  ? original
760
781
  : style === 'localized' ? localized
761
782
  : localized === original ? original : `${localized} (${original})`;
783
+ // 样本太少时评分没有参考价值:新番刚开播时可能只有一两个人投票,那个数字会随着
784
+ // 样本增加剧烈变化(实测至高之力 1 票时是 7.0,156 票时是 6.1)。宁可留空也不要
785
+ // 给读者一个会误导人的数字。
786
+ const votes = subject?.ratingTotal ?? 0;
762
787
  const scoreValue = this.localizer.enabled && output.showScore
763
788
  && typeof subject?.score === 'number' && subject.score > 0
789
+ && votes >= constants_1.MIN_SCORE_VOTES
764
790
  ? subject.score
765
791
  : null;
766
792
  const time = (0, time_1.formatClock)(candidate.atMs, this.timeZone);
@@ -874,9 +900,15 @@ class DigestService {
874
900
  const locallyKept = this.dropLocallyFinished(predicted, options.fromMs, following);
875
901
  const airingStates = await this.airingStatesOf(locallyKept);
876
902
  const survived = this.dropFinishedByAniList(locallyKept, options.fromMs, airingStates);
877
- // ── 闸门 3:只对幸存者取 Bangumi 详情与逐集档期 ──
878
- const subjects = await this.subjectsOf(survived);
879
- const concluded = this.dropFinishedByEpisodeDates(survived, options.fromMs, subjects, airingStates);
903
+ // ── 闸门 3:只为**真正要判的**作品取 Bangumi 详情 ──
904
+ //
905
+ // 闸门 3 一开头就短路「AniList 已确认同季」的条目,这类占了绝大多数——实测周表
906
+ // 77 部幸存者里只有 13 部真要看逐集档期。早先无条件给全部幸存者取详情,等于为
907
+ // 60 多部白跑请求。这里先算出真正需要的 ID,只取它们。
908
+ const concluded = this.dropFinishedByEpisodeDates(survived, options.fromMs, await this.subjectsForEpisodeDates(survived, airingStates), airingStates);
909
+ // 展示用的详情(标题、封面、评分)另行批量取:闸门 3 里取过的会命中缓存,
910
+ // 没取过的才发请求,因此不会重复。
911
+ const subjects = await this.subjectsOf(concluded);
880
912
  const scheduled = this.applyAiringSchedule(concluded, airingStates, options);
881
913
  const built = await this.buildItems(scheduled, subscribed, subjects, airingStates);
882
914
  // 可选:剔除超长期番(默认关闭,见 `content.longRunningThreshold`)。
@@ -1105,8 +1137,9 @@ class DigestService {
1105
1137
  const locallyKept = this.dropLocallyFinished(candidates, startMs, following);
1106
1138
  const airingStates = await this.airingStatesOf(locallyKept);
1107
1139
  const survived = this.dropFinishedByAniList(locallyKept, startMs, airingStates);
1108
- const subjects = await this.subjectsOf(survived);
1109
- const airing = this.dropFinishedByEpisodeDates(survived, startMs, subjects, airingStates);
1140
+ // 与 `buildAirings` 同一套做法:只为闸门 3 真正要判的作品取详情
1141
+ const airing = this.dropFinishedByEpisodeDates(survived, startMs, await this.subjectsForEpisodeDates(survived, airingStates), airingStates);
1142
+ const subjects = await this.subjectsOf(airing);
1110
1143
  const items = await this.buildItems(airing, wanted, subjects, airingStates);
1111
1144
  (0, utils_1.debugLog)('[digest] 订阅排期: %d 条订阅 → 未来 %d 天内 %d 场', tids.length, days, items.length);
1112
1145
  return items.sort((a, b) => a.atMs - b.atMs);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-anime-schedule",
3
3
  "description": "番剧更新表与订阅提醒:每日 / 深夜档 / 本周 / 本季四张表,支持单部订阅",
4
- "version": "1.0.5",
4
+ "version": "1.0.7",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -14,6 +14,14 @@
14
14
  "typecheck": "tsc -p tsconfig.json --noEmit"
15
15
  },
16
16
  "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/YuzuharaYuka/koishi-plugin-anime-schedule.git"
20
+ },
21
+ "homepage": "https://github.com/YuzuharaYuka/koishi-plugin-anime-schedule#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/YuzuharaYuka/koishi-plugin-anime-schedule/issues"
24
+ },
17
25
  "keywords": [
18
26
  "chatbot",
19
27
  "koishi",
@@ -30,7 +38,7 @@
30
38
  },
31
39
  "dependencies": {
32
40
  "@napi-rs/canvas": "^1.0.10",
33
- "bangumi-data": "^0.3.229"
41
+ "bangumi-data": "^0.3.230"
34
42
  },
35
43
  "koishi": {
36
44
  "description": {
package/readme.md CHANGED
@@ -56,7 +56,8 @@ npm i koishi-plugin-anime-schedule
56
56
  | `output.format` | `image` | `image` / `text` |
57
57
  | `output.layout` | `table` | 图片排版:`table` / `list` |
58
58
  | `localize.titleStyle` | `localized` | `localized` 仅中文 / `both` 中文(日文原名)/ `original` 仅日文原名 |
59
- | `localize.scoreTtlHours` | `24` | 评分与评价人数的缓存小时数。新番开播头几周评分天天在变,因此单独给一个较短的保质期 |
59
+ | `localize.scoreTtlHours` | `24` | 评分与评价人数的缓存小时数 |
60
+ | `output.showScore` | `true` | 是否显示评分,评价不足 10 人时不显示 |
60
61
  | `render.jpegQuality` | `100` | 图片编码质量,调低可减小体积 |
61
62
  | `advanced.debug` | `false` | 输出调试日志 |
62
63