mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-21 09:14:42 +08:00
feat: implement custom config
This commit is contained in:
@@ -23,6 +23,13 @@ export interface AdminConfig {
|
||||
from: 'config' | 'custom';
|
||||
disabled?: boolean;
|
||||
}[];
|
||||
CustomCategories: {
|
||||
name?: string;
|
||||
type: 'movie' | 'tv';
|
||||
query: string;
|
||||
from: 'config' | 'custom';
|
||||
disabled?: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface AdminConfigResult {
|
||||
|
||||
@@ -17,6 +17,11 @@ interface ConfigFileStruct {
|
||||
api_site: {
|
||||
[key: string]: ApiSite;
|
||||
};
|
||||
custom_category?: {
|
||||
name?: string;
|
||||
type: 'movie' | 'tv';
|
||||
query: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const API_CONFIG = {
|
||||
@@ -86,6 +91,7 @@ async function initConfig() {
|
||||
|
||||
// 从文件中获取源信息,用于补全源
|
||||
const apiSiteEntries = Object.entries(fileConfig.api_site);
|
||||
const customCategories = fileConfig.custom_category || [];
|
||||
|
||||
if (adminConfig) {
|
||||
// 补全 SourceConfig
|
||||
@@ -113,6 +119,37 @@ async function initConfig() {
|
||||
}
|
||||
});
|
||||
|
||||
// 确保 CustomCategories 被初始化
|
||||
if (!adminConfig.CustomCategories) {
|
||||
adminConfig.CustomCategories = [];
|
||||
}
|
||||
|
||||
// 补全 CustomCategories
|
||||
const existedCustomCategories = new Set(
|
||||
adminConfig.CustomCategories.map((c) => c.query + c.type)
|
||||
);
|
||||
customCategories.forEach((category) => {
|
||||
if (!existedCustomCategories.has(category.query + category.type)) {
|
||||
adminConfig!.CustomCategories.push({
|
||||
name: category.name,
|
||||
type: category.type,
|
||||
query: category.query,
|
||||
from: 'config',
|
||||
disabled: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 检查现有 CustomCategories 是否在 fileConfig.custom_category 中,如果不在则标记为 custom
|
||||
const customCategoriesKeys = new Set(
|
||||
customCategories.map((c) => c.query + c.type)
|
||||
);
|
||||
adminConfig.CustomCategories.forEach((category) => {
|
||||
if (!customCategoriesKeys.has(category.query + category.type)) {
|
||||
category.from = 'custom';
|
||||
}
|
||||
});
|
||||
|
||||
const existedUsers = new Set(
|
||||
(adminConfig.UserConfig.Users || []).map((u) => u.username)
|
||||
);
|
||||
@@ -173,6 +210,13 @@ async function initConfig() {
|
||||
from: 'config',
|
||||
disabled: false,
|
||||
})),
|
||||
CustomCategories: customCategories.map((category) => ({
|
||||
name: category.name,
|
||||
type: category.type,
|
||||
query: category.query,
|
||||
from: 'config',
|
||||
disabled: false,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -212,6 +256,14 @@ async function initConfig() {
|
||||
from: 'config',
|
||||
disabled: false,
|
||||
})),
|
||||
CustomCategories:
|
||||
fileConfig.custom_category?.map((category) => ({
|
||||
name: category.name,
|
||||
type: category.type,
|
||||
query: category.query,
|
||||
from: 'config',
|
||||
disabled: false,
|
||||
})) || [],
|
||||
} as AdminConfig;
|
||||
}
|
||||
}
|
||||
@@ -229,6 +281,11 @@ export async function getConfig(): Promise<AdminConfig> {
|
||||
adminConfig = await (storage as any).getAdminConfig();
|
||||
}
|
||||
if (adminConfig) {
|
||||
// 确保 CustomCategories 被初始化
|
||||
if (!adminConfig.CustomCategories) {
|
||||
adminConfig.CustomCategories = [];
|
||||
}
|
||||
|
||||
// 合并一些环境变量配置
|
||||
adminConfig.SiteConfig.SiteName = process.env.SITE_NAME || 'MoonTV';
|
||||
adminConfig.SiteConfig.Announcement =
|
||||
@@ -266,6 +323,33 @@ export async function getConfig(): Promise<AdminConfig> {
|
||||
}
|
||||
});
|
||||
|
||||
// 补全 CustomCategories
|
||||
const customCategories = fileConfig.custom_category || [];
|
||||
const existedCustomCategories = new Set(
|
||||
adminConfig.CustomCategories.map((c) => c.query + c.type)
|
||||
);
|
||||
customCategories.forEach((category) => {
|
||||
if (!existedCustomCategories.has(category.query + category.type)) {
|
||||
adminConfig!.CustomCategories.push({
|
||||
name: category.name,
|
||||
type: category.type,
|
||||
query: category.query,
|
||||
from: 'config',
|
||||
disabled: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 检查现有 CustomCategories 是否在 fileConfig.custom_categories 中,如果不在则标记为 custom
|
||||
const customCategoriesKeys = new Set(
|
||||
customCategories.map((c) => c.query + c.type)
|
||||
);
|
||||
adminConfig.CustomCategories.forEach((category) => {
|
||||
if (!customCategoriesKeys.has(category.query + category.type)) {
|
||||
category.from = 'custom';
|
||||
}
|
||||
});
|
||||
|
||||
const ownerUser = process.env.USERNAME || '';
|
||||
// 检查配置中的站长用户是否和 USERNAME 匹配,如果不匹配则降级为普通用户
|
||||
let containOwner = false;
|
||||
@@ -295,6 +379,7 @@ export async function getConfig(): Promise<AdminConfig> {
|
||||
}
|
||||
|
||||
export async function resetConfig() {
|
||||
const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage';
|
||||
const storage = getStorage();
|
||||
// 获取所有用户名,用于补全 Users
|
||||
let userNames: string[] = [];
|
||||
@@ -323,6 +408,7 @@ export async function resetConfig() {
|
||||
|
||||
// 从文件中获取源信息,用于补全源
|
||||
const apiSiteEntries = Object.entries(fileConfig.api_site);
|
||||
const customCategories = fileConfig.custom_category || [];
|
||||
let allUsers = userNames.map((uname) => ({
|
||||
username: uname,
|
||||
role: 'user',
|
||||
@@ -359,6 +445,16 @@ export async function resetConfig() {
|
||||
from: 'config',
|
||||
disabled: false,
|
||||
})),
|
||||
CustomCategories:
|
||||
storageType === 'redis'
|
||||
? customCategories?.map((category) => ({
|
||||
name: category.name,
|
||||
type: category.type,
|
||||
query: category.query,
|
||||
from: 'config',
|
||||
disabled: false,
|
||||
})) || []
|
||||
: [],
|
||||
} as AdminConfig;
|
||||
|
||||
if (storage && typeof (storage as any).setAdminConfig === 'function') {
|
||||
@@ -371,6 +467,7 @@ export async function resetConfig() {
|
||||
cachedConfig.SiteConfig = adminConfig.SiteConfig;
|
||||
cachedConfig.UserConfig = adminConfig.UserConfig;
|
||||
cachedConfig.SourceConfig = adminConfig.SourceConfig;
|
||||
cachedConfig.CustomCategories = adminConfig.CustomCategories;
|
||||
}
|
||||
|
||||
export async function getCacheTime(): Promise<number> {
|
||||
|
||||
@@ -146,3 +146,82 @@ export async function getDoubanCategories(
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
|
||||
interface DoubanListParams {
|
||||
tag: string;
|
||||
type: string;
|
||||
pageLimit?: number;
|
||||
pageStart?: number;
|
||||
}
|
||||
|
||||
export async function getDoubanList(
|
||||
params: DoubanListParams
|
||||
): Promise<DoubanResult> {
|
||||
const { tag, type, pageLimit = 20, pageStart = 0 } = params;
|
||||
if (shouldUseDoubanClient()) {
|
||||
// 使用客户端代理获取(当设置了代理 URL 时)
|
||||
return fetchDoubanList(params);
|
||||
} else {
|
||||
const response = await fetch(
|
||||
`/api/douban?tag=${tag}&type=${type}&limit=${pageLimit}&start=${pageStart}`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('获取豆瓣列表数据失败');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchDoubanList(
|
||||
params: DoubanListParams
|
||||
): Promise<DoubanResult> {
|
||||
const { tag, type, pageLimit = 20, pageStart = 0 } = params;
|
||||
|
||||
// 验证参数
|
||||
if (!tag || !type) {
|
||||
throw new Error('tag 和 type 参数不能为空');
|
||||
}
|
||||
|
||||
if (!['tv', 'movie'].includes(type)) {
|
||||
throw new Error('type 参数必须是 tv 或 movie');
|
||||
}
|
||||
|
||||
if (pageLimit < 1 || pageLimit > 100) {
|
||||
throw new Error('pageLimit 必须在 1-100 之间');
|
||||
}
|
||||
|
||||
if (pageStart < 0) {
|
||||
throw new Error('pageStart 不能小于 0');
|
||||
}
|
||||
|
||||
const target = `https://movie.douban.com/j/search_subjects?type=${type}&tag=${tag}&sort=recommend&page_limit=${pageLimit}&page_start=${pageStart}`;
|
||||
|
||||
try {
|
||||
const response = await fetchWithTimeout(target);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const doubanData: DoubanCategoryApiResponse = await response.json();
|
||||
|
||||
// 转换数据格式
|
||||
const list: DoubanItem[] = doubanData.items.map((item) => ({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
poster: item.pic?.normal || item.pic?.large || '',
|
||||
rate: item.rating?.value ? item.rating.value.toFixed(1) : '',
|
||||
year: item.card_subtitle?.match(/(\d{4})/)?.[1] || '',
|
||||
}));
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
list: list,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`获取豆瓣分类数据失败: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
'use client';
|
||||
|
||||
const CURRENT_VERSION = '20250730221204';
|
||||
const CURRENT_VERSION = '20250731010759';
|
||||
|
||||
// 版本检查结果枚举
|
||||
export enum UpdateStatus {
|
||||
|
||||
Reference in New Issue
Block a user