/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useContext, useEffect, useRef, useState } from 'react'; import { Banner, Button, Col, Form, Row, Modal, Space, Card, } from '@douyinfe/semi-ui'; import { API, showError, showSuccess, timestamp2string } from '../../helpers'; import { marked } from 'marked'; import { useTranslation } from 'react-i18next'; import { StatusContext } from '../../context/Status'; import Text from '@douyinfe/semi-ui/lib/es/typography/text'; const LEGAL_USER_AGREEMENT_KEY = 'legal.user_agreement'; const LEGAL_PRIVACY_POLICY_KEY = 'legal.privacy_policy'; const OtherSetting = () => { const { t } = useTranslation(); let [inputs, setInputs] = useState({ Notice: '', [LEGAL_USER_AGREEMENT_KEY]: '', [LEGAL_PRIVACY_POLICY_KEY]: '', SystemName: '', Logo: '', Footer: '', About: '', HomePageContent: '', }); let [loading, setLoading] = useState(false); const [showUpdateModal, setShowUpdateModal] = useState(false); const [statusState, statusDispatch] = useContext(StatusContext); const [updateData, setUpdateData] = useState({ tag_name: '', content: '', }); const updateOption = async (key, value) => { setLoading(true); const res = await API.put('/api/option/', { key, value, }); const { success, message } = res.data; if (success) { setInputs((inputs) => ({ ...inputs, [key]: value })); } else { showError(message); } setLoading(false); }; const [loadingInput, setLoadingInput] = useState({ Notice: false, [LEGAL_USER_AGREEMENT_KEY]: false, [LEGAL_PRIVACY_POLICY_KEY]: false, SystemName: false, Logo: false, HomePageContent: false, About: false, Footer: false, CheckUpdate: false, }); const handleInputChange = async (value, e) => { const name = e.target.id; setInputs((inputs) => ({ ...inputs, [name]: value })); }; // 通用设置 const formAPISettingGeneral = useRef(); // 通用设置 - Notice const submitNotice = async () => { try { setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: true })); await updateOption('Notice', inputs.Notice); showSuccess(t('公告已更新')); } catch (error) { console.error(t('公告更新失败'), error); showError(t('公告更新失败')); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false })); } }; // 通用设置 - UserAgreement const submitUserAgreement = async () => { try { setLoadingInput((loadingInput) => ({ ...loadingInput, [LEGAL_USER_AGREEMENT_KEY]: true, })); await updateOption( LEGAL_USER_AGREEMENT_KEY, inputs[LEGAL_USER_AGREEMENT_KEY], ); showSuccess(t('用户协议已更新')); } catch (error) { console.error(t('用户协议更新失败'), error); showError(t('用户协议更新失败')); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, [LEGAL_USER_AGREEMENT_KEY]: false, })); } }; // 通用设置 - PrivacyPolicy const submitPrivacyPolicy = async () => { try { setLoadingInput((loadingInput) => ({ ...loadingInput, [LEGAL_PRIVACY_POLICY_KEY]: true, })); await updateOption( LEGAL_PRIVACY_POLICY_KEY, inputs[LEGAL_PRIVACY_POLICY_KEY], ); showSuccess(t('隐私政策已更新')); } catch (error) { console.error(t('隐私政策更新失败'), error); showError(t('隐私政策更新失败')); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, [LEGAL_PRIVACY_POLICY_KEY]: false, })); } }; // 个性化设置 const formAPIPersonalization = useRef(); // 个性化设置 - SystemName const submitSystemName = async () => { try { setLoadingInput((loadingInput) => ({ ...loadingInput, SystemName: true, })); await updateOption('SystemName', inputs.SystemName); showSuccess(t('系统名称已更新')); } catch (error) { console.error(t('系统名称更新失败'), error); showError(t('系统名称更新失败')); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, SystemName: false, })); } }; // 个性化设置 - Logo const submitLogo = async () => { try { setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: true })); await updateOption('Logo', inputs.Logo); showSuccess('Logo 已更新'); } catch (error) { console.error('Logo 更新失败', error); showError('Logo 更新失败'); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: false })); } }; // 个性化设置 - 首页内容 const submitOption = async (key) => { try { setLoadingInput((loadingInput) => ({ ...loadingInput, HomePageContent: true, })); await updateOption(key, inputs[key]); showSuccess('首页内容已更新'); } catch (error) { console.error('首页内容更新失败', error); showError('首页内容更新失败'); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, HomePageContent: false, })); } }; // 个性化设置 - 关于 const submitAbout = async () => { try { setLoadingInput((loadingInput) => ({ ...loadingInput, About: true })); await updateOption('About', inputs.About); showSuccess('关于内容已更新'); } catch (error) { console.error('关于内容更新失败', error); showError('关于内容更新失败'); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, About: false })); } }; // 个性化设置 - 页脚 const submitFooter = async () => { try { setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: true })); await updateOption('Footer', inputs.Footer); showSuccess('页脚内容已更新'); } catch (error) { console.error('页脚内容更新失败', error); showError('页脚内容更新失败'); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: false })); } }; const checkUpdate = async () => { try { setLoadingInput((loadingInput) => ({ ...loadingInput, CheckUpdate: true, })); // Use a CORS proxy to avoid direct cross-origin requests to GitHub API // Option 1: Use a public CORS proxy service // const proxyUrl = 'https://cors-anywhere.herokuapp.com/'; // const res = await API.get( // `${proxyUrl}https://api.github.com/repos/Calcium-Ion/new-api/releases/latest`, // ); // Option 2: Use the JSON proxy approach which often works better with GitHub API const res = await fetch( 'https://api.github.com/repos/Calcium-Ion/new-api/releases/latest', { headers: { Accept: 'application/json', 'Content-Type': 'application/json', // Adding User-Agent which is often required by GitHub API 'User-Agent': 'new-api-update-checker', }, }, ).then((response) => response.json()); // Option 3: Use a local proxy endpoint // Create a cached version of the response to avoid frequent GitHub API calls // const res = await API.get('/api/status/github-latest-release'); const { tag_name, body } = res; if (tag_name === statusState?.status?.version) { showSuccess(`已是最新版本:${tag_name}`); } else { setUpdateData({ tag_name: tag_name, content: marked.parse(body), }); setShowUpdateModal(true); } } catch (error) { console.error('Failed to check for updates:', error); showError('检查更新失败,请稍后再试'); } finally { setLoadingInput((loadingInput) => ({ ...loadingInput, CheckUpdate: false, })); } }; const getOptions = async () => { const res = await API.get('/api/option/'); const { success, message, data } = res.data; if (success) { let newInputs = {}; data.forEach((item) => { if (item.key in inputs) { newInputs[item.key] = item.value; } }); setInputs(newInputs); formAPISettingGeneral.current.setValues(newInputs); formAPIPersonalization.current.setValues(newInputs); } else { showError(message); } }; useEffect(() => { getOptions(); }, []); // Function to open GitHub release page const openGitHubRelease = () => { window.open( `https://github.com/Calcium-Ion/new-api/releases/tag/${updateData.tag_name}`, '_blank', ); }; const getStartTimeString = () => { const timestamp = statusState?.status?.start_time; return statusState.status ? timestamp2string(timestamp) : ''; }; return ( {/* 版本信息 */}
{t('当前版本')}: {statusState?.status?.version || t('未知')} {t('启动时间')}:{getStartTimeString()}
{/* 通用设置 */}
(formAPISettingGeneral.current = formAPI)} >
{/* 个性化设置 */}
(formAPIPersonalization.current = formAPI)} > {/* */}
setShowUpdateModal(false)} footer={[ , ]} >
); }; export default OtherSetting;