增加剧集更新检查

This commit is contained in:
mtvpls
2025-12-18 23:44:14 +08:00
parent 0ae5923b4b
commit c60e25ded6
12 changed files with 864 additions and 9 deletions
+2
View File
@@ -53,6 +53,8 @@ export interface Favorite {
save_time: number;
search_title?: string;
origin?: 'vod' | 'live';
is_completed?: boolean; // 是否已完结
vod_remarks?: string; // 视频备注信息
}
// ---- 缓存数据结构 ----
+1 -1
View File
@@ -33,7 +33,7 @@ function createStorage(): IStorage {
// 单例存储实例
let storageInstance: IStorage | null = null;
function getStorage(): IStorage {
export function getStorage(): IStorage {
if (!storageInstance) {
storageInstance = createStorage();
}
+7
View File
@@ -16,6 +16,7 @@ interface ApiSearchItem {
vod_content?: string;
vod_douban_id?: number;
type_name?: string;
vod_total?: number;
}
/**
@@ -114,6 +115,8 @@ async function searchWithCache(
desc: cleanHtmlTags(item.vod_content || ''),
type_name: item.type_name,
douban_id: item.vod_douban_id,
vod_remarks: item.vod_remarks,
vod_total: item.vod_total,
};
});
@@ -283,6 +286,8 @@ export async function getDetailFromApi(
desc: cleanHtmlTags(videoDetail.vod_content),
type_name: videoDetail.type_name,
douban_id: videoDetail.vod_douban_id,
vod_remarks: videoDetail.vod_remarks,
vod_total: videoDetail.vod_total,
};
}
@@ -363,5 +368,7 @@ async function handleSpecialSourceDetail(
desc: descText,
type_name: '',
douban_id: 0,
vod_remarks: undefined,
vod_total: undefined,
};
}
+81
View File
@@ -517,4 +517,85 @@ export abstract class BaseRedisStorage implements IStorage {
async deleteGlobalValue(key: string): Promise<void> {
await this.withRetry(() => this.client.del(this.globalValueKey(key)));
}
// ---------- 通知相关 ----------
private notificationsKey(userName: string) {
return `u:${userName}:notifications`;
}
private lastFavoriteCheckKey(userName: string) {
return `u:${userName}:last_fav_check`;
}
async getNotifications(userName: string): Promise<import('./types').Notification[]> {
const val = await this.withRetry(() =>
this.client.get(this.notificationsKey(userName))
);
return val ? (JSON.parse(val) as import('./types').Notification[]) : [];
}
async addNotification(
userName: string,
notification: import('./types').Notification
): Promise<void> {
const notifications = await this.getNotifications(userName);
notifications.unshift(notification); // 新通知放在最前面
// 限制通知数量,最多保留100条
if (notifications.length > 100) {
notifications.splice(100);
}
await this.withRetry(() =>
this.client.set(this.notificationsKey(userName), JSON.stringify(notifications))
);
}
async markNotificationAsRead(
userName: string,
notificationId: string
): Promise<void> {
const notifications = await this.getNotifications(userName);
const notification = notifications.find((n) => n.id === notificationId);
if (notification) {
notification.read = true;
await this.withRetry(() =>
this.client.set(this.notificationsKey(userName), JSON.stringify(notifications))
);
}
}
async deleteNotification(
userName: string,
notificationId: string
): Promise<void> {
const notifications = await this.getNotifications(userName);
const filtered = notifications.filter((n) => n.id !== notificationId);
await this.withRetry(() =>
this.client.set(this.notificationsKey(userName), JSON.stringify(filtered))
);
}
async clearAllNotifications(userName: string): Promise<void> {
await this.withRetry(() => this.client.del(this.notificationsKey(userName)));
}
async getUnreadNotificationCount(userName: string): Promise<number> {
const notifications = await this.getNotifications(userName);
return notifications.filter((n) => !n.read).length;
}
async getLastFavoriteCheckTime(userName: string): Promise<number> {
const val = await this.withRetry(() =>
this.client.get(this.lastFavoriteCheckKey(userName))
);
return val ? parseInt(val, 10) : 0;
}
async setLastFavoriteCheckTime(
userName: string,
timestamp: number
): Promise<void> {
await this.withRetry(() =>
this.client.set(this.lastFavoriteCheckKey(userName), timestamp.toString())
);
}
}
+45
View File
@@ -24,6 +24,8 @@ export interface Favorite {
save_time: number; // 记录保存时间(时间戳)
search_title: string; // 搜索时使用的标题
origin?: 'vod' | 'live';
is_completed?: boolean; // 是否已完结
vod_remarks?: string; // 视频备注信息
}
// 存储接口
@@ -96,6 +98,18 @@ export interface IStorage {
getGlobalValue(key: string): Promise<string | null>;
setGlobalValue(key: string, value: string): Promise<void>;
deleteGlobalValue(key: string): Promise<void>;
// 通知相关
getNotifications(userName: string): Promise<Notification[]>;
addNotification(userName: string, notification: Notification): Promise<void>;
markNotificationAsRead(userName: string, notificationId: string): Promise<void>;
deleteNotification(userName: string, notificationId: string): Promise<void>;
clearAllNotifications(userName: string): Promise<void>;
getUnreadNotificationCount(userName: string): Promise<number>;
// 收藏更新检查相关
getLastFavoriteCheckTime(userName: string): Promise<number>;
setLastFavoriteCheckTime(userName: string, timestamp: number): Promise<void>;
}
// 搜索结果数据结构
@@ -112,6 +126,8 @@ export interface SearchResult {
desc?: string;
type_name?: string;
douban_id?: number;
vod_remarks?: string; // 视频备注信息(如"全80集"、"更新至25集"等)
vod_total?: number; // 总集数
}
// 豆瓣数据结构
@@ -161,3 +177,32 @@ export interface EpisodeFilterRule {
export interface EpisodeFilterConfig {
rules: EpisodeFilterRule[]; // 过滤规则列表
}
// 通知类型枚举
export type NotificationType =
| 'favorite_update' // 收藏更新
| 'system' // 系统通知
| 'announcement'; // 公告
// 通知数据结构
export interface Notification {
id: string; // 通知ID
type: NotificationType; // 通知类型
title: string; // 通知标题
message: string; // 通知内容
timestamp: number; // 通知时间戳
read: boolean; // 是否已读
metadata?: Record<string, any>; // 额外的元数据(如收藏更新的source、id等)
}
// 收藏更新检查结果
export interface FavoriteUpdateCheck {
last_check_time: number; // 上次检查时间戳
updates: Array<{
source: string;
id: string;
title: string;
old_episodes: number;
new_episodes: number;
}>;
}
+81
View File
@@ -415,6 +415,87 @@ export class UpstashRedisStorage implements IStorage {
async deleteGlobalValue(key: string): Promise<void> {
await withRetry(() => this.client.del(this.globalValueKey(key)));
}
// ---------- 通知相关 ----------
private notificationsKey(userName: string) {
return `u:${userName}:notifications`;
}
private lastFavoriteCheckKey(userName: string) {
return `u:${userName}:last_fav_check`;
}
async getNotifications(userName: string): Promise<import('./types').Notification[]> {
const val = await withRetry(() =>
this.client.get(this.notificationsKey(userName))
);
return val ? (val as import('./types').Notification[]) : [];
}
async addNotification(
userName: string,
notification: import('./types').Notification
): Promise<void> {
const notifications = await this.getNotifications(userName);
notifications.unshift(notification); // 新通知放在最前面
// 限制通知数量,最多保留100条
if (notifications.length > 100) {
notifications.splice(100);
}
await withRetry(() =>
this.client.set(this.notificationsKey(userName), notifications)
);
}
async markNotificationAsRead(
userName: string,
notificationId: string
): Promise<void> {
const notifications = await this.getNotifications(userName);
const notification = notifications.find((n) => n.id === notificationId);
if (notification) {
notification.read = true;
await withRetry(() =>
this.client.set(this.notificationsKey(userName), notifications)
);
}
}
async deleteNotification(
userName: string,
notificationId: string
): Promise<void> {
const notifications = await this.getNotifications(userName);
const filtered = notifications.filter((n) => n.id !== notificationId);
await withRetry(() =>
this.client.set(this.notificationsKey(userName), filtered)
);
}
async clearAllNotifications(userName: string): Promise<void> {
await withRetry(() => this.client.del(this.notificationsKey(userName)));
}
async getUnreadNotificationCount(userName: string): Promise<number> {
const notifications = await this.getNotifications(userName);
return notifications.filter((n) => !n.read).length;
}
async getLastFavoriteCheckTime(userName: string): Promise<number> {
const val = await withRetry(() =>
this.client.get(this.lastFavoriteCheckKey(userName))
);
return val ? (val as number) : 0;
}
async setLastFavoriteCheckTime(
userName: string,
timestamp: number
): Promise<void> {
await withRetry(() =>
this.client.set(this.lastFavoriteCheckKey(userName), timestamp)
);
}
}
// 单例 Upstash Redis 客户端