fnlb
Version:
Easily run your own bot using FNLB, a powerful and scalable system for managing Fortnite bots.
2 lines (1 loc) • 5.37 kB
JavaScript
import{fork as u}from"node:child_process";import{createHash as f}from"node:crypto";import{mkdir as g,readFile as m,writeFile as w}from"node:fs/promises";import{resolve as S}from"node:path";class d{static wait(t){return new Promise((r)=>setTimeout(r,t))}}class l{config;activeProcesses=new Map;packageName=`${process.versions.bun?"zenith-bun":"zenith"}`;isLoaded=!1;shouldRestart=!0;runId=0;constructor(t){this.config=t}async start(t){await this.stop(),this.shouldRestart=!0,this.runId++;let r=this.runId;if(!t?.apiToken)throw new Error("[FNLB ShardingManager] Please provide a FNLB API token.");await this.update();let o=t.numberOfShards??1,h=(~~(Math.random()*1e4)).toString(36)+"fnlb"+(~~(Date.now()/1000)).toString(36);for(let i=0;i<o;i++){let e=`${h}-${i.toString().padStart(2,"0")}`,n=await this.startShard(t,e,r);this.activeProcesses.set(e,n)}}async stop(){if(this.shouldRestart=!1,this.runId++,this.activeProcesses.size===0)return;this.log("Stopping all active shards...");for(let[t,r]of this.activeProcesses)this.log(`Stopping shard with ID: ${t}`),r.kill();this.activeProcesses.clear(),this.log("All shards stopped.")}async startShard(t,r,o){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:",r);let h=u(`./.fnlb/${this.packageName}.mjs`,[],{env:{...process.env,FORCE_COLOR:"1",SHARD_ID:r,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)h.stdout?.on("data",(i)=>{let e=i.toString("utf8");process.stdout.write(e),this.config?.onSubProcessLogMessage?.({timestamp:Date.now(),content:e,format:0})});if(!this.config?.disableSubProcessErrorLogs)h.stderr?.on("data",(i)=>{let e=i.toString("utf8");process.stderr.write(e),this.config?.onSubProcessLogMessage?.({timestamp:Date.now(),content:e,format:4})});return h.on("close",async(i)=>{if(this.activeProcesses.delete(r),this.shouldRestart&&o===this.runId){if(i===0)this.warn("Shard exited with code:",i);else this.error("Shard exited with code:",i?.toString()??"none");this.log("Trying to restart shard..."),await d.wait(1e4);let e=await this.startShard(t,r,o);this.activeProcesses.set(r,e)}else this.log(`Shard ${r} stopped.`)}),h}async update(){if(this.isLoaded)return;let t=S(`./.fnlb/${this.packageName}.mjs`),r=await m(t,"utf-8").catch(()=>null),o=this.config?.maxDownloadRetries||1/0,h=this.config?.maxBackoffMs||60000;this.log(r?"Checking for updates...":"Downloading FNLB...");let i=`https://dist.fnlb.net/packages/${this.packageName}/release`,e,n=0,c=1000;while(n<o)try{let s=await fetch(i);if(!s.ok)throw new Error(`Status code: ${s.status}`);e=await s.json();break}catch(s){let a=Math.min(c*2,h);if(n++,this.error("Update error:",s),this.warn(`Check for updates attempt ${n} failed: ${s.message}. Retrying in ${a>=60000?`${~~(a/60000)}m`:`${~~(a/1000)}s`}...`),n>=o)break;await new Promise((p)=>setTimeout(p,c)),c=a}if(!e){if(r){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(r){if(f("sha256").update(r).digest("hex")===e.hash){this.success(`FNLB v${e.version} is up to date`),this.isLoaded=!0,this.success(`Finished loading FNLB v${e.version}`);return}this.log(`Downloading update for FNLB v${e.version}`)}n=0,c=1000;while(n<o)try{let s=await fetch(e.url);if(!s.ok)throw new Error(`Download failed with status ${s.status}`);let a=await s.text();if(f("sha256").update(a).digest("hex")!==e.hash)throw new Error("Downloaded file hash mismatch...");await g(".fnlb",{recursive:!0}),await w(t,a),this.isLoaded=!0,this.success(`Finished loading FNLB v${e.version}`);return}catch(s){let a=Math.min(c*2,h);n++,this.error("Download error:",s),this.warn(`Download attempt ${n} failed: ${s.message}. Retrying in ${a>=60000?`${~~(a/60000)}m`:`${~~(a/1000)}s`}...`),await d.wait(c),c=a}if(r){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 ${n} 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;((o)=>{o.Info="INFO";o.Debug="DEBUG"})(L||={});var M=l;export{M as default,L as LogLevel};