增加剧集更新检查
This commit is contained in:
@@ -53,6 +53,8 @@ export interface Favorite {
|
||||
save_time: number;
|
||||
search_title?: string;
|
||||
origin?: 'vod' | 'live';
|
||||
is_completed?: boolean; // 是否已完结
|
||||
vod_remarks?: string; // 视频备注信息
|
||||
}
|
||||
|
||||
// ---- 缓存数据结构 ----
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ function createStorage(): IStorage {
|
||||
// 单例存储实例
|
||||
let storageInstance: IStorage | null = null;
|
||||
|
||||
function getStorage(): IStorage {
|
||||
export function getStorage(): IStorage {
|
||||
if (!storageInstance) {
|
||||
storageInstance = createStorage();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -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 客户端
|
||||
|
||||
Reference in New Issue
Block a user