@@ -0,0 +1,567 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import crypto from 'node:crypto' ;
const UC_SHARE_API_BASE = 'https://pc-api.uc.cn/1/clouddrive' ;
const UC_DRIVE_API_BASE = 'https://pc-api.uc.cn/1/clouddrive' ;
const UC_OPEN_API_BASE = 'https://open-api-drive.uc.cn' ;
const UC_QUERY_TEMPLATE = 'pr=UCBrowser&fr=pc&sys=darwin&ve=1.8.6&ut={ut}' ;
const UC_OPEN_API_CLIENT_ID = '5acf882d27b74502b7040b0c65519aa7' ;
const UC_OPEN_API_SIGN_KEY = 'l3srvtd7p42l0d0x1u8d7yc8ye9kki4d' ;
const UC_OPEN_API_APP_VER = '1.6.8' ;
const UC_OPEN_API_CHANNEL = 'UCTVOFFICIALWEB' ;
export interface UCShareLinkInfo {
pwdId : string ;
passcode : string ;
}
export interface UCShareItem {
fid : string ;
fileName : string ;
dir : boolean ;
shareFidToken? : string ;
pdirFid? : string ;
size? : number ;
}
export interface UCShareVideoListResult {
title : string ;
shareId : string ;
shareToken : string ;
files : Array < {
fid : string ;
name : string ;
size? : number ;
shareFidToken? : string ;
pdirFid? : string ;
} > ;
}
const VIDEO_EXTENSIONS = [
'.mp4' , '.mkv' , '.avi' , '.m3u8' , '.flv' , '.ts' , '.mov' , '.wmv' , '.webm' , '.rmvb' , '.rm' , '.mpg' , '.mpeg' , '.3gp' , '.f4v' , '.m4v' , '.vob' ,
] ;
const utCache = new Map < string , string > ( ) ;
function buildApiUrl ( base : string , path : string , ut : string , query = '' ) {
const normalizedPath = path . startsWith ( '/' ) ? path : ` / ${ path } ` ;
const queryPrefix = ut
? UC_QUERY_TEMPLATE . replace ( '{ut}' , encodeURIComponent ( ut ) )
: 'pr=UCBrowser&fr=pc&sys=darwin&ve=1.8.6' ;
return ` ${ base } ${ normalizedPath } ? ${ queryPrefix } ${ query ? ` & ${ query } ` : '' } ` ;
}
function getHeaders ( cookie : string ) : HeadersInit {
return {
'content-type' : 'application/json' ,
cookie ,
referer : 'https://drive.uc.cn' ,
'user-agent' :
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) uc-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch' ,
} ;
}
export function getUCPlayHeaders ( cookie : string ) : Record < string , string > {
const keepKeys = [ '_UP_A4A_11_' , 'tfstk' , '__uid' , '__pus' , '__kp' , '__puus' ] ;
const filteredCookie = cookie
. split ( ';' )
. map ( ( item ) = > item . trim ( ) )
. filter ( ( item ) = > keepKeys . some ( ( key ) = > item . includes ( key ) ) )
. join ( '; ' ) ;
return {
cookie : filteredCookie ,
referer : 'https://drive.uc.cn/' ,
'user-agent' :
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) uc-cloud-drive/1.8.6 Chrome/100.0.4896.160 Electron/18.3.5.16-b62cf9c50d Safari/537.36 Channel/ucpan_other_ch' ,
} ;
}
export function normalizeUCCookie ( cookie : string ) : string {
return cookie
. replace ( /; /g , ';' )
. replace ( /: /g , ':' )
. replace ( /, /g , ',' )
. trim ( ) ;
}
export function assertUCCookieHeaderSafe ( cookie : string ) : string {
const normalized = normalizeUCCookie ( cookie ) ;
for ( let i = 0 ; i < normalized . length ; i += 1 ) {
if ( normalized . charCodeAt ( i ) > 255 ) {
throw new Error ( 'UC Cookie 含有非法字符,请确认没有中文标点、中文空格或说明文字' ) ;
}
}
return normalized ;
}
function normalizePath ( path : string ) : string {
const trimmed = path . trim ( ) ;
if ( ! trimmed || trimmed === '/' ) return '/' ;
return ` / ${ trimmed . replace ( /^\/+|\/+$/g , '' ) } ` ;
}
function joinPath ( . . . parts : string [ ] ) {
const joined = parts . filter ( Boolean ) . join ( '/' ) . replace ( /\/+/g , '/' ) ;
return normalizePath ( joined ) ;
}
function sanitizeFolderName ( name : string ) {
return ( name || 'uc-temp' )
. replace ( /[<>:"/\\|?*]/g , ' ' )
. replace ( /[\r\n\t]/g , ' ' )
. replace ( /\s+/g , ' ' )
. trim ( )
. slice ( 0 , 80 ) ;
}
async function parseJson ( response : Response ) {
const text = await response . text ( ) ;
try {
return JSON . parse ( text ) ;
} catch {
throw new Error ( ` UC 接口返回异常: ${ text . slice ( 0 , 200 ) } ` ) ;
}
}
function ensureOk ( data : any , fallbackMessage : string ) {
if ( data ? . code === 0 || data ? . code === 200 || data ? . status === 200 ) return ;
throw new Error ( data ? . message || data ? . msg || fallbackMessage ) ;
}
async function resolveUCUt ( cookie : string ) {
const safeCookie = assertUCCookieHeaderSafe ( cookie ) ;
const cached = utCache . get ( safeCookie ) ;
if ( cached ) return cached ;
const tryFetchUt = async ( headers? : HeadersInit ) = > {
const response = await fetch ( ` ${ UC_DRIVE_API_BASE } /file ` , {
method : 'GET' ,
headers ,
cache : 'no-store' ,
} ) ;
const text = ( await response . text ( ) ) . trim ( ) ;
if ( ! response . ok || ! text ) return '' ;
return text ;
} ;
let ut = await tryFetchUt ( ) ;
if ( ! ut ) {
ut = await tryFetchUt ( getHeaders ( safeCookie ) ) ;
}
if ( ! ut ) {
utCache . set ( safeCookie , '' ) ;
return '' ;
}
utCache . set ( safeCookie , ut ) ;
return ut ;
}
function getDriveItemName ( item : any ) : string {
return String ( item ? . file_name || item ? . name || '' ) ;
}
function buildInstantPlayFolderName ( pwdId : string , title? : string ) {
const baseName = sanitizeFolderName ( title || 'uc-temp' ) || 'uc-temp' ;
return ` ${ baseName } _ ${ pwdId } ` . slice ( 0 , 120 ) ;
}
export function parseUCShareUrl ( url : string , passcode = '' ) : UCShareLinkInfo {
const parsed = new URL ( url ) ;
const pwdId = parsed . pathname . match ( /\/s\/([A-Za-z0-9_-]+)/ ) ? . [ 1 ] || '' ;
if ( ! pwdId ) {
throw new Error ( '无法解析 UC 分享链接' ) ;
}
return {
pwdId ,
passcode : passcode || parsed . searchParams . get ( 'pwd' ) || parsed . searchParams . get ( 'passcode' ) || '' ,
} ;
}
async function fetchShareToken ( cookie : string , share : UCShareLinkInfo , ut : string ) {
const response = await fetch ( buildApiUrl ( UC_SHARE_API_BASE , '/share/sharepage/token' , ut ) , {
method : 'POST' ,
headers : getHeaders ( cookie ) ,
body : JSON.stringify ( {
pwd_id : share.pwdId ,
passcode : share.passcode ,
} ) ,
cache : 'no-store' ,
} ) ;
const data = await parseJson ( response ) ;
ensureOk ( data , '获取 UC 分享 token 失败' ) ;
const stoken = data ? . data ? . stoken || data ? . data ? . share_token || data ? . data ? . token ;
if ( ! stoken ) {
throw new Error ( 'UC 分享 token 缺失' ) ;
}
return {
stoken ,
shareTitle : data?.data?.title || '' ,
} ;
}
async function fetchShareFolderItems ( cookie : string , pwdId : string , stoken : string , ut : string , pdirFid = '0' , page = 1 ) : Promise < { items : UCShareItem [ ] ; total? : number } > {
const query = new URLSearchParams ( {
pwd_id : pwdId ,
stoken ,
pdir_fid : pdirFid ,
force : '0' ,
_page : String ( page ) ,
_size : '100' ,
_sort : 'file_type:asc,file_name:asc' ,
} ) ;
const response = await fetch ( buildApiUrl ( UC_SHARE_API_BASE , '/share/sharepage/detail' , ut , query . toString ( ) ) , {
method : 'GET' ,
headers : getHeaders ( cookie ) ,
cache : 'no-store' ,
} ) ;
const data = await parseJson ( response ) ;
ensureOk ( data , '获取 UC 分享详情失败' ) ;
const list = data ? . data ? . list || [ ] ;
return {
items : list.map ( ( item : any ) = > ( {
fid : String ( item . fid || item . file_id || '' ) ,
fileName : String ( item . file_name || item . name || '' ) ,
dir : Boolean ( item . dir || item . is_dir || item . file_type === 0 ) ,
shareFidToken : item.share_fid_token || item . fid_token || item . share_token || undefined ,
pdirFid : String ( item . pdir_fid || pdirFid || '0' ) ,
size : Number ( item . size || 0 ) ,
} ) ) ,
total : Number ( data ? . metadata ? . _total || 0 ) ,
} ;
}
async function collectShareItemsRecursive ( cookie : string , pwdId : string , stoken : string , ut : string , pdirFid = '0' ) : Promise < UCShareItem [ ] > {
const result : UCShareItem [ ] = [ ] ;
const pageSize = 100 ;
for ( let page = 1 ; page < 100 ; page += 1 ) {
const { items , total } = await fetchShareFolderItems ( cookie , pwdId , stoken , ut , pdirFid , page ) ;
for ( const item of items ) {
if ( item . dir ) {
const children = await collectShareItemsRecursive ( cookie , pwdId , stoken , ut , item . fid ) ;
result . push ( . . . children ) ;
} else {
result . push ( item ) ;
}
}
if ( items . length < pageSize || page >= Math . ceil ( ( total || 0 ) / pageSize ) ) break ;
}
return result ;
}
function isVideoFile ( fileName : string ) {
const lower = fileName . toLowerCase ( ) ;
return VIDEO_EXTENSIONS . some ( ( ext ) = > lower . endsWith ( ext ) ) ;
}
async function fetchDriveFolderItems ( cookie : string , ut : string , pdirFid = '0' , page = 1 , size = 200 ) : Promise < any [ ] > {
const query = new URLSearchParams ( {
pdir_fid : pdirFid ,
_page : String ( page ) ,
_size : String ( size ) ,
_sort : 'file_type:asc,file_name:asc' ,
} ) ;
const response = await fetch ( buildApiUrl ( UC_DRIVE_API_BASE , '/file/sort' , ut , query . toString ( ) ) , {
method : 'GET' ,
headers : getHeaders ( cookie ) ,
cache : 'no-store' ,
} ) ;
const data = await parseJson ( response ) ;
ensureOk ( data , '获取 UC 目录列表失败' ) ;
return data ? . data ? . list || [ ] ;
}
async function fetchAllDriveFolderItems ( cookie : string , ut : string , pdirFid = '0' ) : Promise < any [ ] > {
const allItems : any [ ] = [ ] ;
const pageSize = 200 ;
for ( let page = 1 ; page < 100 ; page += 1 ) {
const items = await fetchDriveFolderItems ( cookie , ut , pdirFid , page , pageSize ) ;
allItems . push ( . . . items ) ;
if ( items . length < pageSize ) break ;
}
return allItems ;
}
async function createDriveFolder ( cookie : string , ut : string , parentFid : string , folderName : string ) {
const response = await fetch ( buildApiUrl ( UC_DRIVE_API_BASE , '/file' , ut ) , {
method : 'POST' ,
headers : getHeaders ( cookie ) ,
body : JSON.stringify ( {
pdir_fid : parentFid ,
file_name : folderName ,
dir_path : '' ,
dir_init_lock : false ,
} ) ,
cache : 'no-store' ,
} ) ;
const data = await parseJson ( response ) ;
ensureOk ( data , ` 创建 UC 目录失败: ${ folderName } ` ) ;
const fid = data ? . data ? . fid || data ? . data ? . file_id || data ? . metadata ? . fid ;
if ( ! fid ) throw new Error ( ` UC 目录创建成功但未返回 fid: ${ folderName } ` ) ;
return String ( fid ) ;
}
async function findDirectoryByName ( cookie : string , ut : string , parentFid : string , folderName : string ) : Promise < any | null > {
const items = await fetchAllDriveFolderItems ( cookie , ut , parentFid ) ;
return items . find ( ( item : any ) = > Boolean ( item . dir || item . is_dir ) && getDriveItemName ( item ) === folderName ) || null ;
}
export async function validateUCCookieReadable ( cookie : string ) : Promise < void > {
const safeCookie = assertUCCookieHeaderSafe ( cookie ) ;
const ut = await resolveUCUt ( safeCookie ) ;
await fetchDriveFolderItems ( safeCookie , ut , '0' ) ;
}
export async function listUCShareVideos ( shareUrl : string , cookie : string , passcode = '' ) : Promise < UCShareVideoListResult > {
const safeCookie = assertUCCookieHeaderSafe ( cookie ) ;
const ut = await resolveUCUt ( safeCookie ) ;
const share = parseUCShareUrl ( shareUrl , passcode ) ;
const { stoken , shareTitle } = await fetchShareToken ( safeCookie , share , ut ) ;
const allItems = await collectShareItemsRecursive ( safeCookie , share . pwdId , stoken , ut , '0' ) ;
const files = allItems
. filter ( ( item ) = > ! item . dir && isVideoFile ( item . fileName ) && Number ( item . size || 0 ) >= 5 * 1024 * 1024 )
. map ( ( item ) = > ( {
fid : item.fid ,
name : item.fileName ,
size : item.size ,
shareFidToken : item.shareFidToken ,
pdirFid : item.pdirFid ,
} ) ) ;
if ( files . length === 0 ) throw new Error ( '分享中没有可播放的视频文件' ) ;
return {
title : shareTitle || 'UC网盘立即播放' ,
shareId : share.pwdId ,
shareToken : stoken ,
files ,
} ;
}
export async function ensureUCDrivePath ( cookie : string , inputPath : string ) : Promise < { fid : string ; path : string } > {
const safeCookie = assertUCCookieHeaderSafe ( cookie ) ;
const ut = await resolveUCUt ( safeCookie ) ;
const normalized = normalizePath ( inputPath ) ;
if ( normalized === '/' ) return { fid : '0' , path : normalized } ;
const segments = normalized . split ( '/' ) . filter ( Boolean ) ;
let currentFid = '0' ;
let currentPath = '' ;
for ( const segment of segments ) {
const items = await fetchDriveFolderItems ( safeCookie , ut , currentFid ) ;
const existed = items . find ( ( item : any ) = > Boolean ( item . dir || item . is_dir ) && getDriveItemName ( item ) === segment ) ;
currentPath = joinPath ( currentPath , segment ) ;
if ( existed ) {
currentFid = String ( existed . fid || existed . file_id ) ;
continue ;
}
currentFid = await createDriveFolder ( safeCookie , ut , currentFid , segment ) ;
}
return { fid : currentFid , path : currentPath || '/' } ;
}
async function submitSaveTask ( cookie : string , ut : string , share : UCShareLinkInfo , stoken : string , toPdirFid : string , items : UCShareItem [ ] ) {
if ( items . length === 0 ) throw new Error ( '没有可保存的文件' ) ;
const response = await fetch ( buildApiUrl ( UC_SHARE_API_BASE , '/share/sharepage/save' , ut ) , {
method : 'POST' ,
headers : getHeaders ( cookie ) ,
body : JSON.stringify ( {
pwd_id : share.pwdId ,
stoken ,
pdir_fid : '0' ,
to_pdir_fid : toPdirFid ,
scene : 'link' ,
filelist : items.map ( ( item ) = > item . fid ) ,
fid_list : items.map ( ( item ) = > item . fid ) ,
fid_token_list : items.map ( ( item ) = > item . shareFidToken || '' ) ,
share_fid_token_list : items.map ( ( item ) = > item . shareFidToken || '' ) ,
} ) ,
cache : 'no-store' ,
} ) ;
const data = await parseJson ( response ) ;
ensureOk ( data , '提交 UC 转存任务失败' ) ;
return data ? . data ? . task_id ? String ( data . data . task_id ) : undefined ;
}
async function _pollTask ( cookie : string , ut : string , taskId : string ) {
for ( let i = 0 ; i < 25 ; i += 1 ) {
const query = new URLSearchParams ( { task_id : taskId , retry_index : String ( i ) } ) ;
const response = await fetch ( buildApiUrl ( UC_SHARE_API_BASE , '/task' , ut , query . toString ( ) ) , {
method : 'GET' , headers : getHeaders ( cookie ) , cache : 'no-store' ,
} ) ;
const data = await parseJson ( response ) ;
ensureOk ( data , '查询 UC 任务状态失败' ) ;
const task = data ? . data || { } ;
if ( task ? . status === 2 || task ? . status === 'finished' || task ? . status === 'success' || task ? . finished_at ) return ;
if ( task ? . status === - 1 || task ? . status === 'failed' || task ? . err_code ) {
throw new Error ( task ? . message || task ? . err_msg || 'UC 任务执行失败' ) ;
}
await new Promise ( ( resolve ) = > setTimeout ( resolve , 1200 ) ) ;
}
throw new Error ( 'UC 任务处理超时' ) ;
}
export async function ensureUCPlayFolder ( cookie : string , playTempSavePath : string , shareId : string , title? : string ) : Promise < { folderFid : string ; folderPath : string ; folderName : string } > {
const safeCookie = assertUCCookieHeaderSafe ( cookie ) ;
const ut = await resolveUCUt ( safeCookie ) ;
const tempRoot = await ensureUCDrivePath ( safeCookie , playTempSavePath ) ;
const folderName = buildInstantPlayFolderName ( shareId , title ) ;
const existedFolder = await findDirectoryByName ( safeCookie , ut , tempRoot . fid , folderName ) ;
if ( existedFolder ) {
return {
folderFid : String ( existedFolder . fid || existedFolder . file_id ) ,
folderPath : joinPath ( tempRoot . path , folderName ) ,
folderName ,
} ;
}
const folderFid = await createDriveFolder ( safeCookie , ut , tempRoot . fid , folderName ) ;
return { folderFid , folderPath : joinPath ( tempRoot . path , folderName ) , folderName } ;
}
export async function saveUCShareFile ( cookie : string , input : { shareId : string ; shareToken : string ; fileId : string ; shareFileToken? : string ; playFolderFid : string ; } ) : Promise < string > {
const safeCookie = assertUCCookieHeaderSafe ( cookie ) ;
const ut = await resolveUCUt ( safeCookie ) ;
const taskId = await submitSaveTask ( safeCookie , ut , { pwdId : input.shareId , passcode : '' } , input . shareToken , input . playFolderFid , [ { fid : input.fileId , fileName : '' , dir : false , shareFidToken : input.shareFileToken } ] ) ;
if ( ! taskId ) throw new Error ( 'UC 转存任务创建失败' ) ;
for ( let i = 0 ; i < 25 ; i += 1 ) {
const query = new URLSearchParams ( { task_id : taskId , retry_index : String ( i ) } ) ;
const response = await fetch ( buildApiUrl ( UC_SHARE_API_BASE , '/task' , ut , query . toString ( ) ) , { method : 'GET' , headers : getHeaders ( safeCookie ) , cache : 'no-store' } ) ;
const data = await parseJson ( response ) ;
ensureOk ( data , '查询 UC 任务状态失败' ) ;
const saveAsTopFids = data ? . data ? . save_as ? . save_as_top_fids ;
if ( Array . isArray ( saveAsTopFids ) && saveAsTopFids . length > 0 ) {
return String ( saveAsTopFids [ 0 ] ) ;
}
const status = data ? . data ? . status ;
if ( status === - 1 || status === 'failed' || data ? . data ? . err_code ) {
throw new Error ( data ? . data ? . message || data ? . data ? . err_msg || 'UC 任务执行失败' ) ;
}
if ( status === 2 || status === 'finished' || status === 'success' || data ? . data ? . finished_at ) break ;
await new Promise ( ( resolve ) = > setTimeout ( resolve , 1200 ) ) ;
}
throw new Error ( 'UC 转存结果获取失败' ) ;
}
function generateDeviceID ( timestamp : string ) {
return crypto . createHash ( 'md5' ) . update ( timestamp ) . digest ( 'hex' ) . slice ( 0 , 16 ) ;
}
function generateReqId ( deviceID : string , timestamp : string ) {
return crypto . createHash ( 'md5' ) . update ( deviceID + timestamp ) . digest ( 'hex' ) . slice ( 0 , 16 ) ;
}
function generateXPanToken ( method : string , pathname : string , timestamp : string , key : string ) {
return crypto . createHash ( 'sha256' ) . update ( ` ${ method } & ${ pathname } & ${ timestamp } & ${ key } ` ) . digest ( 'hex' ) ;
}
export async function getUCPlayUrls ( cookie : string , savedFileId : string , token = '' ) : Promise < Array < { name : string ; url : string ; priority : number ; headers ? : Record < string , string > } > > {
const safeCookie = assertUCCookieHeaderSafe ( cookie ) ;
const ut = await resolveUCUt ( safeCookie ) ;
const urls : Array < { name : string ; url : string ; priority : number ; headers? : Record < string , string > } > = [ ] ;
if ( token ) {
try {
const pathname = '/file' ;
const timestamp = ` ${ Math . floor ( Date . now ( ) / 1000 ) } 000 ` ;
const deviceId = generateDeviceID ( timestamp ) ;
const reqId = generateReqId ( deviceId , timestamp ) ;
const xPanToken = generateXPanToken ( 'GET' , pathname , timestamp , UC_OPEN_API_SIGN_KEY ) ;
const query = new URLSearchParams ( {
req_id : reqId ,
access_token : token ,
app_ver : UC_OPEN_API_APP_VER ,
device_id : deviceId ,
device_brand : 'Xiaomi' ,
platform : 'tv' ,
device_name : 'M2004J7AC' ,
device_model : 'M2004J7AC' ,
build_device : 'M2004J7AC' ,
build_product : 'M2004J7AC' ,
device_gpu : 'Adreno (TM) 550' ,
activity_rect : '{}' ,
channel : UC_OPEN_API_CHANNEL ,
method : 'streaming' ,
group_by : 'source' ,
fid : savedFileId ,
resolution : 'low,normal,high,super,2k,4k' ,
support : 'dolby_vision' ,
} ) ;
const response = await fetch ( ` ${ UC_OPEN_API_BASE } ${ pathname } ? ${ query . toString ( ) } ` , {
method : 'GET' ,
headers : {
'user-agent' : 'Mozilla/5.0 (Linux; Android 9) AppleWebKit/533.1 (KHTML, like Gecko) Mobile Safari/533.1' ,
connection : 'Keep-Alive' ,
'accept-encoding' : 'gzip' ,
'x-pan-tm' : timestamp ,
'x-pan-token' : xPanToken ,
'content-type' : 'text/plain;charset=UTF-8' ,
'x-pan-client-id' : UC_OPEN_API_CLIENT_ID ,
} ,
cache : 'no-store' ,
} ) ;
const data = await parseJson ( response ) ;
const openVideoInfo = Array . isArray ( data ? . data ? . video_info )
? data . data . video_info . find ( ( item : any ) = > item ? . accessable && item ? . url )
: null ;
if ( openVideoInfo ? . url ) {
urls . push ( { name : '原画' , url : String ( openVideoInfo . url ) , priority : 9999 , headers : { } } ) ;
}
} catch {
// ignore token failure, fallback to cookie mode
}
}
try {
const response = await fetch ( buildApiUrl ( UC_DRIVE_API_BASE , '/file/download' , ut ) , {
method : 'POST' ,
headers : getHeaders ( safeCookie ) ,
body : JSON.stringify ( { fids : [ savedFileId ] } ) ,
cache : 'no-store' ,
} ) ;
const data = await parseJson ( response ) ;
ensureOk ( data , '获取 UC 下载地址失败' ) ;
const downloadUrl = data ? . data ? . [ 0 ] ? . download_url ;
if ( downloadUrl ) {
urls . push ( { name : '原画' , url : String ( downloadUrl ) , priority : 9999 , headers : getUCPlayHeaders ( safeCookie ) } ) ;
}
} catch {
// ignore
}
try {
const response = await fetch ( buildApiUrl ( UC_DRIVE_API_BASE , '/file/v2/play' , ut ) , {
method : 'POST' ,
headers : getHeaders ( safeCookie ) ,
body : JSON.stringify ( {
fid : savedFileId ,
resolutions : 'normal,low,high,super,2k,4k' ,
supports : 'fmp4' ,
} ) ,
cache : 'no-store' ,
} ) ;
const data = await parseJson ( response ) ;
ensureOk ( data , '获取 UC 转码地址失败' ) ;
const nameMap : Record < string , string > = { FOUR_K : '4K' , SUPER : '超清' , HIGH : '高清' , NORMAL : '流畅' , LOW : '低清' } ;
if ( Array . isArray ( data ? . data ? . video_list ) ) {
for ( const video of data . data . video_list ) {
const resolution = video ? . video_info ? . resoultion ;
const playUrl = video ? . video_info ? . url ;
const priority = Number ( video ? . video_info ? . width || 0 ) ;
if ( resolution && playUrl ) {
urls . push ( { name : nameMap [ String ( resolution ) ] || String ( resolution ) , url : String ( playUrl ) , priority , headers : getUCPlayHeaders ( safeCookie ) } ) ;
}
}
}
} catch {
// ignore
}
const deduped = urls . filter ( ( item , index , array ) = > array . findIndex ( ( v ) = > v . url === item . url ) === index ) ;
deduped . sort ( ( a , b ) = > b . priority - a . priority ) ;
if ( deduped . length === 0 ) throw new Error ( '未获取到 UC 播放地址' ) ;
return deduped ;
}