ts3bot/services/ts3client.iced

277 lines
9.0 KiB
Plaintext
Raw Normal View History

xvfb = require("xvfb")
log = require("../logger")("TS3Client")
config = require("../config")
services = require("../services")
x11tools = require("../x11")
TS3Settings = require("../ts3settings")
TS3ClientQuery = require("../ts3query")
path = require "path"
merge = require "merge"
fs = require "fs"
spawn = require("child_process").spawn
StreamSplitter = require("stream-splitter")
require_bin = require("../require_bin")
ts3client_binpath = require_bin path.join(config.get("ts3-install-path"), "ts3client_linux_" + (if process.arch == "x64" then "amd64" else process.arch))
module.exports = class TS3ClientService extends services.Service
dependencies: [
"xvfb",
"blackbox",
"pulseaudio"
]
constructor: -> super "TS3Client",
start: (args, cb) =>
if typeof args == "function"
cb = args
args = null
if @process
cb? null, @process
return
if not args
args = []
await fs.access ts3client_binpath, fs.R_OK | fs.X_OK, defer err
if err
2015-10-16 07:47:58 +00:00
log.error "Can't access TeamSpeak3 client binary at #{ts3client_binpath}, does the binary exist and have you given correct access?"
cb? new Error "Access to TeamSpeak3 binary failed."
return
await @_preconfigure defer()
# spawn process
proc = null
doStart = null
forwardLog = (token) =>
token = token.trim() # get rid of \r
if token.indexOf("|") > 0
token = token.split("|")
level = token[1].toUpperCase().trim()
source = token[2].trim()
sourceStr = if source then "#{source}: " else ""
message = token[4].trim()
switch level
when "ERROR" then log.error "%s%s", sourceStr, message
when "WARN" then log.warn "%s%s", sourceStr, message
when "INFO" then log.debug "%s%s", sourceStr, message
else log.silly "%s%s", sourceStr, message
else
log.debug token
scheduleRestart = null # see below
onExit = => # autorestart
@_running = false
@process = null
if @_requestedExit
return
log.warn "TeamSpeak3 unexpectedly terminated!"
scheduleRestart()
doStart = () =>
env =
HOME: process.env.HOME
DISPLAY: process.env.DISPLAY
XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR
KDEDIRS: ''
KDEDIR: ''
QTDIR: config.get("ts3-install-path")
QT_PLUGIN_PATH: config.get("ts3-install-path")
LD_LIBRARY_PATH: config.get("ts3-install-path")
if process.env.LD_LIBRARY_PATH
env.LD_LIBRARY_PATH += ":#{process.env.LD_LIBRARY_PATH}"
@log.silly "Environment variables:", env
@log.silly "Arguments:", JSON.stringify args
@_requestedExit = false
proc = spawn ts3client_binpath, args,
detached: true
stdio: ['ignore', 'pipe', 'pipe']
cwd: config.get("ts3-install-path")
env: env
@_running = true
# logging
stdoutTokenizer = proc.stdout.pipe StreamSplitter "\n"
stdoutTokenizer.encoding = "utf8";
stdoutTokenizer.on "token", forwardLog
stderrTokenizer = proc.stderr.pipe StreamSplitter "\n"
stderrTokenizer.encoding = "utf8";
stderrTokenizer.on "token", forwardLog
# connect to client query plugin when it's loaded
stdoutTokenizer.on "token", (token) =>
if token.indexOf("Loading plugin: libclientquery_plugin") >= 0
# client query plugin is now loading
@_queryReconnectTimer = setTimeout @query.connect.bind(@query), 250
stderrTokenizer.on "token", (token) =>
if token.indexOf("Query: bind failed") >= 0
# without query this ts3 instance is worthless
await @_ts3GracefulShutdown defer()
scheduleRestart()
# autorestart
proc.on "exit", onExit
@process = proc
scheduleRestart = () =>
log.warn "Restarting in 5 seconds..."
@_startTimer = setTimeout doStart.bind(@), 5000
if @_queryReconnectTimer
clearTimeout @_queryReconnectTimer
doStart()
# ts3 query
@query = new TS3ClientQuery "127.0.0.1", 25639
@_queryReconnectTimer = null
@query.on "error", (err) =>
log.warn "Error in TS3 query connection", err
@query.on "close", =>
if not @_requestedExit
log.warn "Connection to TS3 client query interface lost, reconnecting..."
@_queryReconnectTimer = setTimeout @query.connect.bind(@query), 1000
else
log.debug "Connection to TS3 client query interface lost."
@query.on "open", => log.debug "Connected to TS3 client query interface."
@query.on "connecting", => log.debug "Connecting to TS3 client query interface..."
cb? null, @process
stop: (cb) -> @_ts3GracefulShutdown cb
_ts3GracefulShutdown: (cb) ->
@_requestedExit = true
if @_startTimer
clearTimeout @_startTimer
if @_queryReconnectTimer
clearTimeout @_queryReconnectTimer
if @_running
log.silly "Using xdotool to gracefully shut down TS3"
await x11tools.getWindowIdByProcessId @process.pid, defer(err, wid)
if not wid
log.debug "Can not find a window for #{@name}."
log.warn "Can not properly shut down #{@name}, it will time out on the server instead."
@process.kill()
else
log.silly "Sending keys to TS3"
await x11tools.sendKeys wid, "ctrl+q", defer(err)
if err
log.warn "Can not properly shut down #{@name}, it will time out on the server instead."
log.silly "Using SIGTERM for shutdown of #{@name}"
@process.kill()
# wait for 10 seconds then SIGKILL if still up
log.silly "Now waiting 10 seconds for shutdown..."
killTimer = setTimeout (() =>
log.silly "10 seconds gone, using SIGKILL now since we're impatient."
@process.kill("SIGKILL")), 10000
await @process.once "exit", defer()
clearTimeout killTimer
@_running = false
@process = null
else
log.warn "TeamSpeak3 seems to have died prematurely."
cb?()
_preconfigure: (cb) =>
ts3settings = new TS3Settings config.get("ts3-config-path")
await ts3settings.open defer()
# Delete bookmars to prevent auto-connect bookmarks from weirding out the client
await ts3settings.query "delete * from Bookmarks", defer()
# Let's make sure we have an identity!
force = ts3settings.getIdentitiesSize() <= 0 or config.get("identity-path")
if force
if not config.get("identity-path")
throw new Error "Need a file to import the bot's identity from."
ts3settings.clearIdentities()
await ts3settings.importIdentity config.get("identity-path"), defer(identity)
identity.select()
if config.get("nickname")
# Enforce nickname from configuration
identity = ts3settings.getSelectedIdentity()
identity.nickname = config.get "nickname"
identity.update()
# Some settings to help the TS3Bot to do what it's supposed to do
2015-10-16 08:11:38 +00:00
now = new Date()
await ts3settings.setMultiple [
[ "Application", "HotkeyMode", "2" ]
[ "Chat", "MaxLines", "1" ]
[ "Chat", "LogChannelChats", "0" ]
[ "Chat", "LogClientChats", "0" ]
[ "Chat", "LogServerChats", "0" ]
[ "Chat", "ReloadChannelChats", "0" ]
[ "Chat", "ReloadClientChats", "0" ]
[ "Chat", "IndicateChannelChats", "0" ]
[ "Chat", "IndicatePrivateChats", "0" ]
[ "Chat", "IndicateServerChats", "0" ]
[ "ClientLogView", "LogLevel", "000001" ]
[ "FileTransfer", "SimultaneousDownloads", "2" ]
[ "FileTransfer", "SimultaneousUploads", "2" ]
[ "FileTransfer", "UploadBandwidth", "0" ]
[ "FileTransfer", "DownloadBandwidth", "0" ]
[ "General", "LastShownLicense", "1" ] # ugh...
[ "General", "LastShownLicenseLang", "C" ]
[ "Global", "MainWindowMaximized", "1" ]
[ "Global", "MainWindowMaximizedScreen", "1" ]
[ "Messages", "Disconnect", config.get "quit-message" ]
[ "Misc", "WarnWhenMutedInfoShown", "1" ]
[ "Misc", "LastShownNewsBrowserVersion", "4" ]
[ "News", "NewsClosed", "1" ]
[ "News", "Language", "en" ]
2015-10-16 08:11:38 +00:00
[ "News", "LastModified", now.toISOString() ]
[ "News", "NextCheck", new Date(now.getTime() + 1000 * 60 * 60 * 24 * 365).toISOString() ]
[ "Notifications", "SoundPack", "nosounds" ]
[ "Plugins", "teamspeak_control_plugin", "false" ]
2015-10-16 08:37:19 +00:00
[ "Plugins", "clientquery_plugin", "true" ]
[ "Plugins", "lua_plugin", "false" ]
[ "Plugins", "test_plugin", "false" ]
[ "Plugins", "ts3g15", "false" ]
[ "Profiles", "DefaultPlaybackProfile", "Default" ]
[ "Profiles", "Playback/Default", {
Device: ''
DeviceDisplayName: "Default"
VolumeModifier: -40
VolumeFactorWaveDb: -17
PlayMicClicksOnOwn: false
PlayMicClicksOnOthers: false
MonoSoundExpansion: 2
Mode: "PulseAudio"
PlaybackMonoOverCenterSpeaker: false
} ]
[ "Profiles", "DefaultCaptureProfile", "Default" ]
[ "Profiles", "Capture/Default", {
Device: ''
DeviceDisplayName: "Default"
Mode: "PulseAudio"
} ]
[ "Profiles", "Capture/Default/PreProcessing", {
continous_transmission: "false"
vad: "true"
vad_over_ptt: "false"
delay_ptt_msecs: "250"
voiceactivation_level: "-49"
echo_reduction: false
echo_cancellation: false
denoise: false
delay_ptt: false
agc: if config.get("ts3-agc") then "true" else "false"
echo_reduction_db: 10
} ]
[ "Statistics", "ParticipateStatistics", "0" ]
[ "Statistics", "ConfirmedParticipation", "1" ]
], defer()
await ts3settings.close defer()
cb?()