├── .gitignore ├── .npmignore ├── API.md ├── LICENSE ├── README.md ├── client.js ├── package-lock.json ├── package.json └── src └── LiveSplitClient.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Additional repo ignore 107 | tools/ 108 | 109 | dev.js 110 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Additional repo ignore 107 | tools/ -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## LiveSplitClient 4 | Node.js client for the LiveSplit Server running instance 5 | 6 | **Kind**: global class 7 | 8 | * [LiveSplitClient](#LiveSplitClient) 9 | * [new LiveSplitClient(address)](#new_LiveSplitClient_new) 10 | * [.connected](#LiveSplitClient+connected) : boolean 11 | * [.connect()](#LiveSplitClient+connect) ⇒ Promise 12 | * [.disconnect()](#LiveSplitClient+disconnect) ⇒ boolean 13 | * [.send(command, [expectResponse])](#LiveSplitClient+send) ⇒ Promise \| boolean 14 | * [.startTimer()](#LiveSplitClient+startTimer) ⇒ boolean 15 | * [.startOrSplit()](#LiveSplitClient+startOrSplit) ⇒ boolean 16 | * [.split()](#LiveSplitClient+split) ⇒ boolean 17 | * [.unsplit()](#LiveSplitClient+unsplit) ⇒ boolean 18 | * [.skipSplit()](#LiveSplitClient+skipSplit) ⇒ boolean 19 | * [.pause()](#LiveSplitClient+pause) ⇒ boolean 20 | * [.resume()](#LiveSplitClient+resume) ⇒ boolean 21 | * [.reset()](#LiveSplitClient+reset) ⇒ boolean 22 | * [.initGameTime()](#LiveSplitClient+initGameTime) ⇒ boolean 23 | * [.setGameTime(time)](#LiveSplitClient+setGameTime) ⇒ boolean 24 | * [.setLoadingTimes(time)](#LiveSplitClient+setLoadingTimes) ⇒ boolean 25 | * [.pauseGameTime()](#LiveSplitClient+pauseGameTime) ⇒ boolean 26 | * [.unpauseGameTime()](#LiveSplitClient+unpauseGameTime) ⇒ boolean 27 | * [.setComparison(comparison)](#LiveSplitClient+setComparison) ⇒ boolean 28 | * [.getDelta([comparison])](#LiveSplitClient+getDelta) ⇒ Promise 29 | * [.getLastSplitTime()](#LiveSplitClient+getLastSplitTime) ⇒ Promise 30 | * [.getComparisonSplitTime()](#LiveSplitClient+getComparisonSplitTime) ⇒ Promise 31 | * [.getCurrentTime()](#LiveSplitClient+getCurrentTime) ⇒ Promise 32 | * [.getFinalTime([comparison])](#LiveSplitClient+getFinalTime) ⇒ Promise 33 | * [.getPredictedTime([comparison])](#LiveSplitClient+getPredictedTime) ⇒ Promise 34 | * [.getBestPossibleTime()](#LiveSplitClient+getBestPossibleTime) ⇒ Promise 35 | * [.getSplitIndex()](#LiveSplitClient+getSplitIndex) ⇒ Promise 36 | * [.getCurrentSplitName()](#LiveSplitClient+getCurrentSplitName) ⇒ Promise 37 | * [.getPreviousSplitName()](#LiveSplitClient+getPreviousSplitName) ⇒ Promise 38 | * [.getCurrentTimerPhase()](#LiveSplitClient+getCurrentTimerPhase) ⇒ Promise 39 | * [.getAll()](#LiveSplitClient+getAll) ⇒ Promise 40 | 41 | 42 | 43 | ### new LiveSplitClient(address) 44 | 45 | | Param | Type | Description | 46 | | --- | --- | --- | 47 | | address | string | Connection address, like: 127.0.0.1:1234 | 48 | 49 | 50 | 51 | ### liveSplitClient.connected : boolean 52 | Client connection status. 53 | 54 | **Kind**: instance property of [LiveSplitClient](#LiveSplitClient) 55 | 56 | 57 | ### liveSplitClient.connect() ⇒ Promise 58 | Performs connection attempt to the LiveSplit Server instance. 59 | 60 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 61 | **Returns**: Promise - Connection result or error. 62 | 63 | 64 | ### liveSplitClient.disconnect() ⇒ boolean 65 | Disconnect client from the sever. 66 | 67 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 68 | **Returns**: boolean - Disconnection result. 69 | 70 | 71 | ### liveSplitClient.send(command, [expectResponse]) ⇒ Promise \| boolean 72 | Send command to the LiveSplit Server instance. 73 | 74 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 75 | **Returns**: Promise \| boolean - - Promise if answer was expected, else true. 76 | 77 | | Param | Type | Default | Description | 78 | | --- | --- | --- | --- | 79 | | command | string | | Existing LiveSplit Server command without linebreaks. | 80 | | [expectResponse] | boolean | true | Expect response from the server. | 81 | 82 | 83 | 84 | ### liveSplitClient.startTimer() ⇒ boolean 85 | Start timer 86 | 87 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 88 | 89 | 90 | ### liveSplitClient.startOrSplit() ⇒ boolean 91 | Start or split 92 | 93 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 94 | 95 | 96 | ### liveSplitClient.split() ⇒ boolean 97 | Split 98 | 99 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 100 | 101 | 102 | ### liveSplitClient.unsplit() ⇒ boolean 103 | Unsplit 104 | 105 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 106 | 107 | 108 | ### liveSplitClient.skipSplit() ⇒ boolean 109 | Skip split 110 | 111 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 112 | 113 | 114 | ### liveSplitClient.pause() ⇒ boolean 115 | Pause 116 | 117 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 118 | 119 | 120 | ### liveSplitClient.resume() ⇒ boolean 121 | Resume 122 | 123 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 124 | 125 | 126 | ### liveSplitClient.reset() ⇒ boolean 127 | Reset 128 | 129 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 130 | 131 | 132 | ### liveSplitClient.initGameTime() ⇒ boolean 133 | Init game time. Could be called only once according to LiveSplit Server documentation. 134 | 135 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 136 | 137 | 138 | ### liveSplitClient.setGameTime(time) ⇒ boolean 139 | Set game time 140 | 141 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 142 | 143 | | Param | Type | Description | 144 | | --- | --- | --- | 145 | | time | string | Game time | 146 | 147 | 148 | 149 | ### liveSplitClient.setLoadingTimes(time) ⇒ boolean 150 | Set loading times 151 | 152 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 153 | 154 | | Param | Type | Description | 155 | | --- | --- | --- | 156 | | time | string | Game time | 157 | 158 | 159 | 160 | ### liveSplitClient.pauseGameTime() ⇒ boolean 161 | Pause game time 162 | 163 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 164 | 165 | 166 | ### liveSplitClient.unpauseGameTime() ⇒ boolean 167 | Unpause game time 168 | 169 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 170 | 171 | 172 | ### liveSplitClient.setComparison(comparison) ⇒ boolean 173 | Set comparison 174 | 175 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 176 | 177 | | Param | Type | Description | 178 | | --- | --- | --- | 179 | | comparison | string | Comparison | 180 | 181 | 182 | 183 | ### liveSplitClient.getDelta([comparison]) ⇒ Promise 184 | Get delta 185 | 186 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 187 | **Returns**: Promise - Command result or null on timeout. 188 | 189 | | Param | Type | Description | 190 | | --- | --- | --- | 191 | | [comparison] | string | Comparison | 192 | 193 | 194 | 195 | ### liveSplitClient.getLastSplitTime() ⇒ Promise 196 | Get last split time 197 | 198 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 199 | **Returns**: Promise - Command result or null on timeout. 200 | 201 | 202 | ### liveSplitClient.getComparisonSplitTime() ⇒ Promise 203 | Get comparison split time 204 | 205 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 206 | **Returns**: Promise - Command result or null on timeout. 207 | 208 | 209 | ### liveSplitClient.getCurrentTime() ⇒ Promise 210 | Get current time 211 | 212 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 213 | **Returns**: Promise - Command result or null on timeout. 214 | 215 | 216 | ### liveSplitClient.getFinalTime([comparison]) ⇒ Promise 217 | Get final time 218 | 219 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 220 | **Returns**: Promise - Command result or null on timeout. 221 | 222 | | Param | Type | Description | 223 | | --- | --- | --- | 224 | | [comparison] | string | Comparison | 225 | 226 | 227 | 228 | ### liveSplitClient.getPredictedTime([comparison]) ⇒ Promise 229 | Get predicted time 230 | 231 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 232 | **Returns**: Promise - Command result or null on timeout. 233 | 234 | | Param | Type | Description | 235 | | --- | --- | --- | 236 | | [comparison] | string | Comparison | 237 | 238 | 239 | 240 | ### liveSplitClient.getBestPossibleTime() ⇒ Promise 241 | Get best pssible time 242 | 243 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 244 | **Returns**: Promise - Command result or null on timeout. 245 | 246 | 247 | ### liveSplitClient.getSplitIndex() ⇒ Promise 248 | Get split index 249 | 250 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 251 | **Returns**: Promise - Command result or null on timeout. 252 | 253 | 254 | ### liveSplitClient.getCurrentSplitName() ⇒ Promise 255 | Get current split name 256 | 257 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 258 | **Returns**: Promise - Command result or null on timeout. 259 | 260 | 261 | ### liveSplitClient.getPreviousSplitName() ⇒ Promise 262 | Get previous split name 263 | 264 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 265 | **Returns**: Promise - Command result or null on timeout. 266 | 267 | 268 | ### liveSplitClient.getCurrentTimerPhase() ⇒ Promise 269 | Get current timer phase 270 | 271 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 272 | **Returns**: Promise - Command result or null on timeout. 273 | 274 | 275 | ### liveSplitClient.getAll() ⇒ Promise 276 | Get all available information. Synthetic method that calls every server getter command if possible. 277 | 278 | **Kind**: instance method of [LiveSplitClient](#LiveSplitClient) 279 | **Returns**: Promise - Commands execution result or false on timeout. 280 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 satanch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-livesplit-client 2 | 3 | Zero-dependency Node.js client library for the LiveSplit Server. 4 | 5 | ## How to install 6 | 7 | Simply run in your terminal: 8 | 9 | ```sh 10 | npm install livesplit-client 11 | ``` 12 | 13 | ## How to use 14 | 15 | * Enable [LiveSplit Server](https://github.com/LiveSplit/LiveSplit?tab=readme-ov-file#the-livesplit-server). 16 | * Then look into this usage example: 17 | 18 | ```js 19 | const LiveSplitClient = require('livesplit-client'); 20 | 21 | (async () => { 22 | try { 23 | // Initialize client with LiveSplit Server's IP:PORT 24 | const client = new LiveSplitClient('127.0.0.1:16834'); 25 | 26 | // Connect to the server, Promise will be resolved when the connection will be succesfully established 27 | await client.connect(); 28 | 29 | // Start timer. 30 | client.startOrSplit(); 31 | 32 | // Job done, now we can close this connection 33 | client.disconnect(); 34 | } catch (err) { 35 | console.error(err); // Something went wrong 36 | } 37 | })(); 38 | ``` 39 | 40 | ## Library API 41 | 42 | ### Library docs 43 | 44 | [Click here and you will be navigated to the latest API docs](https://github.com/satanch/node-livesplit-client/blob/main/API.md). 45 | 46 | ### Setting custom timeout 47 | 48 | Default command response timeout is 100 ms. You can set your own timeout: 49 | 50 | ```js 51 | const LiveSplitClient = require('livesplit-client'); 52 | const client = new LiveSplitClient('127.0.0.1:16834'); 53 | client.timeout = 250; // Timeout in ms 54 | ``` 55 | 56 | ### Sending custom commands without library methods 57 | 58 | You could use `client.send('command', expectResponse)`. Please note, that `\r\n` will be automatically added to your command. 59 | If your are expecting the answer, your should specify `true` as second argument of this method. In other case `Promise` will not be returned and you could potentially break answers order. 60 | 61 | ## Extended example 62 | 63 | ```js 64 | const LiveSplitClient = require('livesplit-client'); 65 | 66 | (async () => { 67 | try { 68 | // Initialize client with LiveSplit Server's IP:PORT 69 | const client = new LiveSplitClient('127.0.0.1:16834'); 70 | 71 | // Connected event 72 | client.on('connected', () => { 73 | console.log('Connected!'); 74 | }); 75 | 76 | // Disconnected event 77 | client.on('disconnected', () => { 78 | console.log('Disconnected!'); 79 | }); 80 | 81 | // Error event 82 | client.on('error', (err) => { 83 | console.log(err); 84 | }); 85 | 86 | // Raw data reciever 87 | client.on('data', (data) => { 88 | console.log('Debug data:', data); 89 | }); 90 | 91 | // Some async sleep sugar for this example 92 | const sleep = (time) => { 93 | return new Promise((r) => { 94 | setTimeout(() => r(), time); 95 | }); 96 | }; 97 | 98 | // Connect to the server, Promise will be resolved when the connection will be succesfully established 99 | await client.connect(); 100 | 101 | // Start timer 102 | client.startOrSplit(); 103 | 104 | // Wait for 1 sec 105 | await sleep(1000); 106 | 107 | // Current time after 1 second 108 | const time = await client.getCurrentTime(); 109 | 110 | console.log('Current time after 1 sec.:', time); // Blazing fast and accurate numbers 111 | 112 | // Get split name 113 | const splitName = await client.getCurrentSplitName(); 114 | console.log('Split name:', splitName); 115 | 116 | // Get all available information 117 | const info = await client.getAll(); 118 | console.log('Summary:', info); 119 | 120 | // Pause and reset 121 | await client.pause(); 122 | await client.reset(); 123 | 124 | // Job done, now we can close this connection 125 | client.disconnect(); 126 | } catch (err) { 127 | console.error(err); // Something went wrong 128 | } 129 | })(); 130 | ``` 131 | 132 | ## Contribution 133 | 134 | Feel free to create issues and PR's. Thank you for your help! 135 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | /* 2 | node-livesplit-client 3 | Developed by satanch 4 | */ 5 | 6 | module.exports = require('./src/LiveSplitClient'); 7 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "livesplit-client", 3 | "version": "1.0.4", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "livesplit-client", 9 | "version": "1.0.4", 10 | "license": "MIT" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "livesplit-client", 3 | "version": "1.0.4", 4 | "description": "LiveSplit Server Node.js client implementation", 5 | "main": "client.js", 6 | "scripts": { 7 | "doc": "jsdoc2md ./src/LiveSplitClient.js > ./API.md" 8 | }, 9 | "keywords": [ 10 | "livesplit", 11 | "server" 12 | ], 13 | "author": "satanch", 14 | "license": "MIT", 15 | "repository": { 16 | "type" : "git", 17 | "url" : "https://github.com/satanch/node-livesplit-client.git" 18 | }, 19 | "homepage": "https://github.com/satanch/node-livesplit-client#readme" 20 | } 21 | -------------------------------------------------------------------------------- /src/LiveSplitClient.js: -------------------------------------------------------------------------------- 1 | const net = require('net'); 2 | const EventEmitter = require('events'); 3 | const { deprecate } = require('util'); 4 | 5 | /** 6 | * Node.js client for the LiveSplit Server running instance 7 | * @constructor 8 | * @param {string} address - Connection address, like: 127.0.0.1:1234 9 | */ 10 | class LiveSplitClient extends EventEmitter { 11 | constructor(address) { 12 | super(); 13 | 14 | if (typeof address !== 'string') 15 | throw new TypeError('Invalid argument type! IP:PORT expected.'); 16 | 17 | address = address.split(':'); 18 | 19 | if (address.length !== 2) 20 | throw new Error('Failed to parse connection details! IP:PORT expected.'); 21 | 22 | this._connectionDetails = { 23 | ip: address[0], 24 | port: parseInt(address[1]) 25 | }; 26 | 27 | this._connected = false; 28 | this.timeout = 100; 29 | this._processingCommands = false; 30 | this._commands = []; 31 | this._noResponseCommands = []; 32 | 33 | /* 34 | According to: https://github.com/LiveSplit/LiveSplit.Server/blob/a4a57716dce90936606bfc8f8ac84f7623773aa5/README.md#commands 35 | 36 | When using Game Time, it's important that you call "initgametime" once. Once "initgametime" is used, an additional comparison will appear and you can switch to it via the context menu (Compare Against > Game Time). This special comparison will show everything based on the Game Time (every component now shows Game Time based information). 37 | */ 38 | this._initGameTimeOnce = false; 39 | 40 | this.getPreviousSplitname = deprecate(this.getPreviousSplitname, 'Method "getPreviousSplitname" is deprecated! Please, use "getPreviousSplitName" (capital letter "N") instead.'); 41 | 42 | return this; 43 | } 44 | 45 | /** 46 | * Performs connection attempt to the LiveSplit Server instance. 47 | * @returns {Promise} Connection result or error. 48 | */ 49 | connect() { 50 | this._socket = new net.Socket(); 51 | 52 | return new Promise((resolve, reject) => { 53 | this._socket.connect(this._connectionDetails.port, this._connectionDetails.ip, () => { 54 | this._connected = true; 55 | this.emit('connected'); 56 | resolve(this._connected); 57 | }); 58 | 59 | this._socket.on('data', (data) => { 60 | this.emit( 61 | 'data', 62 | data.toString('utf-8').replace('\r\n', '') 63 | ); 64 | }); 65 | 66 | this._socket.on('error', (err) => { 67 | this.emit('error', err); 68 | reject(err); 69 | }); 70 | 71 | this._socket.on('close', () => { 72 | this._connected = false; 73 | this.emit('disconnected'); 74 | }); 75 | }); 76 | } 77 | 78 | /** 79 | * Disconnect client from the sever. 80 | * @returns {boolean} Disconnection result. 81 | */ 82 | disconnect() { 83 | if (!this._connected) return false; 84 | this._socket.destroy(); 85 | this._connected = false; 86 | return true; 87 | } 88 | 89 | /** 90 | * Client connection status. 91 | * @type {boolean} 92 | */ 93 | get connected() { 94 | return this._connected; 95 | } 96 | 97 | /** 98 | * Send command to the LiveSplit Server instance. 99 | * @param {string} command - Existing LiveSplit Server command without linebreaks. 100 | * @param {boolean} [expectResponse=true] - Expect response from the server. 101 | * @returns {Promise|boolean} - Promise if answer was expected, else true. 102 | */ 103 | send(command, expectResponse = true) { 104 | if (!this._connected) 105 | throw new Error('Client must be connected to the server!'); 106 | 107 | if (typeof command !== 'string') 108 | throw new Error('String expected!'); 109 | 110 | this._checkDisallowedSymbols(command); 111 | 112 | command = `${command}\r\n`; 113 | 114 | if (expectResponse) { 115 | let resolve = null; 116 | let promise = new Promise((res) => { 117 | resolve = res; 118 | }); 119 | 120 | this._commands.push({ 121 | command, 122 | resolve 123 | }); 124 | 125 | if (!this._processingCommands) 126 | this._sendNext(); 127 | 128 | return promise; 129 | } else { 130 | if (!this._processingCommands) 131 | this._socket.write(command); 132 | else 133 | this._noResponseCommands.push(command); 134 | 135 | return true; 136 | } 137 | } 138 | 139 | _sendNext() { 140 | if (this._commands.length === 0) { 141 | this._processingCommands = false; 142 | 143 | while (this._noResponseCommands.length > 0) 144 | this._socket.write(this._noResponseCommands.pop()); 145 | 146 | return; 147 | } 148 | 149 | this._processingCommands = true; 150 | 151 | let next = this._commands[0]; 152 | 153 | let timeout = setTimeout(() => { 154 | this._commands.shift(); 155 | this.removeListener('data', listener); 156 | next.resolve(null); 157 | this._sendNext(); 158 | }, this.timeout); 159 | 160 | let listener = (data) => { 161 | this._commands.shift(); 162 | clearTimeout(timeout); 163 | next.resolve(data); 164 | this._sendNext(); 165 | }; 166 | 167 | this.once('data', listener); 168 | this._socket.write(next.command); 169 | } 170 | 171 | _checkDisallowedSymbols(str) { 172 | if (str.indexOf('\r\n') !== -1) 173 | throw new Error('No newline symbols allowed!'); 174 | 175 | return true; 176 | } 177 | 178 | /** 179 | * Start timer 180 | * @returns {boolean} 181 | */ 182 | startTimer() { 183 | return this.send('starttimer', false); 184 | } 185 | 186 | /** 187 | * Start or split 188 | * @returns {boolean} 189 | */ 190 | startOrSplit() { 191 | return this.send('startorsplit', false); 192 | } 193 | 194 | /** 195 | * Split 196 | * @returns {boolean} 197 | */ 198 | split() { 199 | return this.send('split', false); 200 | } 201 | 202 | /** 203 | * Unsplit 204 | * @returns {boolean} 205 | */ 206 | unsplit() { 207 | return this.send('unsplit', false); 208 | } 209 | 210 | /** 211 | * Skip split 212 | * @returns {boolean} 213 | */ 214 | skipSplit() { 215 | return this.send('skipsplit', false); 216 | } 217 | 218 | /** 219 | * Pause 220 | * @returns {boolean} 221 | */ 222 | pause() { 223 | return this.send('pause', false); 224 | } 225 | 226 | /** 227 | * Resume 228 | * @returns {boolean} 229 | */ 230 | resume() { 231 | return this.send('resume', false); 232 | } 233 | 234 | /** 235 | * Reset 236 | * @returns {boolean} 237 | */ 238 | reset() { 239 | return this.send('reset', false); 240 | } 241 | 242 | /** 243 | * Init game time. Could be called only once according to LiveSplit Server documentation. 244 | * @returns {boolean} 245 | */ 246 | initGameTime() { 247 | if (this._initGameTimeOnce) return false; 248 | this._initGameTimeOnce = true; 249 | return this.send('initgametime', false); 250 | } 251 | 252 | /** 253 | * Set game time 254 | * @param {string} time - Game time 255 | * @returns {boolean} 256 | */ 257 | setGameTime(time) { 258 | return this.send(`setgametime ${time}`, false); 259 | } 260 | 261 | /** 262 | * Set loading times 263 | * @param {string} time - Game time 264 | * @returns {boolean} 265 | */ 266 | setLoadingTimes(time) { 267 | return this.send(`setloadingtimes ${time}`, false); 268 | } 269 | 270 | /** 271 | * Pause game time 272 | * @returns {boolean} 273 | */ 274 | pauseGameTime() { 275 | return this.send('pausegametime', false); 276 | } 277 | 278 | /** 279 | * Unpause game time 280 | * @returns {boolean} 281 | */ 282 | unpauseGameTime() { 283 | return this.send('unpausegametime', false); 284 | } 285 | 286 | /** 287 | * Set comparison 288 | * @param {string} comparison - Comparison 289 | * @returns {boolean} 290 | */ 291 | setComparison(comparison) { 292 | return this.send(`setcomparison ${comparison}`, false); 293 | } 294 | 295 | /** 296 | * Get delta 297 | * @param {string} [comparison] - Comparison 298 | * @returns {Promise} Command result or null on timeout. 299 | */ 300 | getDelta(comparison = '') { 301 | if (comparison) comparison = ` ${comparison}`; 302 | return this.send(`getdelta${comparison}`, true); 303 | } 304 | 305 | /** 306 | * Get last split time 307 | * @returns {Promise} Command result or null on timeout. 308 | */ 309 | getLastSplitTime() { 310 | return this.send('getlastsplittime', true); 311 | } 312 | 313 | /** 314 | * Get comparison split time 315 | * @returns {Promise} Command result or null on timeout. 316 | */ 317 | getComparisonSplitTime() { 318 | return this.send('getcomparisonsplittime', true); 319 | } 320 | 321 | /** 322 | * Get current time 323 | * @returns {Promise} Command result or null on timeout. 324 | */ 325 | getCurrentTime() { 326 | return this.send('getcurrenttime', true); 327 | } 328 | 329 | /** 330 | * Get final time 331 | * @param {string} [comparison] - Comparison 332 | * @returns {Promise} Command result or null on timeout. 333 | */ 334 | getFinalTime(comparison = '') { 335 | if (comparison) comparison = ` ${comparison}`; 336 | return this.send(`getfinaltime${comparison}`, true); 337 | } 338 | 339 | /** 340 | * Get predicted time 341 | * @param {string} [comparison] - Comparison 342 | * @returns {Promise} Command result or null on timeout. 343 | */ 344 | getPredictedTime(comparison = '') { 345 | if (comparison) comparison = ` ${comparison}`; 346 | return this.send(`getpredictedtime${comparison}`, true); 347 | } 348 | 349 | /** 350 | * Get best pssible time 351 | * @returns {Promise} Command result or null on timeout. 352 | */ 353 | getBestPossibleTime() { 354 | return this.send('getbestpossibletime', true); 355 | } 356 | 357 | /** 358 | * Get split index 359 | * @returns {Promise} Command result or null on timeout. 360 | */ 361 | getSplitIndex() { 362 | return this.send('getsplitindex', true); 363 | } 364 | 365 | /** 366 | * Get current split name 367 | * @returns {Promise} Command result or null on timeout. 368 | */ 369 | getCurrentSplitName() { 370 | return this.send('getcurrentsplitname', true); 371 | } 372 | 373 | /** 374 | * Get previous split name 375 | * @returns {Promise} Command result or null on timeout. 376 | */ 377 | getPreviousSplitName() { 378 | return this.send('getprevioussplitname', true); 379 | } 380 | 381 | getPreviousSplitname() { 382 | return this.getPreviousSplitName(); 383 | } 384 | 385 | /** 386 | * Get current timer phase 387 | * @returns {Promise} Command result or null on timeout. 388 | */ 389 | getCurrentTimerPhase() { 390 | return this.send('getcurrenttimerphase', true); 391 | } 392 | 393 | /** 394 | * Get all available information. Synthetic method that calls every server getter command if possible. 395 | * @returns {Promise} Commands execution result or false on timeout. 396 | */ 397 | async getAll() { 398 | const output = {}; 399 | 400 | for (let method of ['getCurrentTimerPhase', 'getDelta', 'getLastSplitTime', 'getComparisonSplitTime', 'getCurrentTime', 'getFinalTime', 'getPredictedTime', 'getBestPossibleTime', 'getSplitIndex', 'getCurrentSplitName', 'getPreviousSplitName']) { 401 | output[ 402 | method.replace('get', '').charAt(0).toLowerCase() + method.replace('get', '').slice(1) 403 | ] = await this[method](); 404 | } 405 | 406 | return output; 407 | } 408 | } 409 | 410 | module.exports = LiveSplitClient; 411 | --------------------------------------------------------------------------------