fnlb 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,31 @@
1
- # Fortnite LobbyBot
1
+ # 🚀 FNLB – Self-Host Your Own Fortnite Bot
2
2
 
3
- Self-Host your own Fortnite LobbyBot with FNLB's system.
3
+ Easily run your own bot using **[FNLB](https://fnlb.net)**, a powerful and scalable system for managing Fortnite bots.
4
+ Whether you're running one bot or hundreds, FNLB gives you full control.
4
5
 
5
- ## Installation
6
+ ## 📦 Installation
6
7
 
7
- ```sh
8
- npm i fnlb@latest
8
+ Install the latest version via **npm install**:
9
+
10
+ ```bash
11
+ npm install fnlb@latest
12
+ ```
13
+
14
+ Using **[bun](https://bun.sh)**? Install the latest version via **bun install**:
15
+
16
+ ```bash
17
+ bun install fnlb@latest
9
18
  ```
10
19
 
11
- ## Start a bot
20
+ ## ⛓️‍💥 Useful Links
21
+
22
+ - 📖 **[FNLB Documentation](https://docs.fnlb.net/introduction)**
23
+ - 📄 **[FNLB Changelog](https://docs.fnlb.net/bots/changelog)**
24
+ - 🗨️ **[FNLB Discord Server](https://fnlb.net/discord)**
25
+
26
+ ## 🤖 Starting a Single Bot
12
27
 
13
- This will start a single bot. You need to change the API Token to use the one of your FNLB account.
28
+ Get started with a single bot using your [FNLB API token](https://app.fnlb.net/account):
14
29
 
15
30
  ```ts
16
31
  import FNLB from 'fnlb';
@@ -18,13 +33,14 @@ import FNLB from 'fnlb';
18
33
  const fnlb = new FNLB();
19
34
 
20
35
  await fnlb.start({
21
- apiToken: 'abc'
36
+ apiToken: 'your-api-token', // 🔑 Replace with your actual token from https://app.fnlb.net/account
37
+ categories: ['your-category-id'] // Get the category of your bot on https://app.fnlb.net/bots -> Click on your bot -> About this bot -> Category ID
22
38
  });
23
39
  ```
24
40
 
25
- ## Start multiple bots
41
+ ## 🔁 Running Multiple Bots (Same Shard)
26
42
 
27
- To do it you can configure the botsPerShard setting. This example will spawn 10 bots on the same subprocess.
43
+ Run multiple bots in a **single subprocess** using the `botsPerShard` option:
28
44
 
29
45
  ```ts
30
46
  import FNLB from 'fnlb';
@@ -32,14 +48,15 @@ import FNLB from 'fnlb';
32
48
  const fnlb = new FNLB();
33
49
 
34
50
  await fnlb.start({
35
- apiToken: 'abc',
36
- botsPerShard: 10
51
+ apiToken: 'your-api-token',
52
+ categories: ['your-category-id'],
53
+ botsPerShard: 10 // 🧱 Spawns max 10 bots
37
54
  });
38
55
  ```
39
56
 
40
- ## Start multiple shards
57
+ ## 🧩 Using Multiple Shards (Subprocesses)
41
58
 
42
- To do it you can configure the numberOfShards setting. This example will spawn 2 shards (subprocesses) with 10 bots per shard for a total of 20 bots.
59
+ Scale even more by spawning multiple **shards** (subprocesses) with multiple bots each:
43
60
 
44
61
  ```ts
45
62
  import FNLB from 'fnlb';
@@ -47,15 +64,19 @@ import FNLB from 'fnlb';
47
64
  const fnlb = new FNLB();
48
65
 
49
66
  await fnlb.start({
50
- apiToken: 'abc',
51
- numberOfShards: 2,
52
- botsPerShard: 10
67
+ apiToken: 'your-api-token',
68
+ categories: ['your-category-id'],
69
+ numberOfShards: 2, // 2 shards 🧩
70
+ botsPerShard: 10 // max 10 bots per shard 🤖
53
71
  });
54
72
  ```
55
73
 
56
- ## Start bots only from certain categories
74
+ > 💡 Total bots: `numberOfShards × botsPerShard`
75
+ > In this example: 2 × 10 = **20 bots max**
57
76
 
58
- To do it you can configure the categories setting.
77
+ ## 🗂️ Launching Bots Across Multiple Categories
78
+
79
+ Want to run bots from different FNLB categories? Just add them to the `categories` array:
59
80
 
60
81
  ```ts
61
82
  import FNLB from 'fnlb';
@@ -63,16 +84,16 @@ import FNLB from 'fnlb';
63
84
  const fnlb = new FNLB();
64
85
 
65
86
  await fnlb.start({
66
- apiToken: 'abc',
67
- categories: ['abc', 'abc']
87
+ apiToken: 'your-api-token',
88
+ categories: ['category-id-1', 'category-id-2'], // 🔄 Multi-category support
68
89
  numberOfShards: 2,
69
90
  botsPerShard: 10
70
91
  });
71
92
  ```
72
93
 
73
- ## Stop your bot
94
+ ## Stopping All Bots
74
95
 
75
- The FNLB.stop() method will kill all the shards.
96
+ Shut everything down cleanly using the `stop()` method:
76
97
 
77
98
  ```ts
78
99
  import FNLB from 'fnlb';
@@ -80,26 +101,61 @@ import FNLB from 'fnlb';
80
101
  const fnlb = new FNLB();
81
102
 
82
103
  await fnlb.start({
83
- apiToken: 'abc',
104
+ apiToken: 'your-api-token',
84
105
  numberOfShards: 2,
85
106
  botsPerShard: 10
86
107
  });
87
108
 
88
- // do something
89
-
90
- await fnlb.stop()
109
+ await fnlb.stop(); // 🛑 Stops all shards and bots
91
110
  ```
92
111
 
93
- ## Set a cluster name
112
+ ## 🏷️ Naming Your Cluster
94
113
 
95
- You can use the clusterName option to set your cluster name.
114
+ Customize your cluster with a unique name using the `clusterName` option:
96
115
 
97
116
  ```ts
98
117
  import FNLB from 'fnlb';
99
118
 
100
- const fnlb = new FNLB({ clusterName: 'MyCluster' });
119
+ const fnlb = new FNLB({ clusterName: 'MyAwesomeCluster' });
101
120
 
102
121
  await fnlb.start({
103
- apiToken: 'abc'
122
+ apiToken: 'your-api-token'
104
123
  });
105
124
  ```
125
+
126
+ ## 🔄 Auto-Restart Every Hour (Optional)
127
+
128
+ Want to keep things fresh? Here's how to restart your bots automatically every hour:
129
+
130
+ ```ts
131
+ import FNLB from 'fnlb';
132
+
133
+ const fnlb = new FNLB();
134
+
135
+ async function startFNLB() {
136
+ await fnlb.start({
137
+ apiToken: 'your-api-token',
138
+ numberOfShards: 1,
139
+ botsPerShard: 5,
140
+ categories: ['your-category-id']
141
+ });
142
+ }
143
+
144
+ async function restartFNLB() {
145
+ console.log('🔁 Restarting FNLB...');
146
+ await fnlb.stop();
147
+ await startFNLB();
148
+ }
149
+
150
+ await startFNLB();
151
+
152
+ // ⏱️ Restart every hour (3600000 ms)
153
+ setInterval(restartFNLB, 3_600_000);
154
+ ```
155
+
156
+ ## 🗨️ Join the Community
157
+
158
+ Need help, support, or just want to chat with other developers?
159
+ Come hang out with us on Discord! 👇
160
+
161
+ [![Join FNLB on Discord](https://discord.com/api/guilds/1106879710744543303/widget.png?style=banner3)](https://fnlb.net/discord)
package/dist/index.d.ts CHANGED
@@ -20,6 +20,12 @@ export interface FNLBConfig {
20
20
  disableErrorLogs?: boolean;
21
21
  disableSubProcessLogs?: boolean;
22
22
  disableSubProcessErrorLogs?: boolean;
23
+ maxDownloadRetries?: number;
24
+ maxBackoffMs?: number;
25
+ }
26
+ export declare enum LogLevel {
27
+ Info = "INFO",
28
+ Debug = "DEBUG"
23
29
  }
24
30
  export interface StartConfig {
25
31
  apiToken: string;
@@ -28,22 +34,23 @@ export interface StartConfig {
28
34
  botsPerShard?: number;
29
35
  hideUsernames?: boolean;
30
36
  hideEmails?: boolean;
31
- logLevel?: "INFO" | "DEBUG";
37
+ logLevel?: LogLevel;
32
38
  }
33
39
  declare class FNLB {
34
- private isLoaded;
35
40
  private readonly config?;
36
41
  private readonly activeProcesses;
42
+ private readonly packageName;
43
+ private isLoaded;
37
44
  private shouldRestart;
38
45
  constructor(config?: FNLBConfig);
46
+ start(config: StartConfig): Promise<void>;
47
+ stop(): Promise<void>;
48
+ startShard(config: StartConfig, id: string): Promise<import("child_process").ChildProcess>;
49
+ update(): Promise<void>;
39
50
  private log;
40
51
  private success;
41
52
  private warn;
42
53
  private error;
43
- update(): Promise<void>;
44
- start(config: StartConfig): Promise<void>;
45
- stop(): Promise<void>;
46
- startShard(config: StartConfig, id: string): Promise<import("child_process").ChildProcess>;
47
54
  }
48
55
 
49
56
  export {
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{fork as d}from"node:child_process";import{createHash as p}from"node:crypto";import{readFile as g,writeFile as f}from"node:fs/promises";import u from"node:path";class c{static wait(t){return new Promise((s)=>setTimeout(s,t))}}class i{isLoaded=!1;config;activeProcesses=new Map;shouldRestart=!0;constructor(t){this.config=t}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})}async update(){if(this.isLoaded)return;let t=u.join("zenith.mjs"),s=await g(t,"utf-8").catch(()=>null);this.log(s?"Checking for updates...":"Downloading FNLB...");let o=await fetch("https://dist.fnlb.net/packages/zenith/release");if(!o.ok)throw new Error(`[FNLB ShardingManager] Failed to check for updates, status code: ${o.status}`);let e=await o.json();if(s){let h=p("sha256");if(h.update(s),h.digest().toString("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}`)}let a=e.url,n=await fetch(a);if(!n.ok)throw new Error(`[FNLB ShardingManager] Failed to download update, status code: ${n.status}`);this.log(`Downloaded FNLB v${e.version}`);let l=await n.text();await f(t,l),this.isLoaded=!0,this.success(`Finished loading FNLB v${e.version}`)}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 s=t.numberOfShards??1;for(let r=0;r<s;r++){let o=new Date,e=`${r.toString().padStart(2,"0")}/${o.getDay()}${o.getHours()}${o.getMinutes()}${o.getSeconds()}`,a=await this.startShard(t,e);this.activeProcesses.set(e,a)}}async stop(){this.log("Stopping all active processes..."),this.shouldRestart=!1;for(let[t,s]of this.activeProcesses)this.log(`Stopping process with id: ${t}`),s.kill();this.activeProcesses.clear(),this.log("All processes stopped.")}async startShard(t,s){if(await this.update(),!t?.apiToken)throw new Error("[FNLB ShardingManager] Please provide a FNLB API token.");this.log("Starting shard with id:",s);let r=d("zenith.mjs",[],{env:{...process.env,FORCE_COLOR:"1",SHARD_ID:s,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)r.stdout?.on("data",(o)=>{let e=o.toString("utf8");process.stdout.write(e),this.config?.onSubProcessLogMessage?.({timestamp:Date.now(),content:e,format:0})});if(!this.config?.disableSubProcessErrorLogs)r.stderr?.on("data",(o)=>{let e=o.toString("utf8");process.stderr.write(e),this.config?.onSubProcessLogMessage?.({timestamp:Date.now(),content:e,format:4})});return r.on("close",async(o)=>{if(this.activeProcesses.delete(s),this.shouldRestart){if(o===0)this.warn("Child process exited with code:",o?.toString()??"none");else this.error("Child process exited with code:",o?.toString()??"none");await c.wait(1e4);let e=await this.startShard(t,s);this.activeProcesses.set(s,e)}else this.log(`Child process ${s} stopped.`)}),r}}var D=i;export{D as default};
1
+ 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};
package/package.json CHANGED
@@ -1,19 +1,17 @@
1
1
  {
2
2
  "name": "fnlb",
3
- "version": "1.0.0",
4
- "author": "FNLB-Project",
3
+ "version": "1.1.0",
4
+ "author": "FNLB",
5
5
  "main": "dist/index.js",
6
+ "homepage": "https://fnlb.net/",
6
7
  "devDependencies": {
7
8
  "@biomejs/biome": "^1.9.4",
8
- "@types/bun": "^1.2.1",
9
- "bun-plugin-dts": "^0.3.0",
10
- "@typescript-eslint/eslint-plugin": "^8.22.0",
11
- "@typescript-eslint/parser": "^8.22.0",
12
- "eslint": "^9.19.0"
9
+ "@types/bun": "^1.2.8",
10
+ "bun-plugin-dts": "^0.3.0"
13
11
  },
14
- "description": "Self-Host your own Fortnite LobbyBot with FNLB's system.",
12
+ "description": "Easily run your own bot using FNLB, a powerful and scalable system for managing Fortnite bots.",
15
13
  "files": ["dist"],
16
- "keywords": [],
14
+ "keywords": ["fnlb", "fortnite", "lobbybot", "self-host", "botlobbies"],
17
15
  "license": "ISC",
18
16
  "packageManager": "bun@1.1.17",
19
17
  "scripts": {