diff --git a/src/app/api/douban/search/route.ts b/src/app/api/douban/search/route.ts new file mode 100644 index 0000000..d53967b --- /dev/null +++ b/src/app/api/douban/search/route.ts @@ -0,0 +1,50 @@ +import { NextRequest, NextResponse } from 'next/server'; + +import { fetchDoubanData } from '@/lib/douban'; + +export const runtime = 'nodejs'; + +interface DoubanSearchResult { + id: string; + title: string; + year: string; + type?: string; + sub_title?: string; + episode?: string; + img?: string; +} + +interface DoubanSearchResponse { + code: number; + data?: DoubanSearchResult[]; +} + +/** + * GET /api/douban/search?q= + * 搜索豆瓣影视作品 + */ +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url); + const query = searchParams.get('q'); + + if (!query) { + return NextResponse.json({ error: '缺少搜索关键词' }, { status: 400 }); + } + + try { + const target = `https://movie.douban.com/j/subject_suggest?q=${encodeURIComponent(query)}`; + const data = await fetchDoubanData(target); + + const response: DoubanSearchResponse = { + code: 200, + data: data, + }; + + return NextResponse.json(response); + } catch (error) { + return NextResponse.json( + { error: '搜索豆瓣数据失败', details: (error as Error).message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/xiaoya/play/route.ts b/src/app/api/xiaoya/play/route.ts index 34d66ae..b621d26 100644 --- a/src/app/api/xiaoya/play/route.ts +++ b/src/app/api/xiaoya/play/route.ts @@ -8,9 +8,58 @@ import { XiaoyaClient } from '@/lib/xiaoya.client'; export const runtime = 'nodejs'; +/** + * 使用 HEAD 请求跟随重定向获取最终 URL(直连方法 - 降级使用) + */ +async function getFinalUrl(url: string, maxRedirects = 5): Promise { + let currentUrl = url; + let redirectCount = 0; + + while (redirectCount < maxRedirects) { + try { + const response = await fetch(currentUrl, { + method: 'HEAD', + redirect: 'manual', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + }, + }); + + if (response.status >= 300 && response.status < 400) { + const location = response.headers.get('location'); + if (!location) { + return currentUrl; + } + + if (location.startsWith('http://') || location.startsWith('https://')) { + currentUrl = location; + } else if (location.startsWith('/')) { + const urlObj = new URL(currentUrl); + currentUrl = `${urlObj.protocol}//${urlObj.host}${location}`; + } else { + const urlObj = new URL(currentUrl); + const pathParts = urlObj.pathname.split('/'); + pathParts.pop(); + pathParts.push(location); + currentUrl = `${urlObj.protocol}//${urlObj.host}${pathParts.join('/')}`; + } + + redirectCount++; + } else { + return currentUrl; + } + } catch (error) { + console.error('[xiaoya/play] 获取最终 URL 失败:', error); + return currentUrl; + } + } + + return currentUrl; +} + /** * GET /api/xiaoya/play?path= - * 获取小雅视频的播放链接 + * 获取小雅视频的播放链接(优先使用视频预览流,失败时降级到直连) */ export async function GET(request: NextRequest) { try { @@ -44,10 +93,70 @@ export async function GET(request: NextRequest) { xiaoyaConfig.Token ); - const playUrl = await client.getDownloadUrl(path); + // 优先尝试视频预览流方法 + try { + const token = await client.getToken(); - // 返回 302 重定向到播放链接 - return NextResponse.redirect(playUrl); + const response = await fetch(`${xiaoyaConfig.ServerURL}/api/fs/other`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': token, + }, + body: JSON.stringify({ + path: path, + method: 'video_preview', + password: '', + }), + }); + + if (!response.ok) { + throw new Error(`视频预览请求失败: ${response.status}`); + } + + const data = await response.json(); + + if (data.code !== 200) { + throw new Error(`视频预览失败: ${data.message}`); + } + + const taskList = data.data?.video_preview_play_info?.live_transcoding_task_list; + if (!taskList || taskList.length === 0) { + throw new Error('未找到可用的播放链接'); + } + + const qualityOrder: Record = { + 'FHD': 1, + 'HD': 2, + 'SD': 3, + 'LD': 4, + }; + + const qualities = taskList + .filter((task: any) => task.status === 'finished') + .map((task: any) => ({ + name: task.template_id, + url: task.url, + })) + .sort((a, b) => (qualityOrder[a.name] || 999) - (qualityOrder[b.name] || 999)); + + if (qualities.length === 0) { + throw new Error('未找到已完成的播放链接'); + } + + return NextResponse.json({ + url: qualities[0].url, + qualities + }); + } catch (error) { + // 视频预览流失败,降级到直连方法 + console.log('[xiaoya/play] 视频预览流失败,降级到直连方法:', (error as Error).message); + + const playUrl = await client.getDownloadUrl(path); + const finalUrl = await getFinalUrl(playUrl); + + return NextResponse.json({ url: finalUrl }); + } } catch (error) { return NextResponse.json( { error: (error as Error).message }, diff --git a/src/app/api/xiaoya/search/route.ts b/src/app/api/xiaoya/search/route.ts index b2f81b1..da3b273 100644 --- a/src/app/api/xiaoya/search/route.ts +++ b/src/app/api/xiaoya/search/route.ts @@ -4,13 +4,12 @@ import { NextRequest, NextResponse } from 'next/server'; import { getAuthInfoFromCookie } from '@/lib/auth'; import { getConfig } from '@/lib/config'; -import { XiaoyaClient } from '@/lib/xiaoya.client'; export const runtime = 'nodejs'; /** - * GET /api/xiaoya/search?keyword=&page= - * 搜索小雅视频 + * GET /api/xiaoya/search?keyword=&type= + * 搜索小雅视频(使用小雅的网页搜索引擎) */ export async function GET(request: NextRequest) { try { @@ -21,7 +20,7 @@ export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const keyword = searchParams.get('keyword'); - const page = parseInt(searchParams.get('page') || '1'); + const type = searchParams.get('type') || 'video'; // video, music, ebook, all if (!keyword) { return NextResponse.json({ error: '缺少搜索关键词' }, { status: 400 }); @@ -38,34 +37,59 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: '小雅未配置或未启用' }, { status: 400 }); } - const client = new XiaoyaClient( - xiaoyaConfig.ServerURL, - xiaoyaConfig.Username, - xiaoyaConfig.Password, - xiaoyaConfig.Token - ); + // 使用小雅的搜索引擎 + const searchUrl = `${xiaoyaConfig.ServerURL}/search?box=${encodeURIComponent(keyword)}&type=${type}&url=`; - const result = await client.search(keyword, page, 50); + const response = await fetch(searchUrl, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + }, + }); - // 只返回视频文件 - const videoExtensions = ['.mp4', '.mkv', '.avi', '.m3u8', '.flv', '.ts', '.mov', '.wmv', '.webm']; + if (!response.ok) { + throw new Error(`搜索请求失败: ${response.status}`); + } - const videos = result.content - .filter(item => - !item.is_dir && - videoExtensions.some(ext => item.name.toLowerCase().endsWith(ext)) - ) - .map(item => ({ - name: item.name, - path: item.name, // Alist 搜索返回的是完整路径 - })); + const html = await response.text(); + + // 解析 HTML 中的链接 + // 格式: path/to/file + const linkRegex = /]+)>([^<]+)<\/a>/g; + const results: Array<{ name: string; path: string }> = []; + + let match; + while ((match = linkRegex.exec(html)) !== null) { + let path = match[1]; + const displayText = match[2]; + + // 跳过返回首页和频道链接 + if (path === '/' || path.startsWith('http')) { + continue; + } + + // URL 解码路径 + try { + path = decodeURIComponent(path); + } catch (e) { + console.error('URL 解码失败:', path, e); + } + + // 提取文件名(路径的最后一部分) + const pathParts = displayText.split('/'); + const fileName = pathParts[pathParts.length - 1]; + + results.push({ + name: fileName, + path: path, + }); + } return NextResponse.json({ - videos, - total: result.total, - page, + videos: results, + total: results.length, }); } catch (error) { + console.error('小雅搜索失败:', error); return NextResponse.json( { error: (error as Error).message }, { status: 500 } diff --git a/src/app/play/page.tsx b/src/app/play/page.tsx index c6bdcb5..1987a83 100644 --- a/src/app/play/page.tsx +++ b/src/app/play/page.tsx @@ -993,6 +993,11 @@ function PlayPageClient() { if (detCacheAge < detCacheMaxAge && data && data.backdrop) { console.log('使用缓存的TMDB详情数据'); setTmdbBackdrop(data.backdrop); + + // 如果没有豆瓣ID,使用TMDb数据补充 + if (!videoDoubanId || videoDoubanId === 0) { + populateDoubanFieldsFromTMDB(data); + } return; } } catch (e) { @@ -1023,6 +1028,11 @@ function PlayPageClient() { if (result.backdrop) { setTmdbBackdrop(result.backdrop); + // 如果没有豆瓣ID,使用TMDb数据补充 + if (!videoDoubanId || videoDoubanId === 0) { + populateDoubanFieldsFromTMDB(result); + } + // 保存title到tmdbId的映射到localStorage(1个月) if (result.tmdbId) { try { @@ -1056,13 +1066,42 @@ function PlayPageClient() { } }; + // 辅助函数:使用TMDb数据填充豆瓣字段 + const populateDoubanFieldsFromTMDB = (tmdbData: any) => { + // 设置评分 + if (tmdbData.rating) { + const ratingValue = parseFloat(tmdbData.rating); + setDoubanRating({ + value: ratingValue, + count: 0, // TMDb不提供评分人数 + star_count: Math.round(ratingValue / 2), // 转换为5星制 + }); + } + + // 设置年份 + if (tmdbData.releaseDate) { + const year = tmdbData.releaseDate.split('-')[0]; + setDoubanYear(year); + } + + // 设置card_subtitle(使用mediaType和年份) + if (tmdbData.mediaType && tmdbData.releaseDate) { + const year = tmdbData.releaseDate.split('-')[0]; + const typeText = tmdbData.mediaType === 'movie' ? '电影' : '电视剧'; + setDoubanCardSubtitle(`${year} / ${typeText}`); + } + }; + fetchTMDBBackdrop(); - }, [videoTitle]); + }, [videoTitle, videoDoubanId]); // 视频播放地址 const [videoUrl, setVideoUrl] = useState(''); + // 视频清晰度列表 + const [videoQualities, setVideoQualities] = useState>([]); + // 视频源代理模式状态 const [sourceProxyMode, setSourceProxyMode] = useState(false); @@ -1419,6 +1458,21 @@ function PlayPageClient() { detailData: SearchResult | null, episodeIndex: number ) => { + // 动态设置 referrer policy:只在小雅源时不发送 Referer + const existingMeta = document.querySelector('meta[name="referrer"]'); + if (detailData?.source === 'xiaoya') { + if (!existingMeta) { + const meta = document.createElement('meta'); + meta.name = 'referrer'; + meta.content = 'no-referrer'; + document.head.appendChild(meta); + } + } else { + if (existingMeta) { + existingMeta.remove(); + } + } + if ( !detailData || !detailData.episodes || @@ -1434,6 +1488,29 @@ function PlayPageClient() { let newUrl = detailData?.episodes[episodeIndex] || ''; + // 如果是小雅接口,先请求获取真实 URL + if (newUrl.startsWith('/api/xiaoya/play')) { + try { + const response = await fetch(newUrl); + const data = await response.json(); + if (data.url) { + newUrl = data.url; + // 保存清晰度列表 + if (data.qualities && data.qualities.length > 0) { + setVideoQualities(data.qualities); + } else { + setVideoQualities([]); + } + } + } catch (error) { + console.error('获取小雅播放链接失败:', error); + setVideoQualities([]); + } + } else { + // 非小雅源,清空清晰度列表 + setVideoQualities([]); + } + // 检查是否有本地下载的文件 const hasLocalFile = await checkLocalDownload(currentSource, currentId, episodeIndex); @@ -3992,10 +4069,17 @@ function PlayPageClient() { fastForward: true, autoOrientation: true, lock: true, + ...(videoQualities.length > 0 ? { + quality: videoQualities.map((q, index) => ({ + default: index === 0, + html: q.name, + url: q.url, + })), + } : {}), moreVideoAttr: { - crossOrigin: 'anonymous', playsInline: true, 'webkit-playsinline': 'true', + ...(detail?.source === 'xiaoya' ? { referrerpolicy: 'no-referrer' } : {}), } as any, // HLS 支持配置 customType: { diff --git a/src/app/private-library/page.tsx b/src/app/private-library/page.tsx index b792a99..e7949db 100644 --- a/src/app/private-library/page.tsx +++ b/src/app/private-library/page.tsx @@ -76,6 +76,10 @@ export default function PrivateLibraryPage() { const [xiaoyaPath, setXiaoyaPath] = useState('/'); const [xiaoyaFolders, setXiaoyaFolders] = useState>([]); const [xiaoyaFiles, setXiaoyaFiles] = useState>([]); + const [xiaoyaSearchKeyword, setXiaoyaSearchKeyword] = useState(''); + const [xiaoyaSearchResults, setXiaoyaSearchResults] = useState>([]); + const [isSearching, setIsSearching] = useState(false); + const [mounted, setMounted] = useState(false); const pageSize = 20; const observerTarget = useRef(null); const isFetchingRef = useRef(false); @@ -88,6 +92,38 @@ export default function PrivateLibraryPage() { const isInitializedRef = useRef(false); const hasRestoredViewRef = useRef(false); + // 客户端挂载标记 + useEffect(() => { + setMounted(true); + }, []); + + // 小雅搜索处理函数 + const handleXiaoyaSearch = async () => { + if (!xiaoyaSearchKeyword.trim()) return; + + setIsSearching(true); + try { + const response = await fetch(`/api/xiaoya/search?keyword=${encodeURIComponent(xiaoyaSearchKeyword)}`); + if (!response.ok) { + throw new Error('搜索失败'); + } + + const data = await response.json(); + if (data.error) { + setError(data.error); + setXiaoyaSearchResults([]); + } else { + setXiaoyaSearchResults(data.videos || []); + } + } catch (err) { + console.error('搜索失败:', err); + setError('搜索失败'); + setXiaoyaSearchResults([]); + } finally { + setIsSearching(false); + } + }; + // 从URL初始化状态,并检查配置自动跳转 useEffect(() => { const urlSourceParam = searchParams.get('source'); @@ -420,17 +456,19 @@ export default function PrivateLibraryPage() { {/* 第一级:源类型选择(OpenList / Emby / 小雅) */} -
- setSourceType(value as LibrarySourceType)} - /> -
+ {mounted && ( +
+ setSourceType(value as LibrarySourceType)} + /> +
+ )} {/* 第二级:Emby源选择(仅当选择Emby且有多个源时显示) */} {sourceType === 'emby' && embySourceOptions.length > 1 && ( @@ -548,17 +586,150 @@ export default function PrivateLibraryPage() { )} {loading ? ( -
- {Array.from({ length: pageSize }).map((_, index) => ( -
- ))} -
+ sourceType === 'xiaoya' ? ( + // 小雅加载骨架屏 - 文件夹列表样式 +
+ {/* 文件夹骨架屏 */} +
+
+
+ {Array.from({ length: 12 }).map((_, index) => ( +
+ ))} +
+
+
+ ) : ( + // OpenList/Emby 加载骨架屏 - 海报卡片样式 +
+ {Array.from({ length: pageSize }).map((_, index) => ( +
+ ))} +
+ ) ) : sourceType === 'xiaoya' ? ( // 小雅浏览模式
+ {/* 搜索框 */} +
+
+ setXiaoyaSearchKeyword(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && xiaoyaSearchKeyword.trim()) { + handleXiaoyaSearch(); + } + }} + className='w-full px-4 py-2 pr-10 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500' + /> + {xiaoyaSearchKeyword ? ( + + ) : ( + + )} +
+
+ + {/* 搜索结果 */} + {xiaoyaSearchResults.length > 0 ? ( +
+
+

+ 搜索结果 ({xiaoyaSearchResults.length}) +

+ +
+
+ {xiaoyaSearchResults.map((item) => { + // 判断是否为视频文件 + const videoExtensions = ['.mp4', '.mkv', '.avi', '.m3u8', '.flv', '.ts', '.mov', '.wmv', '.webm']; + const isVideoFile = videoExtensions.some(ext => item.name.toLowerCase().endsWith(ext)); + + // 从路径中提取文件夹名作为标题 + const pathParts = item.path.split('/').filter(Boolean); + const folderName = pathParts[pathParts.length - (isVideoFile ? 2 : 1)] || ''; + const title = folderName + .replace(/\s*\(\d{4}\)\s*\{tmdb-\d+\}$/i, '') + .trim() || item.name; + + return ( + + ); + })} +
+
+ ) : isSearching ? ( +
+
+
+ 搜索中... +
+
+ ) : ( + <> {/* 面包屑导航 */}
)} + + )}
) : videos.length === 0 ? (
diff --git a/src/components/EpisodeSelector.tsx b/src/components/EpisodeSelector.tsx index 0794870..53074bf 100644 --- a/src/components/EpisodeSelector.tsx +++ b/src/components/EpisodeSelector.tsx @@ -683,6 +683,13 @@ const EpisodeSelector: React.FC = ({ if (title.match(/^OVA\s+\d+/i)) { return title; } + // 如果匹配 S01E01 格式,提取并返回 + const sxxexxMatch = title.match(/[Ss](\d+)[Ee](\d{1,4}(?:\.\d+)?)/); + if (sxxexxMatch) { + const season = sxxexxMatch[1].padStart(2, '0'); + const episode = sxxexxMatch[2]; + return `S${season}E${episode}`; + } // 如果匹配"第X集"、"第X话"、"X集"、"X话"格式,提取中间的数字(支持小数) const match = title.match(/(?:第)?(\d+(?:\.\d+)?)(?:集|话)/); if (match) { diff --git a/src/lib/video-parser.ts b/src/lib/video-parser.ts index dc86e80..025de36 100644 --- a/src/lib/video-parser.ts +++ b/src/lib/video-parser.ts @@ -28,11 +28,11 @@ export function parseVideoFileName(fileName: string): ParsedVideoInfo { // 降级方案:使用多种正则模式提取集数 // 按优先级排序:更具体的模式优先 - const patterns: Array<{ pattern: RegExp; isOVA?: boolean }> = [ + const patterns: Array<{ pattern: RegExp; isOVA?: boolean; extractSeason?: boolean }> = [ // OVA01, OVA 01, ova01, ova 01 (OVA特殊处理) - 最优先 { pattern: /OVA\s*(\d+(?:\.\d+)?)/i, isOVA: true }, - // S01E01, s01e01, S01E01.5 (支持小数) - 最具体 - { pattern: /[Ss]\d+[Ee](\d+(?:\.\d+)?)/ }, + // S01E01, s01e01, S01E1234, S01E01.5 (支持1-4位数字和小数) - 最具体 + { pattern: /[Ss](\d+)[Ee](\d{1,4}(?:\.\d+)?)/, extractSeason: true }, // [01], (01), [01.5], (01.5) (支持小数,但要排除中文括号内容) - 很具体 { pattern: /[\[\(](\d+(?:\.\d+)?)[\]\)]/ }, // E01, E1, e01, e1, E01.5 (支持小数) @@ -45,12 +45,22 @@ export function parseVideoFileName(fileName: string): ParsedVideoInfo { { pattern: /^(\d+(?:\.\d+)?)[^\d.]/ }, ]; - for (const { pattern, isOVA } of patterns) { + for (const { pattern, isOVA, extractSeason } of patterns) { const match = fileName.match(pattern); if (match && match[1]) { - const episode = parseFloat(match[1]); - if (episode > 0 && episode < 10000) { // 合理的集数范围 - return { episode, isOVA }; + if (extractSeason && match[2]) { + // 同时提取 season 和 episode + const season = parseInt(match[1]); + const episode = parseFloat(match[2]); + if (season > 0 && season < 100 && episode > 0 && episode < 10000) { + return { season, episode }; + } + } else { + // 只提取 episode + const episode = parseFloat(match[1]); + if (episode > 0 && episode < 10000) { + return { episode, isOVA }; + } } } } diff --git a/src/lib/xiaoya-metadata.ts b/src/lib/xiaoya-metadata.ts index a49e992..fa94c55 100644 --- a/src/lib/xiaoya-metadata.ts +++ b/src/lib/xiaoya-metadata.ts @@ -139,7 +139,7 @@ export async function getXiaoyaMetadata( }; } - // 优先级 3: 实时搜索 TMDb + // 优先级 3: 实时搜索 TMDb(使用文件名) if (tmdbApiKey) { const fileName = pathParts[pathParts.length - 1]; const searchQuery = fileName @@ -147,6 +147,39 @@ export async function getXiaoyaMetadata( .replace(/[\[\]()]/g, ' ') .trim(); + // 如果文件名是纯数字(可能带小数点)或者是 SxxExx 格式,跳过文件名搜索,直接使用文件夹名 + const isPureNumber = /^[\d.]+$/.test(searchQuery); + const isSeasonEpisode = /^S\d+E\d+/i.test(searchQuery); + + if (!isPureNumber && !isSeasonEpisode) { + const { searchTMDB, getTMDBImageUrl } = await import('./tmdb.search'); + const tmdbResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy); + + if (tmdbResult.code === 200 && tmdbResult.result) { + return { + tmdbId: tmdbResult.result.id, + title: tmdbResult.result.title || tmdbResult.result.name || folderName, + year: tmdbResult.result.release_date?.substring(0, 4) || + tmdbResult.result.first_air_date?.substring(0, 4), + rating: tmdbResult.result.vote_average, + plot: tmdbResult.result.overview, + poster: tmdbResult.result.poster_path + ? getTMDBImageUrl(tmdbResult.result.poster_path) + : undefined, + mediaType: tmdbResult.result.media_type, + source: 'tmdb', + }; + } + } + } + + // 优先级 4: 实时搜索 TMDb(使用文件夹名) + if (tmdbApiKey) { + const searchQuery = folderName + .replace(/[\[\](){}]/g, ' ') + .replace(/\d{4}/g, '') + .trim(); + const { searchTMDB, getTMDBImageUrl } = await import('./tmdb.search'); const tmdbResult = await searchTMDB(tmdbApiKey, searchQuery, tmdbProxy); @@ -200,6 +233,7 @@ export async function getXiaoyaEpisodes( return videoFiles.map(file => { const parsed = parseVideoFileName(file.name); + console.log('[xiaoya-metadata] 解析文件名:', file.name, '结果:', parsed); let title = file.name; if (parsed.season && parsed.episode) { @@ -214,11 +248,21 @@ export async function getXiaoyaEpisodes( }; }); } else { - // 电影:只有一个文件 - const fileName = pathParts[pathParts.length - 1]; - return [{ - path: videoPath, - title: fileName, - }]; + // 电影:列出同一文件夹下的所有视频 + const parentDir = pathParts.slice(0, -1).join('/'); + const listResponse = await xiaoyaClient.listDirectory(`/${parentDir}`); + + const videoExtensions = ['.mp4', '.mkv', '.avi', '.m3u8', '.flv', '.ts', '.mov', '.wmv', '.webm']; + const videoFiles = listResponse.content + .filter(item => + !item.is_dir && + videoExtensions.some(ext => item.name.toLowerCase().endsWith(ext)) + ) + .sort((a, b) => a.name.localeCompare(b.name)); + + return videoFiles.map(file => ({ + path: `/${parentDir}/${file.name}`, + title: file.name, + })); } } diff --git a/src/lib/xiaoya.client.ts b/src/lib/xiaoya.client.ts index c297305..a8c6988 100644 --- a/src/lib/xiaoya.client.ts +++ b/src/lib/xiaoya.client.ts @@ -59,7 +59,7 @@ export class XiaoyaClient { /** * 获取缓存的 Token 或重新登录 */ - private async getToken(): Promise { + async getToken(): Promise { // 如果配置了 Token,直接使用 if (this.configToken) { return this.configToken;