mirror of https://github.com/icedream/ts3bot.git
Update to ICSv2.
ICSv2 will be used as a preparation to fully change to ES2017 only. For now the compilation will be done by transpiling from ICSv2 to ES2015+, then using lebab to upgrade some left-over code parts and then using Babel to transpile down some of the features that Node does not support yet. This way we already make sure we can theoretically use ES2017. - Use import instead of require. - Rename *.iced to *.coffee with some exceptions (see below). - Rename app.iced to index.coffee. - Rename app.js to index.js. - Wrap app code in function definition to avoid import statements not being at top-most root. - Avoid using fat arrows where unnecessary to reduce overhead in generated ES code. Known issues: - Currently the code can not be run without being built first. We need to see how we can solve that soon.develop
parent
f3b2230976
commit
07de32a44e
|
@ -1,7 +1,34 @@
|
||||||
.git
|
# Logs
|
||||||
**/.gitignore
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
# Deployed apps should consider commenting this line out:
|
||||||
|
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
.git*
|
||||||
|
.docker*
|
||||||
Dockerfile
|
Dockerfile
|
||||||
src/node_modules
|
|
||||||
**/*.log
|
**/*.log
|
||||||
**/*.ini
|
**/*.ini
|
||||||
**/*.md
|
**/*.md
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
|
@ -17,12 +16,14 @@ coverage
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
.grunt
|
.grunt
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
build/Release
|
build/Release
|
||||||
|
|
||||||
# Dependency directory
|
# Dependency directory
|
||||||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
# Deployed apps should consider commenting this line out:
|
||||||
|
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
# Built files
|
||||||
|
src.js
|
||||||
|
dist
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:6.3.1
|
FROM node:7.10.0
|
||||||
|
|
||||||
ARG TS3CLIENT_VERSION=3.0.19.4
|
ARG TS3CLIENT_VERSION=3.0.19.4
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "ts3bot",
|
"name": "ts3bot",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"description": "Allows running TeamSpeak3 as a bot for all kinds of media (local music/videos/streams/YouTube/...) without the need for a real GUI to exist.",
|
"description": "Allows running TeamSpeak3 as a bot for all kinds of media (local music/videos/streams/YouTube/...) without the need for a real GUI to exist.",
|
||||||
"main": "app.js",
|
"main": "dist/node7/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"ts3bot": "app.js"
|
"ts3bot": "dist/node7/index.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"teamspeak",
|
"teamspeak",
|
||||||
|
@ -20,15 +20,22 @@
|
||||||
"media",
|
"media",
|
||||||
"musicbot"
|
"musicbot"
|
||||||
],
|
],
|
||||||
"author": "Carl Kittelberger <icedream2k9@die-optimisten.net>",
|
"scripts": {
|
||||||
|
"iced2es": "iced3 -I none -b --no-header -o src.js -c src && lebab -t let,multi-var,for-each,arg-rest,arg-spread,obj-method,obj-shorthand,no-strict,exponent,template,default-param,includes,class --replace src.js",
|
||||||
|
"es2017-node7": "babel src.js --presets es2017-node7 --source-maps --out-dir ./dist/node7",
|
||||||
|
"build": "npm run iced2es && npm run es2017-node7",
|
||||||
|
"prepublish": "npm run build",
|
||||||
|
"start": "node .",
|
||||||
|
"start-coffee": "node ./src/index.js"
|
||||||
|
},
|
||||||
|
"author": "Carl Kittelberger <icedream@icedream.pw>",
|
||||||
"license": "GPL-3.0+",
|
"license": "GPL-3.0+",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/icedream/ts3bot-control.git"
|
"url": "https://github.com/icedream/ts3bot.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"iced-coffee-script": "^108.0.8",
|
|
||||||
"merge": "^1.2.0",
|
"merge": "^1.2.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"named-regexp": "^0.1.1",
|
"named-regexp": "^0.1.1",
|
||||||
|
@ -49,7 +56,17 @@
|
||||||
"webchimera.js": "^0.2.7",
|
"webchimera.js": "^0.2.7",
|
||||||
"which": "^1.1.2",
|
"which": "^1.1.2",
|
||||||
"winston": "^2.3.1",
|
"winston": "^2.3.1",
|
||||||
"xvfb": "git://github.com/icedream/node-xvfb.git",
|
"xvfb": "^0.2.3",
|
||||||
"youtube-dl": "^1.10.5"
|
"youtube-dl": "^1.10.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.24.1",
|
||||||
|
"babel-preset-es2017-node7": "^0.5.2",
|
||||||
|
"iced-coffee-script-3": "^111.1.1",
|
||||||
|
"lebab": "^2.7.2"
|
||||||
|
},
|
||||||
|
"engineStrict": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 7.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,382 @@
|
||||||
|
import { track as tempTrack } from 'temp'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import prettyMs from 'pretty-ms'
|
||||||
|
import qs from 'querystring'
|
||||||
|
import request from 'request'
|
||||||
|
import Sync from 'sync'
|
||||||
|
import validUrl from 'valid-url'
|
||||||
|
import youtubedl from 'youtube-dl'
|
||||||
|
|
||||||
|
import config from './config'
|
||||||
|
import getLogger from './logger'
|
||||||
|
import services from './services'
|
||||||
|
import parseDuration from './parse_duration.iced'
|
||||||
|
|
||||||
|
isValidUrl = validUrl.isWebUri
|
||||||
|
track = tempTrack()
|
||||||
|
log = getLogger "Main"
|
||||||
|
|
||||||
|
# http://stackoverflow.com/a/7117336
|
||||||
|
removeBB = (str) -> str.replace /\[(\w+)[^\]]*](.*?)\[\/\1]/g, "$2"
|
||||||
|
|
||||||
|
shutdown = (cb) ->
|
||||||
|
ts3clientService = services.find("ts3client")
|
||||||
|
if ts3clientService and ts3clientService.state == "started"
|
||||||
|
await ts3clientService.stop defer(err)
|
||||||
|
if err
|
||||||
|
cb? new Error "Could not stop TeamSpeak3"
|
||||||
|
return
|
||||||
|
|
||||||
|
log.debug "Shutting down services..."
|
||||||
|
await services.shutdown defer(err)
|
||||||
|
if err
|
||||||
|
cb? new Error "Error while shutting down rest of services."
|
||||||
|
log.debug "Services shut down."
|
||||||
|
|
||||||
|
cb?()
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
shutdown: shutdown
|
||||||
|
shutdownSync: => Sync @shutdown
|
||||||
|
|
||||||
|
run = ->
|
||||||
|
# Separate our own PulseAudio from any system one by using our own custom XDG directories.
|
||||||
|
process.env.XDG_RUNTIME_DIR = temp.mkdirSync "ts3bot-xdg"
|
||||||
|
|
||||||
|
# Xvfb for isolated graphical interfaces!
|
||||||
|
xvfbService = services.find("xvfb")
|
||||||
|
await xvfbService.start defer(err, vlc)
|
||||||
|
if err
|
||||||
|
if not process.env.DISPLAY? or process.env.DISPLAY.trim() == ""
|
||||||
|
log.error "Xvfb could not start up and no display is available!", err
|
||||||
|
await shutdown defer()
|
||||||
|
process.exit 1
|
||||||
|
log.warn "Xvfb could not start up - will use existing display!", err
|
||||||
|
|
||||||
|
# PulseAudio daemon
|
||||||
|
await services.find("pulseaudio").start defer err
|
||||||
|
if err
|
||||||
|
log.warn "PulseAudio could not start up, audio may not act as expected!", err
|
||||||
|
|
||||||
|
# VLC via WebChimera.js
|
||||||
|
vlcService = services.find("vlc")
|
||||||
|
await vlcService.start defer(err, vlc)
|
||||||
|
if err
|
||||||
|
log.warn "VLC could not start up!", err
|
||||||
|
await shutdown defer()
|
||||||
|
process.exit 1
|
||||||
|
|
||||||
|
# This is where we keep track of the volume
|
||||||
|
vlcVolume = 50
|
||||||
|
|
||||||
|
# Cached information for tracks in playlist
|
||||||
|
vlcMediaInfo = {}
|
||||||
|
|
||||||
|
# TeamSpeak3
|
||||||
|
ts3clientService = services.find("ts3client")
|
||||||
|
|
||||||
|
ts3clientService.on "started", (ts3proc) ->
|
||||||
|
ts3query = ts3clientService.query
|
||||||
|
|
||||||
|
ts3clientService.once "stopped", () ->
|
||||||
|
ts3query = undefined
|
||||||
|
|
||||||
|
# VLC event handling
|
||||||
|
vlc.onPlaying = () ->
|
||||||
|
try
|
||||||
|
# TODO: Check why info is sometimes null, something must be wrong with the "add"/"play" commands here!
|
||||||
|
# TODO: Do not format as URL in text message if MRL points to local file
|
||||||
|
|
||||||
|
item = vlc.playlist.items[vlc.playlist.currentItem]
|
||||||
|
info = vlcMediaInfo[item.mrl]
|
||||||
|
url = info?.originalUrl or item.mrl
|
||||||
|
title = info?.title or item.mrl
|
||||||
|
ts3query?.sendtextmessage 2, 0, "Now playing [URL=#{url}]#{title}[/URL]."
|
||||||
|
|
||||||
|
# Restore audio volume
|
||||||
|
vlc.audio.volume = vlcVolume
|
||||||
|
catch e
|
||||||
|
log.warn "Error in VLC onPlaying handler", e
|
||||||
|
|
||||||
|
vlc.onPaused = () -> ts3query?.sendtextmessage 2, 0, "Paused."
|
||||||
|
vlc.onForward = () -> ts3query?.sendtextmessage 2, 0, "Fast-forwarding..."
|
||||||
|
vlc.onBackward = () -> ts3query?.sendtextmessage 2, 0, "Rewinding..."
|
||||||
|
vlc.onEncounteredError = () -> log.error "VLC has encountered an error! You will need to restart the bot.", arguments
|
||||||
|
vlc.onStopped = () -> ts3query?.sendtextmessage 2, 0, "Stopped."
|
||||||
|
|
||||||
|
ts3query.currentScHandlerID = 1
|
||||||
|
ts3query.mydata = {}
|
||||||
|
|
||||||
|
ts3query.on "open", ->
|
||||||
|
log.info "TS3 query now ready."
|
||||||
|
|
||||||
|
attempts = 0
|
||||||
|
err = null
|
||||||
|
init = true
|
||||||
|
while init or err != null
|
||||||
|
init = false
|
||||||
|
if err
|
||||||
|
attempts++
|
||||||
|
if attempts == 10
|
||||||
|
log.error "Could not register to TeamSpeak3 client events, giving up!"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
log.warn "Could not register to TeamSpeak3 client events!", err
|
||||||
|
|
||||||
|
[
|
||||||
|
"notifytalkstatuschange"
|
||||||
|
"notifyconnectstatuschange"
|
||||||
|
"notifytextmessage"
|
||||||
|
"notifyclientupdated"
|
||||||
|
"notifycliententerview"
|
||||||
|
"notifyclientleftview"
|
||||||
|
"notifyclientchatclosed"
|
||||||
|
"notifyclientchatcomposing"
|
||||||
|
"notifyclientchannelgroupchanged"
|
||||||
|
"notifyclientmoved"
|
||||||
|
].every(eventName ->
|
||||||
|
await ts3query.clientnotifyregister ts3query.currentScHandlerID, eventName, defer(err)
|
||||||
|
if err
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
)
|
||||||
|
|
||||||
|
ts3query.on "message.selected", (args) ->
|
||||||
|
if args["schandlerid"]
|
||||||
|
ts3query.currentScHandlerID = parseInt args["schandlerid"]
|
||||||
|
|
||||||
|
ts3query.on "message.notifytalkstatuschange", (args) ->
|
||||||
|
await ts3query.use args.schandlerid, defer(err, data)
|
||||||
|
|
||||||
|
ts3query.on "message.notifyconnectstatuschange", (args) ->
|
||||||
|
await ts3query.use args.schandlerid, defer(err, data)
|
||||||
|
|
||||||
|
if args.status == "disconnected" and ts3clientService.state != "stopping"
|
||||||
|
log.warn "Disconnected from TeamSpeak server, reconnecting in a few seconds..."
|
||||||
|
ts3clientService.stopSync()
|
||||||
|
setTimeout (() -> ts3clientService.restartSync()), 8000
|
||||||
|
|
||||||
|
if args.status == "connecting"
|
||||||
|
log.info "Connecting to TeamSpeak server..."
|
||||||
|
|
||||||
|
if args.status == "connection_established"
|
||||||
|
log.info "Connected to TeamSpeak server."
|
||||||
|
|
||||||
|
ts3query.on "message.notifyclientupdated", (args) ->
|
||||||
|
await ts3query.use args.schandlerid, defer(err, data)
|
||||||
|
await ts3query.whoami defer(err, data)
|
||||||
|
if not err
|
||||||
|
ts3query.mydata = data
|
||||||
|
|
||||||
|
ts3query.on "message.notifytextmessage", (args) ->
|
||||||
|
await ts3query.use args.schandlerid, defer(err, data)
|
||||||
|
|
||||||
|
if not args.msg?
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = args.msg
|
||||||
|
invoker = { name: args.invokername, uid: args.invokeruid, id: args.invokerid }
|
||||||
|
targetmode = args.targetmode # 1 = private, 2 = channel
|
||||||
|
|
||||||
|
log.info "<#{invoker.name}> #{msg}"
|
||||||
|
|
||||||
|
# cheap argument parsing here
|
||||||
|
firstSpacePos = msg.indexOf " "
|
||||||
|
if firstSpacePos == 0
|
||||||
|
return
|
||||||
|
if firstSpacePos > 0
|
||||||
|
name = msg.substring 0, firstSpacePos
|
||||||
|
paramline = msg.substring firstSpacePos + 1
|
||||||
|
params = paramline.match(/'[^']*'|"[^"]*"|[^ ]+/g) || []
|
||||||
|
else
|
||||||
|
name = msg
|
||||||
|
paramline = ""
|
||||||
|
params = []
|
||||||
|
|
||||||
|
switch name.toLowerCase()
|
||||||
|
when "current"
|
||||||
|
item = vlc.playlist.items[vlc.playlist.currentItem]
|
||||||
|
if not item?
|
||||||
|
ts3query?.sendtextmessage args.targetmode, invoker.id, "Not playing anything at the moment."
|
||||||
|
return
|
||||||
|
|
||||||
|
info = vlcMediaInfo[item.mrl]
|
||||||
|
url = info?.originalUrl or item.mrl
|
||||||
|
title = info?.title or item.mrl
|
||||||
|
ts3query?.sendtextmessage args.targetmode, invoker.id, "Currently playing [URL=#{url}]#{title}[/URL]."
|
||||||
|
|
||||||
|
# Restore audio volume
|
||||||
|
vlc.audio.volume = vlcVolume
|
||||||
|
when "pause"
|
||||||
|
# now we can toggle-pause playback this easily! yay!
|
||||||
|
vlc.togglePause()
|
||||||
|
return
|
||||||
|
when "play"
|
||||||
|
inputBB = paramline.trim()
|
||||||
|
input = (removeBB paramline).trim()
|
||||||
|
|
||||||
|
# we gonna interpret play without a url as an attempt to unpause the current song
|
||||||
|
if input.length <= 0
|
||||||
|
vlc.play()
|
||||||
|
return
|
||||||
|
|
||||||
|
# only allow playback from file if it's a preconfigured alias
|
||||||
|
if isValidUrl input
|
||||||
|
log.debug "Got input URL:", input
|
||||||
|
else
|
||||||
|
input = config.get "aliases:#{input}"
|
||||||
|
if not(isValidUrl input) and not(fs.existsSync input)
|
||||||
|
log.debug "Got neither valid URL nor valid alias:", input
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, you're not allowed to play #{inputBB} via the bot."
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: permission system to check if uid is allowed to play this url or alias
|
||||||
|
|
||||||
|
vlc.playlist.clear()
|
||||||
|
|
||||||
|
# let's give youtube-dl a shot!
|
||||||
|
await youtubedl.getInfo input, ["--format=bestaudio"], defer(err, info)
|
||||||
|
if err or not info?
|
||||||
|
log.debug "There is no audio-only download for #{inputBB}, downloading full video instead."
|
||||||
|
await youtubedl.getInfo input, ["--format=best"], defer(err, info)
|
||||||
|
if err or not info?
|
||||||
|
info =
|
||||||
|
url: input
|
||||||
|
if not info.url?
|
||||||
|
info.url = input
|
||||||
|
info.title = input # URL as title
|
||||||
|
info.originalUrl = input
|
||||||
|
vlcMediaInfo[info.url] = info
|
||||||
|
|
||||||
|
# play it in VLC
|
||||||
|
vlc.play info.url
|
||||||
|
when "time", "seek", "pos", "position"
|
||||||
|
inputBB = paramline.trim()
|
||||||
|
input = (removeBB paramline).trim()
|
||||||
|
|
||||||
|
# we gonna interpret no argument as us needing to return the current position
|
||||||
|
if input.length <= 0
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Currently position is #{prettyMs vlc.input.time}."
|
||||||
|
return
|
||||||
|
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Seeking to #{prettyMs vlc.input.time}."
|
||||||
|
vlc.input.time = parseDuration input
|
||||||
|
|
||||||
|
return
|
||||||
|
when "stop-after"
|
||||||
|
vlc.playlist.mode = vlc.playlist.Single
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Playback will stop after the current playlist item."
|
||||||
|
when "loop"
|
||||||
|
inputBB = paramline
|
||||||
|
input = null
|
||||||
|
switch (removeBB paramline).toLowerCase().trim()
|
||||||
|
when ""
|
||||||
|
# just show current mode
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Playlist looping is #{if vlc.playlist.mode == vlc.playlist.Loop then "on" else "off"}."
|
||||||
|
when "on"
|
||||||
|
# enable looping
|
||||||
|
vlc.playlist.mode = vlc.playlist.Loop
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Playlist looping is now on."
|
||||||
|
when "off"
|
||||||
|
# disable looping
|
||||||
|
vlc.playlist.mode = vlc.playlist.Normal
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Playlist looping is now off."
|
||||||
|
else
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} on|off[/B] - Turns playlist looping on or off"
|
||||||
|
return
|
||||||
|
when "next"
|
||||||
|
if vlc.playlist.items.count == 0
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "The playlist is empty."
|
||||||
|
return
|
||||||
|
if vlc.playlist.mode != vlc.playlist.Loop and vlc.playlist.currentItem == vlc.playlist.items.count - 1
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Can't jump to next playlist item, this is the last one!"
|
||||||
|
return
|
||||||
|
vlc.playlist.next()
|
||||||
|
when "prev", "previous"
|
||||||
|
if vlc.playlist.items.count == 0
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "The playlist is empty."
|
||||||
|
return
|
||||||
|
if vlc.playlist.mode != vlc.playlist.Loop and vlc.playlist.currentItem <= 0
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Can't jump to previous playlist item, this is the first one!"
|
||||||
|
return
|
||||||
|
vlc.playlist.prev()
|
||||||
|
when "empty", "clear"
|
||||||
|
vlc.playlist.clear()
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Cleared the playlist."
|
||||||
|
when "enqueue", "add", "append"
|
||||||
|
inputBB = paramline.trim()
|
||||||
|
input = (removeBB paramline).trim()
|
||||||
|
|
||||||
|
if inputBB.length <= 0
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} <url>[/B] - Adds the specified URL to the current playlist"
|
||||||
|
return
|
||||||
|
|
||||||
|
# only allow playback from file if it's a preconfigured alias
|
||||||
|
if isValidUrl input
|
||||||
|
log.debug "Got input URL:", input
|
||||||
|
else
|
||||||
|
input = config.get "aliases:#{input}"
|
||||||
|
if not(isValidUrl input) and not(fs.existsSync input)
|
||||||
|
log.debug "Got neither valid URL nor valid alias:", input
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, you're not allowed to play #{inputBB} via the bot."
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: permission system to check if uid is allowed to play this url or alias
|
||||||
|
|
||||||
|
# let's give youtube-dl a shot!
|
||||||
|
await youtubedl.getInfo input, ["--format=bestaudio"], defer(err, info)
|
||||||
|
if err or not info?
|
||||||
|
log.debug "There is no audio-only download for #{inputBB}, downloading full video instead."
|
||||||
|
await youtubedl.getInfo input, ["--format=best"], defer(err, info)
|
||||||
|
if err or not info?
|
||||||
|
info =
|
||||||
|
url: input
|
||||||
|
if not info.url?
|
||||||
|
info.url = input
|
||||||
|
info.title = input # URL as title
|
||||||
|
info.originalUrl = input
|
||||||
|
vlcMediaInfo[info.url] = info
|
||||||
|
|
||||||
|
# add it in VLC
|
||||||
|
vlc.playlist.add info.url
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Added [URL=#{input}]#{info.title}[/URL] to the playlist."
|
||||||
|
|
||||||
|
# TODO: Do we need to make sure that vlc.playlist.mode is not set to "Single" here or is that handled automatically?
|
||||||
|
when "stop"
|
||||||
|
vlc.stop()
|
||||||
|
when "vol"
|
||||||
|
inputBB = paramline.trim()
|
||||||
|
input = (removeBB paramline).trim()
|
||||||
|
|
||||||
|
if inputBB.length <= 0
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Volume is currently set to #{vlcVolume}%."
|
||||||
|
return
|
||||||
|
|
||||||
|
vol = parseInt input
|
||||||
|
|
||||||
|
if paramline.trim().length <= 0 or isNaN(vol) or vol > 200 or vol < 0
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol <number>[/B] - takes a number between 0 (0%) and 200 (200%) to set the volume. 100% is 100. Defaults to 50 (50%) on startup."
|
||||||
|
return
|
||||||
|
|
||||||
|
vlc.audio.volume = vlcVolume = vol
|
||||||
|
ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set to #{vol}%."
|
||||||
|
when "changenick"
|
||||||
|
nick = paramline
|
||||||
|
Sync ->
|
||||||
|
try
|
||||||
|
ts3query.clientupdate.sync ts3query, { client_nickname: nick }
|
||||||
|
catch err
|
||||||
|
log.warn "ChangeNick failed, error information:", err
|
||||||
|
switch err.id
|
||||||
|
when 513 then ts3query.sendtextmessage args.targetmode, invoker.id, "That nickname is already in use."
|
||||||
|
when 1541 then ts3query.sendtextmessage args.targetmode, invoker.id, "That nickname is too short or too long."
|
||||||
|
else ts3query.sendtextmessage args.targetmode, invoker.id, "That unfortunately didn't work out."
|
||||||
|
|
||||||
|
await ts3clientService.start [ config.get("ts3-server") ], defer(err, ts3proc)
|
||||||
|
if err
|
||||||
|
log.error "TeamSpeak3 could not start, shutting down.", err
|
||||||
|
await shutdown defer()
|
||||||
|
process.exit 1
|
||||||
|
|
||||||
|
run()
|
381
src/app.iced
381
src/app.iced
|
@ -1,381 +0,0 @@
|
||||||
Sync = require "sync"
|
|
||||||
|
|
||||||
config = require("./config")
|
|
||||||
getLogger = require("./logger")
|
|
||||||
services = require("./services")
|
|
||||||
sync = require "sync"
|
|
||||||
request = require "request"
|
|
||||||
fs = require("fs")
|
|
||||||
path = require("path")
|
|
||||||
qs = require "querystring"
|
|
||||||
temp = require("temp").track()
|
|
||||||
youtubedl = require "youtube-dl"
|
|
||||||
isValidUrl = (require "valid-url").isWebUri
|
|
||||||
parseDuration = require "./parse_duration.iced"
|
|
||||||
prettyMs = require "pretty-ms"
|
|
||||||
|
|
||||||
log = getLogger "Main"
|
|
||||||
|
|
||||||
# http://stackoverflow.com/a/7117336
|
|
||||||
removeBB = (str) -> str.replace /\[(\w+)[^\]]*](.*?)\[\/\1]/g, "$2"
|
|
||||||
|
|
||||||
module.exports =
|
|
||||||
shutdown: (cb) =>
|
|
||||||
ts3clientService = services.find("ts3client")
|
|
||||||
if ts3clientService and ts3clientService.state == "started"
|
|
||||||
await ts3clientService.stop defer(err)
|
|
||||||
if err
|
|
||||||
cb? new Error "Could not stop TeamSpeak3"
|
|
||||||
return
|
|
||||||
|
|
||||||
log.debug "Shutting down services..."
|
|
||||||
await services.shutdown defer(err)
|
|
||||||
if err
|
|
||||||
cb? new Error "Error while shutting down rest of services."
|
|
||||||
log.debug "Services shut down."
|
|
||||||
|
|
||||||
cb?()
|
|
||||||
shutdownSync: => Sync @shutdown
|
|
||||||
|
|
||||||
# Separate our own PulseAudio from any system one by using our own custom XDG directories.
|
|
||||||
process.env.XDG_RUNTIME_DIR = temp.mkdirSync "ts3bot-xdg"
|
|
||||||
|
|
||||||
# Xvfb for isolated graphical interfaces!
|
|
||||||
xvfbService = services.find("xvfb")
|
|
||||||
await xvfbService.start defer err, vlc
|
|
||||||
if err
|
|
||||||
if not process.env.DISPLAY? or process.env.DISPLAY.trim() == ""
|
|
||||||
log.error "Xvfb could not start up and no display is available!", err
|
|
||||||
await module.exports.shutdown defer()
|
|
||||||
process.exit 1
|
|
||||||
log.warn "Xvfb could not start up - will use existing display!", err
|
|
||||||
|
|
||||||
# PulseAudio daemon
|
|
||||||
await services.find("pulseaudio").start defer err
|
|
||||||
if err
|
|
||||||
log.warn "PulseAudio could not start up, audio may not act as expected!", err
|
|
||||||
|
|
||||||
# VLC via WebChimera.js
|
|
||||||
vlcService = services.find("vlc")
|
|
||||||
await vlcService.start defer err, vlc
|
|
||||||
if err
|
|
||||||
log.warn "VLC could not start up!", err
|
|
||||||
await module.exports.shutdown defer()
|
|
||||||
process.exit 1
|
|
||||||
|
|
||||||
# This is where we keep track of the volume
|
|
||||||
vlcVolume = 50
|
|
||||||
|
|
||||||
# Cached information for tracks in playlist
|
|
||||||
vlcMediaInfo = {}
|
|
||||||
|
|
||||||
# TeamSpeak3
|
|
||||||
ts3clientService = services.find("ts3client")
|
|
||||||
|
|
||||||
ts3clientService.on "started", (ts3proc) =>
|
|
||||||
ts3query = ts3clientService.query
|
|
||||||
|
|
||||||
ts3clientService.once "stopped", () =>
|
|
||||||
ts3query = undefined
|
|
||||||
|
|
||||||
# VLC event handling
|
|
||||||
vlc.onPlaying = () =>
|
|
||||||
try
|
|
||||||
# TODO: Check why info is sometimes null, something must be wrong with the "add"/"play" commands here!
|
|
||||||
# TODO: Do not format as URL in text message if MRL points to local file
|
|
||||||
|
|
||||||
item = vlc.playlist.items[vlc.playlist.currentItem]
|
|
||||||
info = vlcMediaInfo[item.mrl]
|
|
||||||
url = info?.originalUrl or item.mrl
|
|
||||||
title = info?.title or item.mrl
|
|
||||||
ts3query?.sendtextmessage 2, 0, "Now playing [URL=#{url}]#{title}[/URL]."
|
|
||||||
|
|
||||||
# Restore audio volume
|
|
||||||
vlc.audio.volume = vlcVolume
|
|
||||||
catch e
|
|
||||||
log.warn "Error in VLC onPlaying handler", e
|
|
||||||
|
|
||||||
vlc.onPaused = () => ts3query?.sendtextmessage 2, 0, "Paused."
|
|
||||||
vlc.onForward = () => ts3query?.sendtextmessage 2, 0, "Fast-forwarding..."
|
|
||||||
vlc.onBackward = () => ts3query?.sendtextmessage 2, 0, "Rewinding..."
|
|
||||||
vlc.onEncounteredError = () => log.error "VLC has encountered an error! You will need to restart the bot.", arguments
|
|
||||||
vlc.onStopped = () => ts3query?.sendtextmessage 2, 0, "Stopped."
|
|
||||||
|
|
||||||
ts3query.currentScHandlerID = 1
|
|
||||||
ts3query.mydata = {}
|
|
||||||
|
|
||||||
ts3query.on "open", =>
|
|
||||||
log.info "TS3 query now ready."
|
|
||||||
|
|
||||||
attempts = 0
|
|
||||||
err = null
|
|
||||||
init = true
|
|
||||||
while init or err != null
|
|
||||||
init = false
|
|
||||||
if err
|
|
||||||
attempts++
|
|
||||||
if attempts == 10
|
|
||||||
log.error "Could not register to TeamSpeak3 client events, giving up!"
|
|
||||||
break
|
|
||||||
else
|
|
||||||
log.warn "Could not register to TeamSpeak3 client events!", err
|
|
||||||
for eventName in [
|
|
||||||
"notifytalkstatuschange"
|
|
||||||
"notifyconnectstatuschange"
|
|
||||||
"notifytextmessage"
|
|
||||||
"notifyclientupdated"
|
|
||||||
"notifycliententerview"
|
|
||||||
"notifyclientleftview"
|
|
||||||
"notifyclientchatclosed"
|
|
||||||
"notifyclientchatcomposing"
|
|
||||||
"notifyclientchannelgroupchanged"
|
|
||||||
"notifyclientmoved"
|
|
||||||
]
|
|
||||||
await ts3query.clientnotifyregister ts3query.currentScHandlerID, eventName, defer(err)
|
|
||||||
if err
|
|
||||||
break
|
|
||||||
|
|
||||||
ts3query.on "message.selected", (args) =>
|
|
||||||
if args["schandlerid"]
|
|
||||||
ts3query.currentScHandlerID = parseInt args["schandlerid"]
|
|
||||||
|
|
||||||
ts3query.on "message.notifytalkstatuschange", (args) =>
|
|
||||||
await ts3query.use args.schandlerid, defer(err, data)
|
|
||||||
|
|
||||||
ts3query.on "message.notifyconnectstatuschange", (args) =>
|
|
||||||
await ts3query.use args.schandlerid, defer(err, data)
|
|
||||||
|
|
||||||
if args.status == "disconnected" and ts3clientService.state != "stopping"
|
|
||||||
log.warn "Disconnected from TeamSpeak server, reconnecting in a few seconds..."
|
|
||||||
ts3clientService.stopSync()
|
|
||||||
setTimeout (() => ts3clientService.restartSync()), 8000
|
|
||||||
|
|
||||||
if args.status == "connecting"
|
|
||||||
log.info "Connecting to TeamSpeak server..."
|
|
||||||
|
|
||||||
if args.status == "connection_established"
|
|
||||||
log.info "Connected to TeamSpeak server."
|
|
||||||
|
|
||||||
ts3query.on "message.notifyclientupdated", (args) =>
|
|
||||||
await ts3query.use args.schandlerid, defer(err, data)
|
|
||||||
await ts3query.whoami defer(err, data)
|
|
||||||
if not err
|
|
||||||
ts3query.mydata = data
|
|
||||||
|
|
||||||
ts3query.on "message.notifytextmessage", (args) =>
|
|
||||||
await ts3query.use args.schandlerid, defer(err, data)
|
|
||||||
|
|
||||||
if not args.msg?
|
|
||||||
return
|
|
||||||
|
|
||||||
msg = args.msg
|
|
||||||
invoker = { name: args.invokername, uid: args.invokeruid, id: args.invokerid }
|
|
||||||
targetmode = args.targetmode # 1 = private, 2 = channel
|
|
||||||
|
|
||||||
log.info "<#{invoker.name}> #{msg}"
|
|
||||||
|
|
||||||
# cheap argument parsing here
|
|
||||||
firstSpacePos = msg.indexOf " "
|
|
||||||
if firstSpacePos == 0
|
|
||||||
return
|
|
||||||
if firstSpacePos > 0
|
|
||||||
name = msg.substring 0, firstSpacePos
|
|
||||||
paramline = msg.substring firstSpacePos + 1
|
|
||||||
params = paramline.match(/'[^']*'|"[^"]*"|[^ ]+/g) || [];
|
|
||||||
else
|
|
||||||
name = msg
|
|
||||||
paramline = ""
|
|
||||||
params = []
|
|
||||||
|
|
||||||
switch name.toLowerCase()
|
|
||||||
when "current"
|
|
||||||
item = vlc.playlist.items[vlc.playlist.currentItem]
|
|
||||||
if not item?
|
|
||||||
ts3query?.sendtextmessage args.targetmode, invoker.id, "Not playing anything at the moment."
|
|
||||||
return
|
|
||||||
|
|
||||||
info = vlcMediaInfo[item.mrl]
|
|
||||||
url = info?.originalUrl or item.mrl
|
|
||||||
title = info?.title or item.mrl
|
|
||||||
ts3query?.sendtextmessage args.targetmode, invoker.id, "Currently playing [URL=#{url}]#{title}[/URL]."
|
|
||||||
|
|
||||||
# Restore audio volume
|
|
||||||
vlc.audio.volume = vlcVolume
|
|
||||||
when "pause"
|
|
||||||
# now we can toggle-pause playback this easily! yay!
|
|
||||||
vlc.togglePause()
|
|
||||||
return
|
|
||||||
when "play"
|
|
||||||
inputBB = paramline.trim()
|
|
||||||
input = (removeBB paramline).trim()
|
|
||||||
|
|
||||||
# we gonna interpret play without a url as an attempt to unpause the current song
|
|
||||||
if input.length <= 0
|
|
||||||
vlc.play()
|
|
||||||
return
|
|
||||||
|
|
||||||
# only allow playback from file if it's a preconfigured alias
|
|
||||||
if isValidUrl input
|
|
||||||
log.debug "Got input URL:", input
|
|
||||||
else
|
|
||||||
input = config.get "aliases:#{input}"
|
|
||||||
if not(isValidUrl input) and not(fs.existsSync input)
|
|
||||||
log.debug "Got neither valid URL nor valid alias:", input
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, you're not allowed to play #{inputBB} via the bot."
|
|
||||||
return
|
|
||||||
|
|
||||||
# TODO: permission system to check if uid is allowed to play this url or alias
|
|
||||||
|
|
||||||
vlc.playlist.clear()
|
|
||||||
|
|
||||||
# let's give youtube-dl a shot!
|
|
||||||
await youtubedl.getInfo input, [
|
|
||||||
"--format=bestaudio"
|
|
||||||
], defer(err, info)
|
|
||||||
if err or not info?
|
|
||||||
log.debug "There is no audio-only download for #{inputBB}, downloading full video instead."
|
|
||||||
await youtubedl.getInfo input, [
|
|
||||||
"--format=best"
|
|
||||||
], defer(err, info)
|
|
||||||
if err or not info?
|
|
||||||
info =
|
|
||||||
url: input
|
|
||||||
if not info.url?
|
|
||||||
info.url = input
|
|
||||||
info.title = input # URL as title
|
|
||||||
info.originalUrl = input
|
|
||||||
vlcMediaInfo[info.url] = info
|
|
||||||
|
|
||||||
# play it in VLC
|
|
||||||
vlc.play info.url
|
|
||||||
when "time", "seek", "pos", "position"
|
|
||||||
inputBB = paramline.trim()
|
|
||||||
input = (removeBB paramline).trim()
|
|
||||||
|
|
||||||
# we gonna interpret no argument as us needing to return the current position
|
|
||||||
if input.length <= 0
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Currently position is #{prettyMs vlc.input.time}."
|
|
||||||
return
|
|
||||||
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Seeking to #{prettyMs vlc.input.time}."
|
|
||||||
vlc.input.time = parseDuration input
|
|
||||||
|
|
||||||
return
|
|
||||||
when "stop-after"
|
|
||||||
vlc.playlist.mode = vlc.playlist.Single
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Playback will stop after the current playlist item."
|
|
||||||
when "loop"
|
|
||||||
inputBB = paramline
|
|
||||||
input = null
|
|
||||||
switch (removeBB paramline).toLowerCase().trim()
|
|
||||||
when ""
|
|
||||||
# just show current mode
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Playlist looping is #{if vlc.playlist.mode == vlc.playlist.Loop then "on" else "off"}."
|
|
||||||
when "on"
|
|
||||||
# enable looping
|
|
||||||
vlc.playlist.mode = vlc.playlist.Loop
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Playlist looping is now on."
|
|
||||||
when "off"
|
|
||||||
# disable looping
|
|
||||||
vlc.playlist.mode = vlc.playlist.Normal
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Playlist looping is now off."
|
|
||||||
else
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} on|off[/B] - Turns playlist looping on or off"
|
|
||||||
return
|
|
||||||
when "next"
|
|
||||||
if vlc.playlist.items.count == 0
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "The playlist is empty."
|
|
||||||
return
|
|
||||||
if vlc.playlist.mode != vlc.playlist.Loop and vlc.playlist.currentItem == vlc.playlist.items.count - 1
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Can't jump to next playlist item, this is the last one!"
|
|
||||||
return
|
|
||||||
vlc.playlist.next()
|
|
||||||
when "prev", "previous"
|
|
||||||
if vlc.playlist.items.count == 0
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "The playlist is empty."
|
|
||||||
return
|
|
||||||
if vlc.playlist.mode != vlc.playlist.Loop and vlc.playlist.currentItem <= 0
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Can't jump to previous playlist item, this is the first one!"
|
|
||||||
return
|
|
||||||
vlc.playlist.prev()
|
|
||||||
when "empty", "clear"
|
|
||||||
vlc.playlist.clear()
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Cleared the playlist."
|
|
||||||
when "enqueue", "add", "append"
|
|
||||||
inputBB = paramline.trim()
|
|
||||||
input = (removeBB paramline).trim()
|
|
||||||
|
|
||||||
if inputBB.length <= 0
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} <url>[/B] - Adds the specified URL to the current playlist"
|
|
||||||
return
|
|
||||||
|
|
||||||
# only allow playback from file if it's a preconfigured alias
|
|
||||||
if isValidUrl input
|
|
||||||
log.debug "Got input URL:", input
|
|
||||||
else
|
|
||||||
input = config.get "aliases:#{input}"
|
|
||||||
if not(isValidUrl input) and not(fs.existsSync input)
|
|
||||||
log.debug "Got neither valid URL nor valid alias:", input
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, you're not allowed to play #{inputBB} via the bot."
|
|
||||||
return
|
|
||||||
|
|
||||||
# TODO: permission system to check if uid is allowed to play this url or alias
|
|
||||||
|
|
||||||
# let's give youtube-dl a shot!
|
|
||||||
await youtubedl.getInfo input, [
|
|
||||||
"--format=bestaudio"
|
|
||||||
], defer(err, info)
|
|
||||||
if err or not info?
|
|
||||||
log.debug "There is no audio-only download for #{inputBB}, downloading full video instead."
|
|
||||||
await youtubedl.getInfo input, [
|
|
||||||
"--format=best"
|
|
||||||
], defer(err, info)
|
|
||||||
if err or not info?
|
|
||||||
info =
|
|
||||||
url: input
|
|
||||||
if not info.url?
|
|
||||||
info.url = input
|
|
||||||
info.title = input # URL as title
|
|
||||||
info.originalUrl = input
|
|
||||||
vlcMediaInfo[info.url] = info
|
|
||||||
|
|
||||||
# add it in VLC
|
|
||||||
vlc.playlist.add info.url
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Added [URL=#{input}]#{info.title}[/URL] to the playlist."
|
|
||||||
|
|
||||||
# TODO: Do we need to make sure that vlc.playlist.mode is not set to "Single" here or is that handled automatically?
|
|
||||||
when "stop"
|
|
||||||
vlc.stop()
|
|
||||||
when "vol"
|
|
||||||
inputBB = paramline.trim()
|
|
||||||
input = (removeBB paramline).trim()
|
|
||||||
|
|
||||||
if inputBB.length <= 0
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Volume is currently set to #{vlcVolume}%."
|
|
||||||
return
|
|
||||||
|
|
||||||
vol = parseInt input
|
|
||||||
|
|
||||||
if paramline.trim().length <= 0 or isNaN(vol) or vol > 200 or vol < 0
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol <number>[/B] - takes a number between 0 (0%) and 200 (200%) to set the volume. 100% is 100. Defaults to 50 (50%) on startup."
|
|
||||||
return
|
|
||||||
|
|
||||||
vlc.audio.volume = vlcVolume = vol
|
|
||||||
ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set to #{vol}%."
|
|
||||||
when "changenick"
|
|
||||||
nick = paramline
|
|
||||||
Sync () =>
|
|
||||||
try
|
|
||||||
ts3query.clientupdate.sync ts3query, { client_nickname: nick }
|
|
||||||
catch err
|
|
||||||
log.warn "ChangeNick failed, error information:", err
|
|
||||||
switch err.id
|
|
||||||
when 513 then ts3query.sendtextmessage args.targetmode, invoker.id, "That nickname is already in use."
|
|
||||||
when 1541 then ts3query.sendtextmessage args.targetmode, invoker.id, "That nickname is too short or too long."
|
|
||||||
else ts3query.sendtextmessage args.targetmode, invoker.id, "That unfortunately didn't work out."
|
|
||||||
|
|
||||||
await ts3clientService.start [ config.get("ts3-server") ], defer(err, ts3proc)
|
|
||||||
if err
|
|
||||||
log.error "TeamSpeak3 could not start, shutting down.", err
|
|
||||||
await module.exports.shutdown defer()
|
|
||||||
process.exit 1
|
|
|
@ -1,7 +1,7 @@
|
||||||
nconf = require "nconf"
|
import nconf from 'nconf'
|
||||||
path = require "path"
|
import path from 'path'
|
||||||
merge = require "merge"
|
import merge from 'merge'
|
||||||
pwgen = require "password-generator"
|
import pwgen from 'password-generator'
|
||||||
|
|
||||||
console.log "Loading configuration..."
|
console.log "Loading configuration..."
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import Sync from 'sync'
|
||||||
|
import readline from 'readline'
|
||||||
|
|
||||||
|
import services from './services'
|
||||||
|
import getLogger from './logger'
|
||||||
|
import app from './app.iced'
|
||||||
|
|
||||||
|
log = getLogger('app')
|
||||||
|
|
||||||
|
# compatibility with Windows for interrupt signal
|
||||||
|
if process.platform == 'win32'
|
||||||
|
rl = readline.createInterface(
|
||||||
|
input: process.stdin
|
||||||
|
output: process.stdout)
|
||||||
|
rl.on 'SIGINT', ->
|
||||||
|
process.emit 'SIGINT'
|
||||||
|
|
||||||
|
doShutdownAsync = (cb) ->
|
||||||
|
log.info 'App shutdown starting...'
|
||||||
|
app.shutdown ->
|
||||||
|
log.info 'Services shutdown starting...'
|
||||||
|
services.shutdown ->
|
||||||
|
if cb and typeof cb == 'function'
|
||||||
|
cb()
|
||||||
|
return
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
|
process.on 'uncaughtException', (err) ->
|
||||||
|
log.error 'Shutting down due to an uncaught exception!', err
|
||||||
|
app.shutdownSync()
|
||||||
|
process.exit 0xFF
|
||||||
|
return
|
||||||
|
|
||||||
|
process.on 'exit', (e) ->
|
||||||
|
log.debug 'Triggered exit', e
|
||||||
|
app.shutdownSync()
|
||||||
|
return
|
||||||
|
|
||||||
|
process.on 'SIGTERM', (e) ->
|
||||||
|
log.debug 'Caught SIGTERM signal'
|
||||||
|
app.shutdown ->
|
||||||
|
process.exit 0
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
|
process.on 'SIGINT', ->
|
||||||
|
log.debug 'Caught SIGINT signal'
|
||||||
|
app.shutdown ->
|
||||||
|
process.exit 0
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
|
process.on 'SIGHUP', ->
|
||||||
|
log.debug 'Caught SIGHUP signal'
|
||||||
|
app.shutdown ->
|
||||||
|
process.exit 0
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
|
process.on 'SIGQUIT', ->
|
||||||
|
log.debug 'Caught SIGQUIT signal'
|
||||||
|
app.shutdown ->
|
||||||
|
process.exit 0
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
|
process.on 'SIGABRT', ->
|
||||||
|
log.debug 'Caught SIGABRT signal'
|
||||||
|
app.shutdown ->
|
||||||
|
process.exit 0
|
||||||
|
return
|
||||||
|
return
|
|
@ -1,6 +1,4 @@
|
||||||
#!/usr/bin/env node
|
require("iced-coffee-script-3/register");
|
||||||
|
|
||||||
require("iced-coffee-script/register");
|
|
||||||
Sync = require("sync");
|
Sync = require("sync");
|
||||||
|
|
||||||
var services = require("./services");
|
var services = require("./services");
|
||||||
|
@ -19,7 +17,7 @@ if (process.platform === "win32") {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app = require("./app.iced");
|
app = require("./app");
|
||||||
|
|
||||||
doShutdownAsync = function(cb) {
|
doShutdownAsync = function(cb) {
|
||||||
log.info("App shutdown starting...");
|
log.info("App shutdown starting...");
|
|
@ -1,8 +1,9 @@
|
||||||
winston = require "winston"
|
import winston from 'winston'
|
||||||
path = require "path"
|
import path from 'path'
|
||||||
config = require "./config"
|
import merge from 'merge'
|
||||||
merge = require "merge"
|
import winstonCommon from 'winston/lib/winston/common'
|
||||||
winstonCommon = require "winston/lib/winston/common"
|
|
||||||
|
import config from './config'
|
||||||
|
|
||||||
winston.emitErrs = true
|
winston.emitErrs = true
|
||||||
|
|
||||||
|
@ -40,10 +41,10 @@ container = new (winston.Container)
|
||||||
|
|
||||||
initialized_loggers = []
|
initialized_loggers = []
|
||||||
|
|
||||||
module.exports = (name, options) =>
|
module.exports = (name, options) ->
|
||||||
if not(name in initialized_loggers)
|
if not(name in initialized_loggers)
|
||||||
logger = container.add name
|
logger = container.add name
|
||||||
logger.filters.push (level, msg, meta) => "[#{name}] #{msg}"
|
logger.filters.push (level, msg, meta) -> "[#{name}] #{msg}"
|
||||||
initialized_loggers.push name
|
initialized_loggers.push name
|
||||||
return logger
|
return logger
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
parseDuration = require "parse-duration"
|
import parseDuration from 'parse-duration'
|
||||||
namedRegex = require("named-regexp").named
|
import { named as namedRegex } from 'named-regexp'
|
||||||
|
|
||||||
durationRegex = namedRegex /^(((:<h>[0-9]{0,2}):)?(:<m>[0-9]{0,2}):)?(:<s>[0-9]{0,2})(:<ms>\.[0-9]*)?$/
|
durationRegex = namedRegex /^(((:<h>[0-9]{0,2}):)?(:<m>[0-9]{0,2}):)?(:<s>[0-9]{0,2})(:<ms>\.[0-9]*)?$/
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
which = require("which").sync
|
import { sync as which } from 'which'
|
||||||
path = require "path"
|
import path from 'path'
|
||||||
log = require("./logger")("RequireBin")
|
|
||||||
|
|
||||||
module.exports = (binName, doErrorIfNotFound) =>
|
import getLogger from './logger'
|
||||||
|
|
||||||
|
log = getLogger "RequireBin"
|
||||||
|
|
||||||
|
module.exports = (binName, doErrorIfNotFound) ->
|
||||||
doErrorIfNotFound = true unless doErrorIfNotFound?
|
doErrorIfNotFound = true unless doErrorIfNotFound?
|
||||||
|
|
||||||
# check if xvfb is findable from here
|
# check if xvfb is findable from here
|
|
@ -3,4 +3,4 @@ module.exports = (a, b) ->
|
||||||
return -1; # a before b
|
return -1; # a before b
|
||||||
if b.dependencies.indexOf(a.name) >= 0
|
if b.dependencies.indexOf(a.name) >= 0
|
||||||
return 1; # a after b
|
return 1; # a after b
|
||||||
return 0; # does not matter
|
return 0 # does not matter
|
|
@ -1,11 +1,11 @@
|
||||||
Sync = require "sync"
|
import Sync from 'sync'
|
||||||
|
import { EventEmitter } from 'events'
|
||||||
|
import merge from 'merge'
|
||||||
|
|
||||||
getLogger = require "./logger"
|
import getLogger from './logger'
|
||||||
EventEmitter = require("events").EventEmitter
|
import services from './services'
|
||||||
merge = require "merge"
|
|
||||||
services = require "./services"
|
|
||||||
|
|
||||||
module.exports = class Service extends EventEmitter
|
class Service extends EventEmitter
|
||||||
constructor: (@name, funcs) ->
|
constructor: (@name, funcs) ->
|
||||||
@log = getLogger @name
|
@log = getLogger @name
|
||||||
@_funcs = funcs
|
@_funcs = funcs
|
||||||
|
@ -152,3 +152,5 @@ module.exports = class Service extends EventEmitter
|
||||||
cb? err
|
cb? err
|
||||||
|
|
||||||
restartSync: () => Sync () => @restart.sync @
|
restartSync: () => Sync () => @restart.sync @
|
||||||
|
|
||||||
|
module.exports = Service
|
|
@ -1,12 +1,16 @@
|
||||||
# At this point I feel like I'm writing my own init system. Phew...
|
# At this point I feel like I'm writing my own init system. Phew...
|
||||||
|
|
||||||
merge = require "merge"
|
import merge from 'merge'
|
||||||
getLogger = require("./logger")
|
import { EventEmitter } from 'events'
|
||||||
EventEmitter = require("events").EventEmitter
|
import Sync from 'sync'
|
||||||
log = getLogger("ServiceMgr")
|
|
||||||
Sync = require "sync"
|
|
||||||
|
|
||||||
getLegacyServiceName = (serviceName) -> serviceName.toLowerCase().replace(/[^A-z0-9]/g, "_")
|
import getLogger from './logger'
|
||||||
|
import { Service } from './service_template'
|
||||||
|
|
||||||
|
log = getLogger("ServiceMgr")
|
||||||
|
|
||||||
|
getLegacyServiceName = (serviceName) ->
|
||||||
|
serviceName.toLowerCase().replace(/[^A-z0-9]/g, "_")
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
services: []
|
services: []
|
||||||
|
@ -52,7 +56,7 @@ module.exports =
|
||||||
shutdownSync: () -> Sync () => @shutdown.sync @
|
shutdownSync: () -> Sync () => @shutdown.sync @
|
||||||
|
|
||||||
# base class for all services
|
# base class for all services
|
||||||
module.exports.Service = require "./service_template"
|
module.exports.Service = Service
|
||||||
|
|
||||||
# register services
|
# register services
|
||||||
services = [
|
services = [
|
|
@ -1,10 +1,12 @@
|
||||||
spawn = require("child_process").spawn
|
import { spawn } from 'child_process'
|
||||||
log = require("../logger")("PulseAudio")
|
import StreamSplitter from 'stream-splitter'
|
||||||
services = require("../services")
|
|
||||||
config = require("../config")
|
|
||||||
StreamSplitter = require("stream-splitter")
|
|
||||||
require_bin = require("../require_bin")
|
|
||||||
|
|
||||||
|
import getLogger from '../logger'
|
||||||
|
import services from '../services'
|
||||||
|
import config from '../config'
|
||||||
|
import require_bin from '../require_bin'
|
||||||
|
|
||||||
|
log = getLogger "PulseAudio"
|
||||||
pulseaudioPath = require_bin config.get("PULSE_BINARY")
|
pulseaudioPath = require_bin config.get("PULSE_BINARY")
|
||||||
pacmdPath = require_bin "pacmd"
|
pacmdPath = require_bin "pacmd"
|
||||||
|
|
||||||
|
@ -18,7 +20,7 @@ module.exports = class PulseAudioService extends services.Service
|
||||||
return
|
return
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
forwardLog = (token) =>
|
forwardLog = (token) ->
|
||||||
token = token.trim() # get rid of \r
|
token = token.trim() # get rid of \r
|
||||||
level = token.substring(0, 1).toUpperCase()
|
level = token.substring(0, 1).toUpperCase()
|
||||||
msg = token.substring token.indexOf("]") + 2
|
msg = token.substring token.indexOf("]") + 2
|
||||||
|
@ -41,7 +43,7 @@ module.exports = class PulseAudioService extends services.Service
|
||||||
# check if there is already a daemon running
|
# check if there is already a daemon running
|
||||||
proc = spawn pulseaudioPath, [ "--check" ], opts
|
proc = spawn pulseaudioPath, [ "--check" ], opts
|
||||||
stderrTokenizer = proc.stderr.pipe StreamSplitter "\n"
|
stderrTokenizer = proc.stderr.pipe StreamSplitter "\n"
|
||||||
stderrTokenizer.encoding = "utf8";
|
stderrTokenizer.encoding = "utf8"
|
||||||
stderrTokenizer.on "token", forwardLog
|
stderrTokenizer.on "token", forwardLog
|
||||||
await proc.once "exit", defer(code, signal)
|
await proc.once "exit", defer(code, signal)
|
||||||
@log.silly "PulseAudio daemon check returned that #{if code == 0 then "a daemon is already running" else "no daemon is running"}"
|
@log.silly "PulseAudio daemon check returned that #{if code == 0 then "a daemon is already running" else "no daemon is running"}"
|
||||||
|
@ -74,7 +76,7 @@ module.exports = class PulseAudioService extends services.Service
|
||||||
stderrTokenizer.encoding = "utf8"
|
stderrTokenizer.encoding = "utf8"
|
||||||
stderrTokenizer.on "token", tokenHandler
|
stderrTokenizer.on "token", tokenHandler
|
||||||
|
|
||||||
proc.on "exit", () =>
|
proc.on "exit", () ->
|
||||||
if not calledCallback
|
if not calledCallback
|
||||||
calledCallback = true
|
calledCallback = true
|
||||||
cb? new Error "PulseAudio daemon terminated unexpectedly."
|
cb? new Error "PulseAudio daemon terminated unexpectedly."
|
||||||
|
@ -89,11 +91,11 @@ module.exports = class PulseAudioService extends services.Service
|
||||||
|
|
||||||
cb?()
|
cb?()
|
||||||
|
|
||||||
findIndexForProcessId: (pid, cb) => throw new Error "Not implemented yet"
|
findIndexForProcessId: (pid, cb) -> throw new Error "Not implemented yet"
|
||||||
|
|
||||||
findIndexForProcessIdSync: (pid) => Sync () => @findIndexForProcessId @, pid
|
findIndexForProcessIdSync: (pid) => Sync () => @findIndexForProcessId @, pid
|
||||||
|
|
||||||
setSinkInputMute: (index, value, cb) => throw new Error "Not implemented yet"
|
setSinkInputMute: (index, value, cb) -> throw new Error "Not implemented yet"
|
||||||
|
|
||||||
setSinkInputMuteSync: (index, value) => Sync () => @setSinkInputMute @, index, value
|
setSinkInputMuteSync: (index, value) => Sync () => @setSinkInputMute @, index, value
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
xvfb = require("xvfb")
|
import xvfb from 'xvfb'
|
||||||
log = require("../logger")("TS3Client")
|
import path from 'path'
|
||||||
config = require("../config")
|
import merge from 'merge'
|
||||||
services = require("../services")
|
import fs from 'fs'
|
||||||
x11tools = require("../x11")
|
import url from 'url'
|
||||||
TS3Settings = require("../ts3settings")
|
import { spawn } from 'child_process'
|
||||||
TS3ClientQuery = require("../ts3query")
|
import StreamSplitter from 'stream-splitter'
|
||||||
path = require "path"
|
|
||||||
merge = require "merge"
|
import getLogger from '../logger'
|
||||||
fs = require "fs"
|
import config from '../config'
|
||||||
url = require "url"
|
import services from '../services'
|
||||||
spawn = require("child_process").spawn
|
import x11tools from '../x11'
|
||||||
StreamSplitter = require("stream-splitter")
|
import TS3Settings from '../ts3settings'
|
||||||
require_bin = require("../require_bin")
|
import TS3ClientQuery from '../ts3query'
|
||||||
|
import require_bin from '../require_bin'
|
||||||
|
|
||||||
|
log = getLogger "TS3Client"
|
||||||
|
|
||||||
ts3client_binpath = require_bin path.join(config.get("ts3-install-path"), "ts3client_linux_" + (if process.arch == "x64" then "amd64" else process.arch))
|
ts3client_binpath = require_bin path.join(config.get("ts3-install-path"), "ts3client_linux_" + (if process.arch == "x64" then "amd64" else process.arch))
|
||||||
|
|
||||||
|
@ -57,7 +60,7 @@ module.exports = class TS3ClientService extends services.Service
|
||||||
# spawn process
|
# spawn process
|
||||||
proc = null
|
proc = null
|
||||||
doStart = null
|
doStart = null
|
||||||
forwardLog = (token) =>
|
forwardLog = (token) ->
|
||||||
token = token.trim() # get rid of \r
|
token = token.trim() # get rid of \r
|
||||||
if token.indexOf("|") > 0
|
if token.indexOf("|") > 0
|
||||||
token = token.split("|")
|
token = token.split("|")
|
||||||
|
@ -106,11 +109,11 @@ module.exports = class TS3ClientService extends services.Service
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
stdoutTokenizer = proc.stdout.pipe StreamSplitter "\n"
|
stdoutTokenizer = proc.stdout.pipe StreamSplitter "\n"
|
||||||
stdoutTokenizer.encoding = "utf8";
|
stdoutTokenizer.encoding = "utf8"
|
||||||
stdoutTokenizer.on "token", forwardLog
|
stdoutTokenizer.on "token", forwardLog
|
||||||
|
|
||||||
stderrTokenizer = proc.stderr.pipe StreamSplitter "\n"
|
stderrTokenizer = proc.stderr.pipe StreamSplitter "\n"
|
||||||
stderrTokenizer.encoding = "utf8";
|
stderrTokenizer.encoding = "utf8"
|
||||||
stderrTokenizer.on "token", forwardLog
|
stderrTokenizer.on "token", forwardLog
|
||||||
|
|
||||||
# connect to client query plugin when it's loaded
|
# connect to client query plugin when it's loaded
|
|
@ -1,8 +1,9 @@
|
||||||
spawn = require("child_process").spawn
|
import { spawn } from 'child_process'
|
||||||
services = require("../services")
|
import wc from 'webchimera.js'
|
||||||
config = require("../config")
|
import StreamSplitter from 'stream-splitter'
|
||||||
wc = require("webchimera.js")
|
|
||||||
StreamSplitter = require("stream-splitter")
|
import services from '../services'
|
||||||
|
import config from '../config'
|
||||||
|
|
||||||
module.exports = class VLCService extends services.Service
|
module.exports = class VLCService extends services.Service
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
@ -42,4 +43,3 @@ module.exports = class VLCService extends services.Service
|
||||||
@_instance = null
|
@_instance = null
|
||||||
|
|
||||||
cb?()
|
cb?()
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
Xvfb = require("xvfb")
|
import Xvfb from 'xvfb'
|
||||||
log = require("../logger")("Xvfb")
|
import getLogger from '../logger'
|
||||||
config = require("../config")
|
import config from '../config'
|
||||||
services = require("../services")
|
import services from '../services'
|
||||||
require_bin = require("../require_bin")
|
import require_bin from '../require_bin'
|
||||||
|
|
||||||
|
log = getLogger "Xvfb"
|
||||||
xvfbPath = require_bin "Xvfb", false
|
xvfbPath = require_bin "Xvfb", false
|
||||||
|
|
||||||
module.exports = class XvfbService extends services.Service
|
module.exports = class XvfbService extends services.Service
|
|
@ -1,8 +1,11 @@
|
||||||
spawn = require("child_process").spawn
|
import { spawn } from 'child_process'
|
||||||
log = require("../logger")("XWindowManager")
|
import StreamSplitter from 'stream-splitter'
|
||||||
services = require("../services")
|
|
||||||
StreamSplitter = require("stream-splitter")
|
import getLogger from '../logger'
|
||||||
require_bin = require("../require_bin")
|
import services from '../services'
|
||||||
|
import require_bin from '../require_bin'
|
||||||
|
|
||||||
|
log = getLogger "XWindowManager"
|
||||||
|
|
||||||
xwmBinPath = require_bin "x-window-manager", false
|
xwmBinPath = require_bin "x-window-manager", false
|
||||||
|
|
||||||
|
@ -42,13 +45,13 @@ module.exports = class XWindowManagerService extends services.Service
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
stdoutTokenizer = proc.stdout.pipe StreamSplitter "\n"
|
stdoutTokenizer = proc.stdout.pipe StreamSplitter "\n"
|
||||||
stdoutTokenizer.encoding = "utf8";
|
stdoutTokenizer.encoding = "utf8"
|
||||||
stdoutTokenizer.on "token", (token) =>
|
stdoutTokenizer.on "token", (token) =>
|
||||||
token = token.trim() # get rid of \r
|
token = token.trim() # get rid of \r
|
||||||
@log.debug token
|
@log.debug token
|
||||||
|
|
||||||
stderrTokenizer = proc.stderr.pipe StreamSplitter "\n"
|
stderrTokenizer = proc.stderr.pipe StreamSplitter "\n"
|
||||||
stderrTokenizer.encoding = "utf8";
|
stderrTokenizer.encoding = "utf8"
|
||||||
stderrTokenizer.on "token", (token) =>
|
stderrTokenizer.on "token", (token) =>
|
||||||
token = token.trim() # get rid of \r
|
token = token.trim() # get rid of \r
|
||||||
@log.warn token
|
@log.warn token
|
|
@ -1,11 +1,14 @@
|
||||||
# @property "prop", { desc... }
|
# @property "prop", { desc... }
|
||||||
Function::property = (prop, desc) -> Object.defineProperty @prototype, prop, desc
|
Function::property = (prop, desc) ->
|
||||||
|
Object.defineProperty @prototype, prop, desc
|
||||||
|
|
||||||
# defineProperty "prop", { desc... }
|
# defineProperty "prop", { desc... }
|
||||||
#Object::defineProperty = (prop, desc) -> Object.defineProperty @, prop, desc
|
#Object::defineProperty = (prop, desc) -> Object.defineProperty @, prop, desc
|
||||||
|
|
||||||
# propertiesof obj
|
# propertiesof obj
|
||||||
#global.propertiesof = (obj) -> Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertyNames(obj.constructor.prototype or {}))
|
#global.propertiesof = (obj) ->
|
||||||
|
# Object.getOwnPropertyNames(obj).concat(
|
||||||
|
# Object.getOwnPropertyNames(obj.constructor.prototype or {}))
|
||||||
|
|
||||||
# descriptorof obj, name
|
# descriptorof obj, name
|
||||||
#global.descriptorof = (obj, name) -> Object.getOwnPropertyDescriptor obj, name
|
#global.descriptorof = (obj, name) -> Object.getOwnPropertyDescriptor obj, name
|
|
@ -1,16 +1,16 @@
|
||||||
require "string.prototype.startswith"
|
import 'string.prototype.startswith'
|
||||||
|
|
||||||
net = require "net"
|
import net from 'net'
|
||||||
getLogger = require "./logger"
|
import { StringDecoder } from 'string_decoder'
|
||||||
StringDecoder = require("string_decoder").StringDecoder
|
import StreamSplitter from 'stream-splitter'
|
||||||
StreamSplitter = require "stream-splitter"
|
import { EventEmitter } from 'events'
|
||||||
events = require "events"
|
import merge from 'merge'
|
||||||
EventEmitter = events.EventEmitter
|
|
||||||
merge = require "merge"
|
import getLogger from './logger'
|
||||||
|
|
||||||
parserLog = getLogger "parser"
|
parserLog = getLogger "parser"
|
||||||
|
|
||||||
escape = (value) => value.toString()\
|
escape = (value) -> value.toString()\
|
||||||
.replace(/\\/g, "\\\\")\
|
.replace(/\\/g, "\\\\")\
|
||||||
.replace(/\//g, "\\/")\
|
.replace(/\//g, "\\/")\
|
||||||
.replace(/\|/g, "\\p")\
|
.replace(/\|/g, "\\p")\
|
||||||
|
@ -19,7 +19,7 @@ escape = (value) => value.toString()\
|
||||||
.replace(/\t/g, "\\t")\
|
.replace(/\t/g, "\\t")\
|
||||||
.replace(/\ /g, "\\s")
|
.replace(/\ /g, "\\s")
|
||||||
|
|
||||||
unescape = (value) => value.toString()\
|
unescape = (value) -> value.toString()\
|
||||||
.replace(/\\s/g, " ")\
|
.replace(/\\s/g, " ")\
|
||||||
.replace(/\\t/g, "\t")\
|
.replace(/\\t/g, "\t")\
|
||||||
.replace(/\\r/g, "\r")\
|
.replace(/\\r/g, "\r")\
|
||||||
|
@ -28,7 +28,7 @@ unescape = (value) => value.toString()\
|
||||||
.replace(/\\\//g, "/")\
|
.replace(/\\\//g, "/")\
|
||||||
.replace(/\\\\/g, "\\")
|
.replace(/\\\\/g, "\\")
|
||||||
|
|
||||||
buildCmd = (name, namedArgs, posArgs) =>
|
buildCmd = (name, namedArgs, posArgs) ->
|
||||||
# TODO: Add support for collected arguments (aka lists)
|
# TODO: Add support for collected arguments (aka lists)
|
||||||
if not name
|
if not name
|
||||||
throw new Error "Need command name"
|
throw new Error "Need command name"
|
||||||
|
@ -52,7 +52,7 @@ buildCmd = (name, namedArgs, posArgs) =>
|
||||||
param += "#{escape(v)}"
|
param += "#{escape(v)}"
|
||||||
param + "\n\r"
|
param + "\n\r"
|
||||||
|
|
||||||
parseCmd = (str) =>
|
parseCmd = (str) ->
|
||||||
params = str.split " "
|
params = str.split " "
|
||||||
|
|
||||||
startIndex = 0
|
startIndex = 0
|
||||||
|
@ -92,7 +92,7 @@ parseCmd = (str) =>
|
||||||
args: collectedArgs
|
args: collectedArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
checkError = (err) =>
|
checkError = (err) ->
|
||||||
err.id = parseInt err.id
|
err.id = parseInt err.id
|
||||||
if err.id == 0
|
if err.id == 0
|
||||||
return null
|
return null
|
||||||
|
@ -150,7 +150,7 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
_sendKeepalive: (cb) =>
|
_sendKeepalive: (cb) =>
|
||||||
@_log.silly "Send: <keep-alive>"
|
@_log.silly "Send: <keep-alive>"
|
||||||
|
|
||||||
@_tcpClient.write "\n\r", "utf8", () => cb?()
|
@_tcpClient.write "\n\r", "utf8", () -> cb?()
|
||||||
|
|
||||||
_stopKeepalive: () =>
|
_stopKeepalive: () =>
|
||||||
if @_keepaliveInt?
|
if @_keepaliveInt?
|
||||||
|
@ -191,37 +191,37 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
|
|
||||||
@_log.silly "Send:", text.trim()
|
@_log.silly "Send:", text.trim()
|
||||||
|
|
||||||
@_tcpClient.write text, "utf8", () => cb?()
|
@_tcpClient.write text, "utf8", () -> cb?()
|
||||||
@_resetKeepalive()
|
@_resetKeepalive()
|
||||||
|
|
||||||
banadd: (cb) =>
|
banadd: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
banclient: (cb) =>
|
banclient: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
bandel: (cb) =>
|
bandel: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
bandelall: (cb) =>
|
bandelall: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
banlist: (cb) =>
|
banlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channeladdperm: (cb) =>
|
channeladdperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelclientaddperm: (cb) =>
|
channelclientaddperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelclientdelperm: (cb) =>
|
channelclientdelperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelclientlist: (cb) =>
|
channelclientlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelclientpermlist: (cb) =>
|
channelclientpermlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -234,8 +234,8 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
cb = cid
|
cb = cid
|
||||||
cid = null
|
cid = null
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "channelconnectinfo",
|
@send "channelconnectinfo",
|
||||||
cid: cid
|
cid: cid
|
||||||
|
|
||||||
|
@ -254,70 +254,70 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
channel_properties = {}
|
channel_properties = {}
|
||||||
channel_properties["channel_name"] = channel_name
|
channel_properties["channel_name"] = channel_name
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "channelcreate", channel_properties
|
@send "channelcreate", channel_properties
|
||||||
|
|
||||||
channeldelete: (cb) =>
|
channeldelete: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channeldelperm: (cb) =>
|
channeldelperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
###
|
###
|
||||||
Changes a channels configuration using given properties.
|
Changes a channels configuration using given properties.
|
||||||
###
|
###
|
||||||
channeledit: (cid, channel_properties, cb) =>
|
channeledit: (cid, channel_properties, cb) =>
|
||||||
@once "message.error", (args) => cb? checkError(args)
|
@once "message.error", (args) -> cb? checkError(args)
|
||||||
@send "channeledit", merge true, channel_properties,
|
@send "channeledit", merge true, channel_properties,
|
||||||
cid: cid
|
cid: cid
|
||||||
|
|
||||||
channelgroupadd: (cb) =>
|
channelgroupadd: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelgroupaddperm: (cb) =>
|
channelgroupaddperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelgroupclientlist: (cb) =>
|
channelgroupclientlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelgroupdel: (cb) =>
|
channelgroupdel: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelgroupdelperm: (cb) =>
|
channelgroupdelperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelgrouplist: (cb) =>
|
channelgrouplist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelgrouppermlist: (cb) =>
|
channelgrouppermlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channellist: (cb) =>
|
channellist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelmove: (cb) =>
|
channelmove: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelpermlist: (cb) =>
|
channelpermlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
channelvariable: (cb) =>
|
channelvariable: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
clientaddperm: (cb) =>
|
clientaddperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
clientdbdelete: (cb) =>
|
clientdbdelete: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
clientdbedit: (cb) =>
|
clientdbedit: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
clientdblist: (cb) =>
|
clientdblist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
clientdelperm: (cb) =>
|
clientdelperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -325,8 +325,8 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
###
|
###
|
||||||
clientgetdbidfromuid: (cluid, cb) =>
|
clientgetdbidfromuid: (cluid, cb) =>
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "clientgetdbidfromuid",
|
@send "clientgetdbidfromuid",
|
||||||
cluid: cluid
|
cluid: cluid
|
||||||
|
|
||||||
|
@ -335,8 +335,8 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
###
|
###
|
||||||
clientgetids: (cb) =>
|
clientgetids: (cb) =>
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "clientgetids",
|
@send "clientgetids",
|
||||||
cluid: cluid
|
cluid: cluid
|
||||||
|
|
||||||
|
@ -346,8 +346,8 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
###
|
###
|
||||||
clientgetnamefromdbid: (cldbid, cb) =>
|
clientgetnamefromdbid: (cldbid, cb) =>
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "clientgetnamefromdbid",
|
@send "clientgetnamefromdbid",
|
||||||
cldbid: cldbid
|
cldbid: cldbid
|
||||||
|
|
||||||
|
@ -357,8 +357,8 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
###
|
###
|
||||||
clientgetnamefromuid: (cluid, cb) =>
|
clientgetnamefromuid: (cluid, cb) =>
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "clientgetnamefromuid",
|
@send "clientgetnamefromuid",
|
||||||
cluid: cluid
|
cluid: cluid
|
||||||
|
|
||||||
|
@ -368,8 +368,8 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
###
|
###
|
||||||
clientgetuidfromclid: (clid, cb) =>
|
clientgetuidfromclid: (clid, cb) =>
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "notifyclientuidfromclid", (args) => merge retval, args
|
@once "notifyclientuidfromclid", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "clientgetuidfromclid",
|
@send "clientgetuidfromclid",
|
||||||
clid: clid
|
clid: clid
|
||||||
|
|
||||||
|
@ -391,7 +391,7 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
if typeof clid == "function"
|
if typeof clid == "function"
|
||||||
cb = clid
|
cb = clid
|
||||||
clid = null
|
clid = null
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "clientkick",
|
@send "clientkick",
|
||||||
reasonid: reasonid
|
reasonid: reasonid
|
||||||
reasonmsg: reasonmsg
|
reasonmsg: reasonmsg
|
||||||
|
@ -449,14 +449,14 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
cleanedModifiers.push v
|
cleanedModifiers.push v
|
||||||
|
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "clientlist", cleanedModifiers
|
@send "clientlist", cleanedModifiers
|
||||||
|
|
||||||
clientmove: (cb) =>
|
clientmove: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
clientmute: (cb) =>
|
clientmute: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -501,7 +501,7 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
notifyconnectstatuschange
|
notifyconnectstatuschange
|
||||||
###
|
###
|
||||||
clientnotifyregister: (schandlerid, event, cb) =>
|
clientnotifyregister: (schandlerid, event, cb) =>
|
||||||
@once "message.error", (args) => cb? checkError(args)
|
@once "message.error", (args) -> cb? checkError(args)
|
||||||
@send "clientnotifyregister",
|
@send "clientnotifyregister",
|
||||||
schandlerid: schandlerid
|
schandlerid: schandlerid
|
||||||
event: event
|
event: event
|
||||||
|
@ -510,7 +510,7 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
Unregisters from all previously registered client notifications.
|
Unregisters from all previously registered client notifications.
|
||||||
###
|
###
|
||||||
clientnotifyunregister: (cb) =>
|
clientnotifyunregister: (cb) =>
|
||||||
@once "message.error", (args) => cb? checkError(args)
|
@once "message.error", (args) -> cb? checkError(args)
|
||||||
@send "clientnotifyunregister"
|
@send "clientnotifyunregister"
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -518,8 +518,8 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
###
|
###
|
||||||
clientpermlist: (cldbid, cb) =>
|
clientpermlist: (cldbid, cb) =>
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "clientpermlist",
|
@send "clientpermlist",
|
||||||
cldbid: cldbid
|
cldbid: cldbid
|
||||||
|
|
||||||
|
@ -530,12 +530,12 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
if typeof msg == "function"
|
if typeof msg == "function"
|
||||||
cb = msg
|
cb = msg
|
||||||
msg = null
|
msg = null
|
||||||
@once "message.error", (args) => cb? checkError(args)
|
@once "message.error", (args) -> cb? checkError(args)
|
||||||
@send "clientpoke",
|
@send "clientpoke",
|
||||||
msg: msg
|
msg: msg
|
||||||
clid: clid
|
clid: clid
|
||||||
|
|
||||||
clientunmute: (cb) =>
|
clientunmute: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -558,7 +558,7 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
connect
|
connect
|
||||||
###
|
###
|
||||||
clientupdate: (idents, cb) =>
|
clientupdate: (idents, cb) =>
|
||||||
@once "message.error", (args) => cb? checkError(args)
|
@once "message.error", (args) -> cb? checkError(args)
|
||||||
@send "clientupdate", idents
|
@send "clientupdate", idents
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -624,20 +624,20 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
if not Array.isArray variables
|
if not Array.isArray variables
|
||||||
throw new Error "variables needs to be an array of requested client variables."
|
throw new Error "variables needs to be an array of requested client variables."
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "clientvariable", { clid: clid }, variables
|
@send "clientvariable", { clid: clid }, variables
|
||||||
|
|
||||||
complainadd: (cb) =>
|
complainadd: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
complaindel: (cb) =>
|
complaindel: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
complaindelall: (cb) =>
|
complaindelall: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
complainlist: (cb) =>
|
complainlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -645,67 +645,67 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
###
|
###
|
||||||
currentschandlerid: (cb) =>
|
currentschandlerid: (cb) =>
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "currentschandlerid"
|
@send "currentschandlerid"
|
||||||
|
|
||||||
disconnect: (cb) => close(cb)
|
disconnect: (cb) -> close(cb)
|
||||||
|
|
||||||
exam: (cb) =>
|
exam: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
ftcreatedir: (cb) =>
|
ftcreatedir: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
ftdeletefile: (cb) =>
|
ftdeletefile: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
ftgetfileinfo: (cb) =>
|
ftgetfileinfo: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
ftgetfilelist: (cb) =>
|
ftgetfilelist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
ftinitdownload: (cb) =>
|
ftinitdownload: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
ftinitupload: (cb) =>
|
ftinitupload: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
ftlist: (cb) =>
|
ftlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
ftrenamefile: (cb) =>
|
ftrenamefile: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
ftstop: (cb) =>
|
ftstop: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
hashpassword: (cb) =>
|
hashpassword: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
help: (cb) =>
|
help: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
messageadd: (cb) =>
|
messageadd: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
messagedel: (cb) =>
|
messagedel: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
messageget: (cb) =>
|
messageget: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
messagelist: (cb) =>
|
messagelist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
messageupdateflag: (cb) =>
|
messageupdateflag: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
permoverview: (cb) =>
|
permoverview: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
quit: (cb) => close(cb)
|
quit: (cb) -> close(cb)
|
||||||
|
|
||||||
###
|
###
|
||||||
Sends a text message a specified target. The type of the target is determined
|
Sends a text message a specified target. The type of the target is determined
|
||||||
|
@ -720,61 +720,61 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
cb = msg
|
cb = msg
|
||||||
msg = target
|
msg = target
|
||||||
target = null
|
target = null
|
||||||
@once "message.error", (args) => cb? checkError(args)
|
@once "message.error", (args) -> cb? checkError(args)
|
||||||
@send "sendtextmessage",
|
@send "sendtextmessage",
|
||||||
targetmode: targetmode
|
targetmode: targetmode
|
||||||
target: target
|
target: target
|
||||||
msg: msg
|
msg: msg
|
||||||
|
|
||||||
serverconnectinfo: (cb) =>
|
serverconnectinfo: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
serverconnectionhandlerlist: (cb) =>
|
serverconnectionhandlerlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergroupadd: (cb) =>
|
servergroupadd: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergroupaddclient: (cb) =>
|
servergroupaddclient: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergroupaddperm: (cb) =>
|
servergroupaddperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergroupclientlist: (cb) =>
|
servergroupclientlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergroupdel: (cb) =>
|
servergroupdel: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergroupdelclient: (cb) =>
|
servergroupdelclient: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergroupdelperm: (cb) =>
|
servergroupdelperm: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergrouplist: (cb) =>
|
servergrouplist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergrouppermlist: (cb) =>
|
servergrouppermlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servergroupsbyclientid: (cb) =>
|
servergroupsbyclientid: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
servervariable: (cb) =>
|
servervariable: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
setclientchannelgroup: (cb) =>
|
setclientchannelgroup: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
tokenadd: (cb) =>
|
tokenadd: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
tokendelete: (cb) =>
|
tokendelete: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
tokenlist: (cb) =>
|
tokenlist: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -782,7 +782,7 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
server will automatically delete the token after it has been used.
|
server will automatically delete the token after it has been used.
|
||||||
###
|
###
|
||||||
tokenuse: (token, cb) =>
|
tokenuse: (token, cb) =>
|
||||||
@once "message.error", (args) => cb? checkError(args)
|
@once "message.error", (args) -> cb? checkError(args)
|
||||||
@send "tokenuse",
|
@send "tokenuse",
|
||||||
token: token
|
token: token
|
||||||
|
|
||||||
|
@ -792,12 +792,12 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
###
|
###
|
||||||
use: (schandlerid, cb) =>
|
use: (schandlerid, cb) =>
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "message.selected", (args) => merge retval, args
|
@once "message.selected", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "use",
|
@send "use",
|
||||||
schandlerid: schandlerid
|
schandlerid: schandlerid
|
||||||
|
|
||||||
verifychannelpassword: (cb) =>
|
verifychannelpassword: (cb) ->
|
||||||
throw new Error "Not implemented yet"
|
throw new Error "Not implemented yet"
|
||||||
|
|
||||||
###
|
###
|
||||||
|
@ -805,7 +805,7 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
incorrect.
|
incorrect.
|
||||||
###
|
###
|
||||||
verifyserverpassword: (password, cb) =>
|
verifyserverpassword: (password, cb) =>
|
||||||
@once "message.error", (args) => cb? checkError(args)
|
@once "message.error", (args) -> cb? checkError(args)
|
||||||
@send "verifyserverpassword",
|
@send "verifyserverpassword",
|
||||||
password: password
|
password: password
|
||||||
|
|
||||||
|
@ -818,6 +818,6 @@ module.exports = class TS3ClientQuery extends EventEmitter
|
||||||
###
|
###
|
||||||
whoami: (cb) =>
|
whoami: (cb) =>
|
||||||
retval = { }
|
retval = { }
|
||||||
@once "vars", (args) => merge retval, args
|
@once "vars", (args) -> merge retval, args
|
||||||
@once "message.error", (args) => cb? checkError(args), retval
|
@once "message.error", (args) -> cb? checkError(args), retval
|
||||||
@send "whoami"
|
@send "whoami"
|
|
@ -1,17 +1,17 @@
|
||||||
sqlite3 = require("sqlite3") #.verbose()
|
import sqlite3, { Database as SQLite3Database } from 'sqlite3'
|
||||||
SQLite3Database = sqlite3.Database
|
import path from 'path'
|
||||||
path = require "path"
|
import mkdirp from 'mkdirp'
|
||||||
mkdirp = require "mkdirp"
|
import SimpleIni from 'simple-ini'
|
||||||
SimpleIni = require "simple-ini"
|
import fs from 'fs'
|
||||||
fs = require "fs"
|
import merge from 'merge'
|
||||||
merge = require "merge"
|
|
||||||
getLogger = require "./logger"
|
import getLogger from './logger'
|
||||||
|
|
||||||
# some properties sugar from http://bl.ocks.org/joyrexus/65cb3780a24ecd50f6df
|
# some properties sugar from http://bl.ocks.org/joyrexus/65cb3780a24ecd50f6df
|
||||||
Function::getter = (prop, get) ->
|
Function::getter = (prop, get) ->
|
||||||
Object.defineProperty @prototype, prop, {get, configurable: yes}
|
Object.defineProperty @prototype, prop, {get, configurable: yes}
|
||||||
Function::setter = (prop, set) ->
|
Function::setter = (prop, set) ->
|
||||||
Object.defineProperty @prototype, prop, {set, configurable: yes}
|
Object.defineProperty @prototype, prop, {set, configurable: yes}
|
||||||
|
|
||||||
module.exports = class SettingsFile
|
module.exports = class SettingsFile
|
||||||
db: null
|
db: null
|
||||||
|
@ -26,21 +26,28 @@ module.exports = class SettingsFile
|
||||||
catch err
|
catch err
|
||||||
throw new Error "Could not create TS3 config directory."
|
throw new Error "Could not create TS3 config directory."
|
||||||
|
|
||||||
@getter "isInitialized", -> () => fs.existsSync(path.join(@configPath, "settings.db")) and fs.existsSync(path.join(@configPath, "ts3clientui_qt.secrets.conf"))
|
@getter "isInitialized", ->
|
||||||
@getter "isReady", -> () => @db != null
|
() =>
|
||||||
|
fs.existsSync(path.join(@configPath, "settings.db")) and
|
||||||
|
fs.existsSync(path.join(@configPath, "ts3clientui_qt.secrets.conf"))
|
||||||
|
@getter "isReady", ->
|
||||||
|
() =>
|
||||||
|
@db != null
|
||||||
|
|
||||||
open: (cb) =>
|
open: (cb) =>
|
||||||
# settings database
|
# settings database
|
||||||
@db = new SQLite3Database path.join(@configPath, "settings.db")
|
@db = new SQLite3Database path.join(@configPath, "settings.db")
|
||||||
await @db.serialize defer()
|
await @db.serialize defer()
|
||||||
await @query "CREATE TABLE IF NOT EXISTS TS3Tables (key varchar NOT NULL UNIQUE,timestamp integer unsigned NOT NULL)", defer()
|
await @query "CREATE TABLE IF NOT EXISTS TS3Tables "+
|
||||||
|
"(key varchar NOT NULL UNIQUE,timestamp integer unsigned NOT NULL)",
|
||||||
|
defer()
|
||||||
|
|
||||||
# secrets file
|
# secrets file
|
||||||
@identities = []
|
@identities = []
|
||||||
@defaultIdentity = null
|
@defaultIdentity = null
|
||||||
secretsPath = path.join(@configPath, "ts3clientui_qt.secrets.conf")
|
secretsPath = path.join(@configPath, "ts3clientui_qt.secrets.conf")
|
||||||
if fs.existsSync(secretsPath)
|
if fs.existsSync(secretsPath)
|
||||||
secrets = new SimpleIni (() => fs.readFileSync(secretsPath, "utf-8")),
|
secrets = new SimpleIni (-> fs.readFileSync(secretsPath, "utf-8")),
|
||||||
quotedValues: false
|
quotedValues: false
|
||||||
for i in [1 .. secrets.Identities.size]
|
for i in [1 .. secrets.Identities.size]
|
||||||
@identities.push
|
@identities.push
|
||||||
|
@ -75,7 +82,8 @@ module.exports = class SettingsFile
|
||||||
|
|
||||||
# Generate INI content
|
# Generate INI content
|
||||||
await secrets.save defer(iniText)
|
await secrets.save defer(iniText)
|
||||||
fs.writeFileSync path.join(@configPath, "ts3clientui_qt.secrets.conf"), iniText
|
fs.writeFileSync path.join(@configPath, "ts3clientui_qt.secrets.conf"),
|
||||||
|
iniText
|
||||||
|
|
||||||
@identities = null
|
@identities = null
|
||||||
@defaultIdentity = null
|
@defaultIdentity = null
|
||||||
|
@ -98,7 +106,9 @@ module.exports = class SettingsFile
|
||||||
if not table
|
if not table
|
||||||
throw new Error "Need table"
|
throw new Error "Need table"
|
||||||
|
|
||||||
await @query "create table if not exists #{table} (timestamp integer unsigned NOT NULL, key varchar NOT NULL UNIQUE, value varchar)", defer()
|
await @query "create table if not exists #{table} "+
|
||||||
|
"(timestamp integer unsigned NOT NULL, key varchar NOT NULL UNIQUE, "+
|
||||||
|
"value varchar)", defer()
|
||||||
|
|
||||||
if not key
|
if not key
|
||||||
return
|
return
|
||||||
|
@ -112,11 +122,13 @@ module.exports = class SettingsFile
|
||||||
|
|
||||||
timestamp = Math.round (new Date).getTime() / 1000
|
timestamp = Math.round (new Date).getTime() / 1000
|
||||||
|
|
||||||
stmt = @db.prepare "insert or replace into TS3Tables (key, timestamp) values (?, ?)"
|
stmt = @db.prepare "insert or replace into TS3Tables (key, timestamp) "+
|
||||||
|
"values (?, ?)"
|
||||||
stmt.run table, timestamp
|
stmt.run table, timestamp
|
||||||
await stmt.finalize defer()
|
await stmt.finalize defer()
|
||||||
|
|
||||||
stmt = @db.prepare "insert or replace into #{table} (timestamp, key, value) values (?, ?, ?)"
|
stmt = @db.prepare "insert or replace into #{table} (timestamp, key, value) "+
|
||||||
|
"values (?, ?, ?)"
|
||||||
stmt.run timestamp, key, value
|
stmt.run timestamp, key, value
|
||||||
await stmt.finalize defer()
|
await stmt.finalize defer()
|
||||||
|
|
||||||
|
@ -141,7 +153,7 @@ module.exports = class SettingsFile
|
||||||
@log.info "Importing identity from #{identityFilePath}..."
|
@log.info "Importing identity from #{identityFilePath}..."
|
||||||
|
|
||||||
# open identity file
|
# open identity file
|
||||||
idFile = new SimpleIni (() => fs.readFileSync(identityFilePath, "utf-8")),
|
idFile = new SimpleIni (-> fs.readFileSync(identityFilePath, "utf-8")),
|
||||||
quotedValues: true
|
quotedValues: true
|
||||||
importedIdentity = {}
|
importedIdentity = {}
|
||||||
for own k, v of idFile.Identity
|
for own k, v of idFile.Identity
|
|
@ -1,16 +1,17 @@
|
||||||
Sync = require "sync"
|
import Sync from 'sync'
|
||||||
|
import { spawn } from 'child_process'
|
||||||
|
import StreamSplitter from 'stream-splitter'
|
||||||
|
|
||||||
log = require("./logger")("X11tools")
|
import getLogger from './logger'
|
||||||
spawn = require("child_process").spawn
|
import services from './services'
|
||||||
services = require("./services")
|
import require_bin from './require_bin'
|
||||||
StreamSplitter = require("stream-splitter")
|
|
||||||
require_bin = require("./require_bin")
|
|
||||||
|
|
||||||
|
log = getLogger "X11tools"
|
||||||
xdotoolBinPath = require_bin "xdotool", false
|
xdotoolBinPath = require_bin "xdotool", false
|
||||||
|
|
||||||
# Just some tools to work with the X11 windows
|
# Just some tools to work with the X11 windows
|
||||||
module.exports =
|
module.exports =
|
||||||
getWindowIdByProcessId: (pid, cb) =>
|
getWindowIdByProcessId: (pid, cb) ->
|
||||||
wid = null
|
wid = null
|
||||||
|
|
||||||
# Return null instantly if xdotool is not available
|
# Return null instantly if xdotool is not available
|
||||||
|
@ -18,21 +19,23 @@ module.exports =
|
||||||
cb? new Error "xdotool is not available"
|
cb? new Error "xdotool is not available"
|
||||||
return
|
return
|
||||||
|
|
||||||
# We provide --name due to the bug mentioned at https://github.com/jordansissel/xdotool/issues/14
|
# We provide --name due to the bug mentioned at
|
||||||
xdoproc = spawn xdotoolBinPath, [ "search", "--any", "--pid", pid, "--name", "xdosearch" ],
|
# https://github.com/jordansissel/xdotool/issues/14
|
||||||
|
xdoproc = spawn xdotoolBinPath, [
|
||||||
|
"search", "--any", "--pid", pid, "--name", "xdosearch" ],
|
||||||
env:
|
env:
|
||||||
DISPLAY: process.env.DISPLAY
|
DISPLAY: process.env.DISPLAY
|
||||||
XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR
|
XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR
|
||||||
stdoutTokenizer = xdoproc.stdout.pipe StreamSplitter "\n"
|
stdoutTokenizer = xdoproc.stdout.pipe StreamSplitter "\n"
|
||||||
stdoutTokenizer.encoding = "utf8";
|
stdoutTokenizer.encoding = "utf8"
|
||||||
stdoutTokenizer.on "token", (token) =>
|
stdoutTokenizer.on "token", (token) ->
|
||||||
token = token.trim() # get rid of \r
|
token = token.trim() # get rid of \r
|
||||||
newWid = parseInt(token)
|
newWid = parseInt(token)
|
||||||
if newWid != 0 and wid == null
|
if newWid != 0 and wid == null
|
||||||
wid = newWid
|
wid = newWid
|
||||||
stderrTokenizer = xdoproc.stderr.pipe StreamSplitter "\n"
|
stderrTokenizer = xdoproc.stderr.pipe StreamSplitter "\n"
|
||||||
stderrTokenizer.encoding = "utf8";
|
stderrTokenizer.encoding = "utf8"
|
||||||
stderrTokenizer.on "token", (token) =>
|
stderrTokenizer.on "token", (token) ->
|
||||||
token = token.trim() # get rid of \r
|
token = token.trim() # get rid of \r
|
||||||
log.warn token
|
log.warn token
|
||||||
await xdoproc.on "exit", defer(e)
|
await xdoproc.on "exit", defer(e)
|
||||||
|
@ -45,9 +48,10 @@ module.exports =
|
||||||
|
|
||||||
cb? null, parseInt(wid)
|
cb? null, parseInt(wid)
|
||||||
|
|
||||||
getWindowIdByProcessIdSync: (pid) => Sync() => @getWindowIdByProcessId.sync @, pid
|
getWindowIdByProcessIdSync: (pid) ->
|
||||||
|
Sync() -> @getWindowIdByProcessId.sync @, pid
|
||||||
|
|
||||||
sendKeys: (wid, keys, cb) =>
|
sendKeys: (wid, keys, cb) ->
|
||||||
# Do not bother trying if xdotool is not available
|
# Do not bother trying if xdotool is not available
|
||||||
if not xdotoolBinPath?
|
if not xdotoolBinPath?
|
||||||
cb? new Error "xdotool not available."
|
cb? new Error "xdotool not available."
|
||||||
|
@ -61,18 +65,24 @@ module.exports =
|
||||||
cb? new Error "Could not start a window manager."
|
cb? new Error "Could not start a window manager."
|
||||||
return
|
return
|
||||||
|
|
||||||
xdoproc = spawn xdotoolBinPath, [ "windowactivate", "--sync", wid, "key", "--clearmodifiers", "--delay", "100" ].concat(keys),
|
xdoproc = spawn xdotoolBinPath, [
|
||||||
|
"windowactivate",
|
||||||
|
"--sync",
|
||||||
|
wid,
|
||||||
|
"key",
|
||||||
|
"--clearmodifiers",
|
||||||
|
"--delay", "100" ].concat(keys),
|
||||||
env:
|
env:
|
||||||
DISPLAY: process.env.DISPLAY
|
DISPLAY: process.env.DISPLAY
|
||||||
XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR
|
XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR
|
||||||
stdoutTokenizer = xdoproc.stdout.pipe StreamSplitter "\n"
|
stdoutTokenizer = xdoproc.stdout.pipe StreamSplitter "\n"
|
||||||
stdoutTokenizer.encoding = "utf8";
|
stdoutTokenizer.encoding = "utf8"
|
||||||
stdoutTokenizer.on "token", (token) =>
|
stdoutTokenizer.on "token", (token) ->
|
||||||
token = token.trim() # get rid of \r
|
token = token.trim() # get rid of \r
|
||||||
log.debug token
|
log.debug token
|
||||||
stderrTokenizer = xdoproc.stderr.pipe StreamSplitter "\n"
|
stderrTokenizer = xdoproc.stderr.pipe StreamSplitter "\n"
|
||||||
stderrTokenizer.encoding = "utf8";
|
stderrTokenizer.encoding = "utf8"
|
||||||
stderrTokenizer.on "token", (token) =>
|
stderrTokenizer.on "token", (token) ->
|
||||||
token = token.trim() # get rid of \r
|
token = token.trim() # get rid of \r
|
||||||
log.warn token
|
log.warn token
|
||||||
await xdoproc.on "exit", defer(e)
|
await xdoproc.on "exit", defer(e)
|
1457
src/yarn.lock
1457
src/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue