import * as AdmZip from 'adm-zip' ; import * as childProcess from 'child_process' ; import fetch from 'electron-fetch' ; import * as fs from 'fs' ; import * as path from 'path' ; import { UserConfigs } from './config' ; import { appDataDir , Global , GlobalPath , RemoteDomains , Logger } from './global' ; param toUpdate param latest export function updateObject ( toUpdate , latest ) { for ( const key in toUpdate ) { if ( typeof toUpdate [ key ] === 'object' && typeof latest [ key ] === 'object' ) { updateObject ( toUpdate [ key ] , latest [ key ] ) ; } else if ( latest [ key ] === undefined ) { delete toUpdate [ key ] ; } } for ( const key in latest ) { if ( toUpdate [ key ] === undefined && typeof latest [ key ] === 'object' ) { toUpdate [ key ] = { } ; fillObject ( toUpdate [ key ] , latest [ key ] ) ; } else { toUpdate [ key ] = latest [ key ] ; } } return toUpdate ; } param toFill param latest export function fillObject ( toFill , latest ) { for ( const key in latest ) { if ( typeof toFill [ key ] === 'object' && typeof latest [ key ] === 'object' ) { if ( ! Array . isArray ( toFill [ key ] ) ) { fillObject ( toFill [ key ] , latest [ key ] ) ; } } else if ( toFill [ key ] === undefined ) { if ( typeof latest [ key ] === 'object' ) { if ( Array . isArray ( latest [ key ] ) ) { toFill [ key ] = [ ... latest [ key ] ] ; } else { toFill [ key ] = { } ; updateObject ( toFill [ key ] , latest [ key ] ) ; } } else { toFill [ key ] = latest [ key ] ; } } } return toFill ; } param toClean param sample export function cleanObject ( toClean , sample ) { for ( const key in toClean ) { if ( sample [ key ] === undefined ) { delete toClean [ key ] ; } else if ( typeof sample [ key ] === 'object' && typeof toClean [ key ] === 'object' ) { cleanObject ( toClean [ key ] , sample [ key ] ) ; } } return toClean ; } export function XOR ( buffer ) { const array = [ ] ; for ( let index = 0 ; index < buffer . length ; index ++ ) { const byte = buffer . readUInt8 ( index ) ; array . push ( Global . XOR_KEY ^ byte ) ; } return Buffer . from ( array ) ; } export function isEncryptRes ( originalUrl ) { return originalUrl . includes ( Global . EXTEND_RES_KEYWORD ) ; } export function isPath ( originalUrl ) { return ( originalUrl . endsWith ( '\\' ) || originalUrl . endsWith ( '/' ) || originalUrl . includes ( '?' ) ) ; } param url param encrypt export async function getRemoteSource ( url , encrypt = false ) { const remoteUrl = getRemoteUrl ( url ) ; const resp = await fetch ( remoteUrl , { headers : { 'User-Agent' : Global . HttpGetUserAgent } } ) ; const statusCode = resp . status ; const fileData = await resp . buffer ( ) ; if ( statusCode === 302 || statusCode === 301 ) { return getRemoteSource ( resp . headers [ 'location' ] , encrypt ) ; } else { if ( statusCode < 200 || statusCode >= 400 ) { Logger . warning ( `从远端服务器请求 ${ remoteUrl } 失败, statusCode = ${ statusCode }` ) ; } return { code : resp . status , data : encrypt ? XOR ( fileData ) : fileData } ; } } export function mkdirPromise ( dirname ) { return new Promise ( ( resolve , reject ) => { fs . mkdir ( dirname , err => { if ( err ) { reject ( err ) ; } resolve ( ) ; } ) ; } ) ; } export function statPromise ( dirname ) { return new Promise ( ( resolve , reject ) => { fs . stat ( dirname , ( err , stats ) => { if ( err ) { reject ( err ) ; } resolve ( stats ) ; } ) ; } ) ; } export async function mkdirs ( dirname ) { try { await statPromise ( dirname ) ; } catch ( e ) { await mkdirs ( path . dirname ( dirname ) ) ; await mkdirPromise ( dirname ) . catch ( ( ) => { } ) ; } } export function mkdirsSync ( dirname ) { try { fs . statSync ( dirname ) ; } catch ( error ) { mkdirsSync ( path . dirname ( dirname ) ) ; fs . mkdirSync ( dirname ) ; } } export function getRemoteUrl ( originalUrl ) { return ( RemoteDomains [ UserConfigs . userData . serverToPlay ] . domain + originalUrl . replace ( /^\/\d\/?/g , '' ) ) ; } export function getLocalURI ( originalUrl ) { const dirBase = path . join ( appDataDir , GlobalPath . LocalDir ) ; return path . join ( dirBase , UserConfigs . userData . serverToPlay . toString ( ) , /^([^?]+)(\?.*)?$/ . exec ( originalUrl ) [ 1 ] ) ; } export async function getRemoteOrCachedFile ( url , encode = true , callback = data => data ) { const originalUrl = url . replace ( /^\/\d\//g , '' ) ; const isEncrypted = isEncryptRes ( originalUrl ) ; const isRoutePath = isPath ( originalUrl ) ; const localPath = getLocalURI ( originalUrl ) ; const ret = { code : 0 , data : undefined } ; let originData ; if ( ! isRoutePath && fs . existsSync ( localPath ) ) { try { originData = await readFile ( localPath ) ; } catch ( e ) { Logger . error ( e ) ; } } if ( originData === undefined ) { try { const remoteSource = await getRemoteSource ( originalUrl , isEncrypted && ! isRoutePath ) ; ret . code = remoteSource . code ; let data = remoteSource . data ; if ( ! isRoutePath && remoteSource . code . toString ( ) [ 0 ] !== '4' ) { data = callback ( remoteSource . data ) ; writeFile ( localPath , data ) ; } originData = data ; } catch ( e ) { return { code : 403 , data : e } ; } } let responseData ; if ( encode ) { responseData = encodeData ( originData ) ; if ( isEncrypted ) { responseData = XOR ( responseData as Buffer ) ; } } else { responseData = originData ; } ret . data = responseData ; return ret ; } export async function fetchAnySite ( url , encoding = 'binary' ) { const resp = await fetch ( url , { headers : { 'User-Agent' : Global . HttpGetUserAgent } } ) ; return ( await resp . buffer ( ) ) . toString ( encoding ) ; } export async function writeFile ( to , data , encoding = 'binary' ) { await mkdirs ( path . dirname ( to ) ) ; return new Promise ( ( resolve , reject ) => { fs . writeFile ( to , data , encoding , err => { if ( err ) { reject ( err ) ; } resolve ( ) ; } ) ; } ) ; } export function readFile ( filepath ) { return new Promise ( ( resolve , reject ) => { fs . readFile ( filepath , ( err , data ) => { if ( err ) { reject ( err ) ; } resolve ( data ) ; } ) ; } ) ; } export function encodeData ( data , encoding = 'binary' ) { if ( typeof data === 'string' ) { return Buffer . from ( data as string , encoding ) ; } else { return Buffer . from ( data as Buffer ) ; } } export function removeDirSync ( dir ) { let command = '' ; if ( process . platform === 'win32' ) { command = `rmdir /s/q "${ dir }"` ; } else { command = `rm -rf "${ dir }"` ; } childProcess . execSync ( command ) ; } export function getExportFileExtension ( dir ) { const extMap = new Map ( [ [ 'resourcepack.json' , 'mspr' ] , [ 'tool.json' , 'mspt' ] , [ 'extension.json' , 'mspe' ] , [ 'execute.json' , 'mspe' ] , [ 'mod.json' , 'mspm' ] ] ) ; let ret = '' ; extMap . forEach ( ( ext , filename ) => ( ret = fs . existsSync ( path . resolve ( dir , filename ) ) ? ext : ret ) ) ; return ret ; } export function zipDir ( from , to ) { const zip = new AdmZip ( ) ; zip . addLocalFolder ( from , path . basename ( from ) ) ; zip . writeZip ( to ) ; return to ; } export function unzipDir ( file , to ) { const zip = new AdmZip ( file ) ; return new Promise ( ( resolve , reject ) => { zip . extractAllToAsync ( to , true , err => { if ( err ) reject ( err ) ; else resolve ( to ) ; } ) ; } ) ; } export function copyFileSync ( source , target ) { let targetFile = target ; if ( fs . existsSync ( target ) ) { if ( fs . lstatSync ( target ) . isDirectory ( ) ) { targetFile = path . join ( target , path . basename ( source ) ) ; } } fs . writeFileSync ( targetFile , fs . readFileSync ( source ) ) ; } export function copyFolderSync ( source , target ) { let files = [ ] ; const targetFolder = path . join ( target , path . basename ( source ) ) ; if ( ! fs . existsSync ( targetFolder ) ) { fs . mkdirSync ( targetFolder ) ; } if ( fs . lstatSync ( source ) . isDirectory ( ) ) { files = fs . readdirSync ( source ) ; files . forEach ( file => { const curSource = path . join ( source , file ) ; if ( fs . lstatSync ( curSource ) . isDirectory ( ) ) { copyFolderSync ( curSource , targetFolder ) ; } else { copyFileSync ( curSource , targetFolder ) ; } } ) ; } } export function getFoldersSync ( folder ) { const folders = [ ] ; fs . readdirSync ( folder ) . forEach ( file => { if ( fs . statSync ( path . join ( folder , file ) ) . isDirectory ( ) ) { folders . push ( file ) ; } } ) ; return folders ; }