functionMagicHttp(env,logger){constphoneUA="Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1";constcomputerUA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 Edg/84.0.522.59";letaxiosInstance;if(env.isNode){constaxios=require("axios");axiosInstance=axios.create()}classInterceptorManager{constructor(isRequest=true){this.handlers=[];this.isRequest=isRequest}use(fulfilled,rejected,options){if(typeoffulfilled==="function"){logger.debug(`Register fulfilled ${fulfilled.name}`)}if(typeofrejected==="function"){logger.debug(`Register rejected ${rejected.name}`)}this.handlers.push({fulfilled:fulfilled,rejected:rejected,synchronous:options&&typeofoptions.synchronous==="boolean"?options.synchronous:false,runWhen:options?options.runWhen:null});returnthis.handlers.length-1}eject(id){if(this.handlers[id]){this.handlers[id]=null}}forEach(fn){this.handlers.forEach(element=>{if(element!==null){fn(element)}})}}functionparamsToQueryString(config){let_config={...config};if(!!_config.params){if(!env.isNode){letqs=Object.keys(_config.params).map(key=>{constencodeKey=encodeURIComponent(key);_config.url=_config.url.replace(newRegExp(`${key}=[^&]*`,"ig"),"");_config.url=_config.url.replace(newRegExp(`${encodeKey}=[^&]*`,"ig"),"");return`${encodeKey}=${encodeURIComponent(_config.params[key])}`}).join("&");if(_config.url.indexOf("?")<0)_config.url+="?";if(!/(&|\?)$/g.test(_config.url)){_config.url+="&"}_config.url+=qs;delete_config.params;logger.debug(`Params to QueryString: ${_config.url}`)}}return_config}constmergeConfig=(method,configOrUrl)=>{letconfig=typeofconfigOrUrl==="object"?{headers:{},...configOrUrl}:{url:configOrUrl,headers:{}};if(!config.method){config["method"]=method}config=paramsToQueryString(config);if(config["rewrite"]===true){if(env.isSurge){config.headers["X-Surge-Skip-Scripting"]=false;deleteconfig["rewrite"]}elseif(env.isQuanX){config["hints"]=false;deleteconfig["rewrite"]}}if(env.isSurgeLike){constcontentType=config.headers["content-type"]||config.headers["Content-Type"];if(config["method"]!=="GET"&&contentType&&contentType.indexOf("application/json")>=0&&config.bodyinstanceofArray){config.body=JSON.stringify(config.body);logger.debug(`Convert Array object to String: ${config.body}`)}}elseif(env.isQuanX){if(config.hasOwnProperty("body")&&typeofconfig["body"]!=="string")config["body"]=JSON.stringify(config["body"]);config["method"]=method}elseif(env.isNode){if(method==="POST"||method==="PUT"||method==="PATCH"||method==="DELETE"){config.data=config.data||config.body}elseif(method==="GET"){config.params=config.params||config.body}deleteconfig.body}returnconfig};constmodifyResponse=(resp,config=null)=>{if(resp){let_resp={...resp,config:resp.config||config,status:resp.statusCode||resp.status,body:resp.body||resp.data,headers:resp.headers||resp.header};if(typeof_resp.body==="string"){try{_resp.body=JSON.parse(_resp.body)}catch{}}delete_resp.data;return_resp}else{returnresp}};constconvertHeadersToLowerCase=headers=>{returnObject.keys(headers).reduce((acc,key)=>{acc[key.toLowerCase()]=headers[key];returnacc},{})};constconvertHeadersToCamelCase=headers=>{returnObject.keys(headers).reduce((acc,key)=>{constnewKey=key.split("-").map(word=>word[0].toUpperCase()+word.slice(1)).join("-");acc[newKey]=headers[key];returnacc},{})};constraiseExceptionByStatusCode=(resp,config=null)=>{if(!!resp&&resp.status>=400){logger.debug(`Raise exception when status code is ${resp.status}`);return{name:"RequestException",message:`Request failed with status code ${resp.status}`,config:config||resp.config,response:resp}}};constinterceptors={request:newInterceptorManager,response:newInterceptorManager(false)};letrequestInterceptorChain=[];letresponseInterceptorChain=[];letsynchronousRequestInterceptors=true;functioninterceptConfig(config){config=paramsToQueryString(config);logger.debug(`HTTP ${config["method"].toUpperCase()}:\n${JSON.stringify(confi
functionMagicData(env,logger){letnode={fs:undefined,data:{}};if(env.isNode){node.fs=require("fs");try{node.fs.accessSync("./magic.json",node.fs.constants.R_OK|node.fs.constants.W_OK)}catch(err){node.fs.writeFileSync("./magic.json","{}",{encoding:"utf8"})}node.data=require("./magic.json")}constdefaultValueComparator=(oldVal,newVal)=>{if(typeofnewVal==="object"){returnfalse}else{returnoldVal===newVal}};const_typeConvertor=val=>{if(val==="true"){returntrue}elseif(val==="false"){returnfalse}elseif(typeofval==="undefined"){returnnull}else{returnval}};const_valConvertor=(val,default_,session,read_no_session)=>{if(session){try{if(typeofval==="string")val=JSON.parse(val);if(val["magic_session"]===true){val=val[session]}else{val=null}}catch{val=null}}if(typeofval==="string"&&val!=="null"){try{val=JSON.parse(val)}catch{}}if(read_no_session===false&&!!val&&val["magic_session"]===true){val=null}if((val===null||typeofval==="undefined")&&default_!==null&&typeofdefault_!=="undefined"){val=default_}val=_typeConvertor(val);returnval};constconvertToObject=obj=>{if(typeofobj==="string"){letdata={};try{data=JSON.parse(obj);consttype=typeofdata;if(type!=="object"||datainstanceofArray||type==="bool"||data===null){data={}}}catch{}returndata}elseif(objinstanceofArray||obj===null||typeofobj==="undefined"||obj!==obj||typeofobj==="boolean"){return{}}else{returnobj}};constreadForNode=(key,default_=null,session="",read_no_session=false,externalData=null)=>{letdata=externalData||node.data;if(!!data&&typeofdata[key]!=="undefined"&&data[key]!==null){val=data[key]}else{val=!!session?{}:null}val=_valConvertor(val,default_,session,read_no_session);returnval};constread=(key,default_=null,session="",read_no_session=false,externalData=null)=>{letval="";if(externalData||env.isNode){val=readForNode(key,default_,session,read_no_session,externalData)}else{if(env.isSurgeLike){val=$persistentStore.read(key)}elseif(env.isQuanX){val=$prefs.valueForKey(key)}val=_valConvertor(val,default_,session,read_no_session)}logger.debug(`READ DATA [${key}]${!!session?`[${session}]`:""} <${typeofval}>\n${JSON.stringify(val)}`);returnval};constwriteForNode=(key,val,session="",externalData=null)=>{letdata=externalData||node.data;data=convertToObject(data);if(!!session){letobj=convertToObject(data[key]);obj["magic_session"]=true;obj[session]=val;data[key]=obj}else{data[key]=val}if(externalData!==null){externalData=data}returndata};constwrite=(key,val,session="",externalData=null)=>{if(typeofval==="undefined"||val!==val){returnfalse}if(!env.isNode&&(typeofval==="boolean"||typeofval==="number")){val=String(val)}letdata="";if(externalData||env.isNode){data=writeForNode(key,val,session,externalData)}else{if(!session){data=val}else{if(env.isSurgeLike){data=!!$persistentStore.read(key)?$persistentStore.read(key):data}elseif(env.isQuanX){data=!!$prefs.valueForKey(key)?$prefs.valueForKey(key):data}data=convertToObject(data);data["magic_session"]=true;data[session]=val}}if(!!data&&typeofdata==="object"){data=JSON.stringify(data,null,4)}logger.debug(`WRITE DATA [${key}]${session?`[${session}]`:""} <${typeofval}>\n${JSON.stringify(val)}`);if(!externalData){if(env.isSurgeLike){return$persistentStore.write(data,key)}elseif(env.isQuanX){return$prefs.setValueForKey(data,key)}elseif(env.isNode){try{node.fs.writeFileSync("./magic.json",data);returntrue}catch(err){logger.error(err);returnfalse}}}returntrue};constupdate=(key,val,session,comparator=defaultValueComparator,externalData=null)=>{val=_typeConvertor(val);constoldValue=read(key,null,session,false,externalData);if(comparator(oldValue,val)===true){returnfalse}else{constresult=write(key,val,session,externalData);letnewVal=read(key,null,session,false,externalData);if(comparator===defaultValueComparator&&typeofnewVal==="object"){returnresult}returncomparator(val,newVal)}};constdelForNode=(key,session,externalData)=>{letdata=externalData||node.data;data=convertToObject(data);if(!!session){obj=convertToObject(data[key]);deleteobj[session];data[key]=obj}else{deletedata[key]}i
functionMagicUtils(env,logger){constretry=(fn,retries=5,interval=0,callback=null)=>{return(...args)=>{returnnewPromise((resolve,reject)=>{function_retry(...args){Promise.resolve().then(()=>fn.apply(this,args)).then(result=>{if(typeofcallback==="function"){Promise.resolve().then(()=>callback(result)).then(()=>{resolve(result)}).catch(ex=>{if(retries>=1){if(interval>0)setTimeout(()=>_retry.apply(this,args),interval);else_retry.apply(this,args)}else{reject(ex)}retries--})}else{resolve(result)}}).catch(ex=>{logger.error(ex);if(retries>=1&&interval>0){setTimeout(()=>_retry.apply(this,args),interval)}elseif(retries>=1){_retry.apply(this,args)}else{reject(ex)}retries--})}_retry.apply(this,args)})}};constformatTime=(time,fmt="yyyy-MM-dd hh:mm:ss")=>{leto={"M+":time.getMonth()+1,"d+":time.getDate(),"h+":time.getHours(),"m+":time.getMinutes(),"s+":time.getSeconds(),"q+":Math.floor((time.getMonth()+3)/3),S:time.getMilliseconds()};if(/(y+)/.test(fmt))fmt=fmt.replace(RegExp.$1,(time.getFullYear()+"").substr(4-RegExp.$1.length));for(letkino)if(newRegExp("("+k+")").test(fmt))fmt=fmt.replace(RegExp.$1,RegExp.$1.length===1?o[k]:("00"+o[k]).substr((""+o[k]).length));returnfmt};constnow=()=>{returnformatTime(newDate,"yyyy-MM-dd hh:mm:ss")};consttoday=()=>{returnformatTime(newDate,"yyyy-MM-dd")};constsleep=time=>{returnnewPromise(resolve=>setTimeout(resolve,time))};constassert=(val,msg=null)=>{if(env.isNode){const_assert=require("assert");if(msg)_assert(val,msg);else_assert(val)}else{if(val!==true){leterr=`AssertionError: ${msg||"The expression evaluated to a falsy value."}`;logger.error(err)}}};return{retry:retry,formatTime:formatTime,now:now,today:today,sleep:sleep,assert:assert}}
functionMagicQingLong(env,data,logger){letqlUrl="";letqlName="";letqlClient="";letqlSecret="";letqlPwd="";letqlToken="";constmagicJsonFileName="magic.json";consttimeout=3e3;consthttp=(()=>MagicHttp(env,logger))();constinit=(url,clientId,clientSecret,username,password)=>{qlUrl=url;qlClient=clientId;qlSecret=clientSecret;qlName=username;qlPwd=password};functionreadQingLongConfig(config){qlUrl=qlUrl||data.read("magic_qlurl");qlToken=qlToken||data.read("magic_qltoken");logger.debug(`QingLong url: ${qlUrl}\nQingLong token: ${qlToken}`);returnconfig}functionsetBaseUrlAndTimeout(config){if(!qlUrl){qlUrl=data.read("magic_qlurl")}if(config.url.indexOf(qlUrl)<0){config.url=`${qlUrl}${config.url}`}return{...config,timeout:timeout}}functionsetTimestamp(config){config.params={...config.params,t:Date.now()};returnconfig}asyncfunctionsetAuthorization(config){qlToken=qlToken||data.read("magic_qltoken","");if(!qlToken){awaitgetToken()}config.headers["authorization"]=`Bearer ${qlToken}`;returnconfig}functionswitchClientMode(config){qlClient=qlClient||data.read("magic_qlclient");if(!!qlClient){config.url=config.url.replace("/api/","/open/")}returnconfig}asyncfunctionrefreshToken(error){try{constmessage=error.message||error.error||JSON.stringify(error);if((message.indexOf("NSURLErrorDomain")>=0&&message.indexOf("-1012")>=0||!!error.response&&error.response.status===401)&&!!error.config&&error.config.refreshToken!==true){logger.warning(`QingLong Panel token has expired`);logger.info("Refreshing the QingLong Panel token");awaitgetToken();error.config["refreshToken"]=true;logger.info("Call the previous method again");returnawaithttp.request(error.config.method,error.config)}else{returnPromise.reject(error)}}catch(ex){returnPromise.reject(ex)}}http.interceptors.request.use(setBaseUrlAndTimeout,undefined);http.interceptors.request.use(switchClientMode,undefined,{runWhen:config=>{returnconfig.url.indexOf("api/user/login")<0&&config.url.indexOf("open/auth/token")<0}});http.interceptors.request.use(setAuthorization,undefined,{runWhen:config=>{returnconfig.url.indexOf("api/user/login")<0&&config.url.indexOf("open/auth/token")<0}});http.interceptors.request.use(setTimestamp,undefined,{runWhen:config=>{returnconfig.url.indexOf("open/auth/token")<0}});http.interceptors.request.use(readQingLongConfig,undefined);http.interceptors.response.use(undefined,refreshToken);asyncfunctiongetToken(){qlClient=qlClient||data.read("magic_qlclient");qlSecret=qlSecret||data.read("magic_qlsecrt");qlName=qlName||data.read("magic_qlname");qlPwd=qlPwd||data.read("magic_qlpwd");if(qlUrl&&qlClient&&qlSecret){logger.info("Get token from QingLong Panel");awaithttp.get({url:`/open/auth/token`,headers:{"content-type":"application/json"},params:{client_id:qlClient,client_secret:qlSecret}}).then(resp=>{if(Object.keys(resp.body).length>0&&resp.body.data&&resp.body.data.token){logger.info("Successfully logged in to QingLong Panel");qlToken=resp.body.data.token;data.write("magic_qltoken",qlToken)}else{thrownewError("Get QingLong Panel token failed.")}}).catch(err=>{logger.error(`Error logging in to QingLong Panel.\n${err.message||err}`)})}elseif(qlUrl&&qlName&&qlPwd){awaithttp.post({url:`/api/user/login`,headers:{"content-type":"application/json"},body:{username:qlName,password:qlPwd}}).then(resp=>{logger.info("Successfully logged in to QingLong Panel");qlToken=resp.body.data.token;data.write("magic_qltoken",qlToken)}).catch(err=>{logger.error(`Error logging in to QingLong Panel.\n${err.message||err}`)})}returnqlToken}asyncfunctionsetEnv(name,value,id=null){qlUrl=qlUrl||data.read("magic_qlurl");if(id===null){letenvIds=awaitsetEnvs([{name:name,value:value}]);if(!!envIds&&envIds.length===1){returnenvIds[0]}}else{awaithttp.put({url:`/api/envs`,headers:{"content-type":"application/json"},body:{name:name,value:value,id:id}}).then(resp=>{if(resp.body.code===200){logger.debug(`QINGLONG UPDATE ENV ${name} <${typeofvalue}> (${id})\n${JSON.stringify(value)}`);returntrue}else{logger.error(`Error adding environment variable from QingLong Panel.\n