feat(protocols/sdtd): add telnet support for players (#692)

* feat(protocols/sdtd): add telnet support

* docs: update CHANGELOG.md
This commit is contained in:
CosminPerRam 2025-04-27 17:06:23 +03:00 committed by GitHub
parent 4db8a473ff
commit 71ce074866
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 159 additions and 5 deletions

View file

@ -7,6 +7,7 @@ import Logger from '../lib/Logger.js'
import DnsResolver from '../lib/DnsResolver.js'
import { Results } from '../lib/Results.js'
import Promises from '../lib/Promises.js'
import { Telnet } from 'telnet-client'
let uid = 0
@ -27,6 +28,8 @@ export default class Core extends EventEmitter {
this.udpSocket = null
this.shortestRTT = 0
this.usedTcp = false
this.telnetClient = new Telnet()
}
// Runs a single attempt with a timeout and cleans up afterward
@ -336,4 +339,23 @@ export default class Core extends EventEmitter {
requestPromise?.cancel()
}
}
async telnetConnect (params) {
await this.telnetClient.connect({
timeout: 2000,
execTimeout: 2000,
host: this.options.host,
debug: this.debugEnabled,
...params
})
}
async telnetExecute (command) {
return await this.telnetClient.exec(command)
}
async telnetClose () {
await this.telnetClient.end()
await this.telnetClient.destroy()
}
}

View file

@ -72,11 +72,12 @@ import xonotic from './xonotic.js'
import altvmp from './altvmp.js'
import vintagestorymaster from './vintagestorymaster.js'
import vintagestory from './vintagestory.js'
import sdtd from './sdtd.js'
export {
armagetron, ase, asa, assettocorsa, battlefield, brokeprotocol, brokeprotocolmaster, buildandshoot, cs2d, discord, doom3, eco, epic, factorio, farmingsimulator, ffow,
fivem, gamespy1, gamespy2, gamespy3, geneshift, goldsrc, gtasao, hawakening, hawakeningmaster, hexen2, jc2mp, kspdmp, mafia2mp, mafia2online, minecraft,
minecraftbedrock, minecraftvanilla, minetest, mumble, mumbleping, nadeo, openttd, palworld, quake1, quake2, quake3, renegadex, renegadexmaster, renown, rfactor, ragemp, samp,
satisfactory, soldat, savage2, starmade, starsiege, teamspeak2, teamspeak3, terraria, toxikk, tribes1, tribes1master, unreal2, ut3, valve,
vcmp, ventrilo, warsow, eldewrito, beammpmaster, beammp, dayz, theisleevrima, xonotic, altvmp, vintagestorymaster, vintagestory
vcmp, ventrilo, warsow, eldewrito, beammpmaster, beammp, dayz, theisleevrima, xonotic, altvmp, vintagestorymaster, vintagestory, sdtd
}

61
protocols/sdtd.js Normal file
View file

@ -0,0 +1,61 @@
import Valve from './valve.js'
import { Players } from '../lib/Results.js'
const playerLineRegex = /(?<=id=\d+,\s*)(?<name>\S[^,]*)(?=,)/
const sanitizeTelnetResponse = response => {
return response
.split(/\r?\n/)
.map(l => l.replace(/\r$/, '').trim())
.filter(l => l.length > 0)
}
export default class sdtd extends Valve {
async run (state) {
await super.run(state)
await this.telnetCalls(state)
}
async telnetCalls (state) {
const telnetPort = this.options.telnetPort
const telnetPassword = this.options.telnetPassword
if (!telnetPort || !telnetPassword) {
this.logger.debug('No telnet args given, skipping.')
return
}
if (!this.options.requestPlayers) {
return
}
await this.telnetConnect({
port: telnetPort,
password: telnetPassword,
passwordPrompt: /Please enter password:/i,
shellPrompt: /\r\n$/
})
await this.telnetCallPlayers(state)
await this.telnetClose()
}
async telnetCallPlayers (state) {
const playersResponse = await this.telnetExecute('listplayers')
state.players = new Players()
for (const possiblePlayerLine of sanitizeTelnetResponse(playersResponse)) {
const match = possiblePlayerLine.match(playerLineRegex)
const name = match?.groups?.name
if (name) {
state.players.push({
name,
responseLine: possiblePlayerLine
})
}
}
state.raw.telnetPlayersResponse = playersResponse
}
}