import type { Advertisement, Advertisements, Chapter, Comment, DramaDetail, PageData } from './api/types'; import type { PlayerInitialData } from './page-data'; import { recordList } from './api/protocol'; import { normalizeSeo } from './seo'; import { chapterAccess } from './client/playback'; function fields(value:object,strings:string[],numbers:string[],booleans:string[]=[]):Record { const data=value as Record,result:Record={}; for(const key of strings)if(typeof data[key]==='string')result[key]=data[key]; for(const key of numbers)if(typeof data[key]==='number'&&Number.isFinite(data[key]))result[key]=data[key]; for(const key of booleans)if(typeof data[key]==='boolean')result[key]=data[key]; return result; } function advertisements(data:Advertisements|undefined):Advertisements|undefined { if(!data||typeof data!=='object'||Array.isArray(data))return; const result:Advertisements={}; for(const key of ['nineGridList','detailbannerList'])result[key]=recordList(data[key]).map(item=>({ ...fields(item,['advertiseCode','advertiseLocationCode','advertiseLocationName'],['location']), id:typeof item.id==='string'?item.id:'',title:typeof item.title==='string'?item.title:'', images:typeof item.images==='string'?item.images:'',link:typeof item.link==='string'?item.link:'', })); return result; } export function publicDrama(data:DramaDetail):DramaDetail { const result=fields(data,['mediaId','title','playName','slug','desc','description','cover','horizontalCover','banner','coverImg','onlineAt','mediaCreatedAt','mediaUpdatedAt','createdAt','updatedAt','addedTime','anchor','preview','recommendTraceId','recommendId','recommendTraceInfo'],['id','totalEpisode','countLike','countCollect','countBrowse','heatValue','isCollect','isLike','watchTimes','updateStatus','operateType'],['isNew','isFeatured','showOnBanner']) as unknown as DramaDetail; for(const key of ['tagNames','themeNames','tagSlugs','themeSlugs'] as const)if(Array.isArray(data[key]))result[key]=data[key].filter(value=>typeof value==='string'); for(const key of ['tagIds','themeIds'] as const)if(Array.isArray(data[key]))result[key]=data[key].filter(Number.isSafeInteger); if(data.seoInfo)result.seoInfo=normalizeSeo(data.seoInfo); result.advitise=advertisements(data.advitise); if(data.video)result.video=fields(data.video,['url','cover'],['videoTime','width','height']); return result; } function publicChapter(data:Chapter):Chapter { const result=fields(data,['title','mediaId'],['id','playletId','currentEpisode','mediaTime','width','height','mediaSize','countBrowse','countCollect','countComment','countLike','ratio','payType','singleCoins','costCoins','balance','isCollect','isLike'],['canWatch','needUnlock','needVip','isUnlocked','vipValid']) as unknown as Chapter; if(data.seoInfo)result.seoInfo=normalizeSeo(data.seoInfo); // Protected sources are serialized only when this identity may actually watch them. if(chapterAccess(data).allowed&&typeof data.videoUrl==='string')result.videoUrl=data.videoUrl; return result; } function publicComment(data:Comment,depth=0):Comment { const result=fields(data,['text','userName','userAvatar','createdAt'],['id','isLike','countLike','parentsId']) as unknown as Comment; if(depth<4)result.children=recordList(data.children).map(item=>publicComment(item,depth+1)); return result; } /** Explicit DTO: no auth tokens, SDK configuration, or arbitrary upstream properties. */ export function playerInitialData(raw:DramaDetail,chapters:Chapter[],chapter:Chapter|null,comments?:PageData):PlayerInitialData { const detail=publicDrama(raw); detail.relatedRecommendData=recordList(raw.relatedRecommendData).filter(item=>Number.isSafeInteger(item.id)&&item.id>0).slice(0,6).map(publicDrama); return {detail,chapters:chapters.map(publicChapter),chapter:chapter?publicChapter(chapter):null, ...(comments?{comments:{list:recordList(comments.list).map(item=>publicComment(item)),...(typeof comments.error==='string'?{error:comments.error.slice(0,300)}:{}),totalCount:typeof comments.totalCount==='number'?comments.totalCount:0}}:{}), }; }