fnbl
Version:
Easily run your own bot using FNLB, a powerful and scalable system for managing Fortnite bots.
2 lines (1 loc) • 5.25 kB
JavaScript
import{fork as g}from"node:child_process";import{createHash as f}from"node:crypto";import{mkdir as u,readFile as m,writeFile as w}from"node:fs/promises";import{resolve as S}from"node:path";class l{static wait(t){return new Promise((e)=>setTimeout(e,t))}}class d{config;activeProcesses=new Map;packageName=`${process.versions.bun?"zenith-bun":"zenith"}`;isLoaded=!1;shouldRestart=!0;constructor(t){this.config=t}async start(t){if(await this.stop(),this.shouldRestart=!0,!t?.apiToken)throw new Error("[FNLB ShardingManager] Please provide a FNLB API token.");await this.update();let e=t.numberOfShards??1,i=(~~(Math.random()*1e4)).toString(36)+"fnlb"+(~~(Date.now()/1000)).toString(36);for(let s=0;s<e;s++){let r=`${i}-${s.toString().padStart(2,"0")}`,n=await this.startShard(t,r);this.activeProcesses.set(r,n)}}async stop(){if(this.shouldRestart=!1,this.activeProcesses.size===0)return;this.log("Stopping all active processes...");for(let[t,e]of this.activeProcesses)this.log(`Stopping process with ID: ${t}`),e.kill();this.activeProcesses.clear(),this.log("All processes stopped.")}async startShard(t,e){if(await this.update(),!t?.apiToken||t.apiToken.length<10)throw new Error("[FNLB ShardingManager] Please provide a valid FNLB API token.");this.log("Starting shard with ID:",e);let i=g(`./.fnlb/${this.packageName}.mjs`,[],{env:{...process.env,FORCE_COLOR:"1",SHARD_ID:e,API_TOKEN:t.apiToken,CATEGORIES:t.categories?.join(","),BOTS_PER_SHARD:(t.botsPerShard??1).toString(),HIDE_USERNAMES:t.hideUsernames?"true":"false",HIDE_EMAILS:t.hideEmails?"true":"false",LOG_LEVEL:t.logLevel,CLUSTER_ID:this.config?.clusterName?.trim().replace(/ +(?= )/g,"").toLowerCase().replaceAll(" ","-")??"unknown",CLUSTER_NAME:this.config?.clusterName?.trim()},stdio:["inherit","pipe","pipe","ipc"]});if(!this.config?.disableSubProcessLogs)i.stdout?.on("data",(s)=>{let r=s.toString("utf8");process.stdout.write(r),this.config?.onSubProcessLogMessage?.({timestamp:Date.now(),content:r,format:0})});if(!this.config?.disableSubProcessErrorLogs)i.stderr?.on("data",(s)=>{let r=s.toString("utf8");process.stderr.write(r),this.config?.onSubProcessLogMessage?.({timestamp:Date.now(),content:r,format:4})});return i.on("close",async(s)=>{if(this.activeProcesses.delete(e),this.shouldRestart){if(s===0)this.warn("Child process exited with code:",s?.toString()??"none");else this.error("Child process exited with code:",s?.toString()??"none");await l.wait(1e4);let r=await this.startShard(t,e);this.activeProcesses.set(e,r)}else this.log(`Child process ${e} stopped.`)}),i}async update(){if(this.isLoaded)return;let t=S(`./.fnlb/${this.packageName}.mjs`),e=await m(t,"utf-8").catch(()=>null),i=this.config?.maxDownloadRetries||1/0,s=this.config?.maxBackoffMs||60000;this.log(e?"Checking for updates...":"Downloading FNLB...");let r=`https://dist.fnlb.net/packages/${this.packageName}/release`,n,c=0,h=1000;while(c<i)try{let o=await fetch(r);if(!o.ok)throw new Error(`Status code: ${o.status}`);n=await o.json();break}catch(o){let a=Math.min(h*2,s);if(c++,this.warn(`Check for updates attempt ${c} failed: ${o.message}. Retrying in ${a>=60000?`${~~(a/60000)}m`:`${~~(a/1000)}s`}...`),c>=i)break;await new Promise((p)=>setTimeout(p,h)),h=a}if(!n){if(e){this.warn("Failed to check for updates. Using existing local version."),this.isLoaded=!0,this.success("Loaded existing FNLB version");return}throw new Error("[FNLB ShardingManager] Failed to check for updates and no local file found.")}if(e){if(f("sha256").update(e).digest("hex")===n.hash){this.success(`FNLB v${n.version} is up to date`),this.isLoaded=!0,this.success(`Finished loading FNLB v${n.version}`);return}this.log(`Downloading update for FNLB v${n.version}`)}c=0,h=1000;while(c<i)try{let o=await fetch(n.url);if(!o.ok)throw new Error(`Download failed with status ${o.status}`);let a=await o.text();if(f("sha256").update(a).digest("hex")!==n.hash)throw new Error("Downloaded file hash mismatch...");await u(".fnlb",{recursive:!0}),await w(t,a),this.isLoaded=!0,this.success(`Finished loading FNLB v${n.version}`);return}catch(o){let a=Math.min(h*2,s);c++,this.warn(`Download attempt ${c} failed: ${o.message}. Retrying in ${a>=60000?`${~~(a/60000)}m`:`${~~(a/1000)}s`}...`),await l.wait(h),h=a}if(e){this.warn("Max retries reached. Using existing local version."),this.isLoaded=!0,this.success("Loaded existing FNLB version");return}throw new Error(`[FNLB ShardingManager] Failed to download and verify update after ${c} attempts`)}log(...t){if(!this.config?.disableLogs)console.log("[FNLB ShardingManager]",...t),this.config?.onLogMessage?.({timestamp:Date.now(),content:t.join(" "),format:0})}success(...t){if(!this.config?.disableLogs)console.log("[FNLB ShardingManager] [OK]",...t),this.config?.onLogMessage?.({timestamp:Date.now(),content:t.join(" "),format:1})}warn(...t){if(!this.config?.disableErrorLogs)console.warn("[FNLB ShardingManager] [WRN]",...t),this.config?.onLogMessage?.({timestamp:Date.now(),content:t.join(" "),format:3})}error(...t){if(!this.config?.disableErrorLogs)console.error("[FNLB ShardingManager] [ERR]",...t),this.config?.onLogMessage?.({timestamp:Date.now(),content:t.join(" "),format:4})}}var L;((i)=>{i.Info="INFO";i.Debug="DEBUG"})(L||={});var M=d;export{M as default,L as LogLevel};