From 7890e2272300fb523b7b9f8be24049ef3fba3061 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 14:19:56 +0200 Subject: [PATCH 01/71] Remove the HTTP API server and do everything directly. --- app.iced | 100 +++++++++++++++++++++++++---------------- services.iced | 1 - services/api.iced | 111 ---------------------------------------------- 3 files changed, 61 insertions(+), 151 deletions(-) delete mode 100644 services/api.iced diff --git a/app.iced b/app.iced index ec9f0a5..6f25ac5 100644 --- a/app.iced +++ b/app.iced @@ -8,6 +8,8 @@ request = require "request" fs = require("fs") path = require("path") qs = require "querystring" +youtubedl = require "youtube-dl" +isValidUrl = (require "valid-url").isWebUri log = getLogger "Main" @@ -44,6 +46,14 @@ await services.find("pulseaudio").start defer err if err log.warn "PulseAudio could not start up, audio may not act as expected!" +# VLC HTTP API +await services.find("vlc").start defer err +if err + log.warn "VLC could not start up!" + await module.exports.shutdown defer() + process.exit 1 +vlc = services.find("vlc").instance + # TeamSpeak3 ts3clientService = services.find("ts3client") @@ -113,7 +123,7 @@ ts3clientService.on "started", (ts3proc) => ts3query.on "message.notifytextmessage", (args) => await ts3query.use args.schandlerid, defer(err, data) - + msg = args.msg invoker = { name: args.invokername, uid: args.invokeruid, id: args.invokerid } targetmode = args.targetmode # 1 = private, 2 = channel @@ -135,39 +145,58 @@ ts3clientService.on "started", (ts3proc) => switch name.toLowerCase() when "play" - q = - uid: invoker.uid - input: removeBB paramline - await request "http://127.0.0.1:16444/play?#{qs.stringify q}", defer(err, response) - switch response.statusCode - when 200 then ts3query.sendtextmessage args.targetmode, invoker.id, "Now playing #{paramline}." - when 400 then ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with what you wrote. Maybe check the URL/sound name you provided?" - when 403 then ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, you're not allowed to play #{q.input} via the bot." - else - log.warn "API reported error", response.statusCode, err - ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, an error occurred. Try again later." + inputBB = paramline + input = removeBB paramline + + # 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 + + await vlc.status.empty defer(err) + if err + log.warn "Couldn't empty VLC playlist", err + ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, an error occurred. Try again later." + return + + # 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 + + await vlc.status.play info.url, defer(err) + if err + vlc.status.empty() + log.warn "VLC API returned an error when trying to play", err + ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with that media. Maybe check the URL/sound name you provided?" + return + + ts3query.sendtextmessage args.targetmode, invoker.id, "Now playing [URL=#{input}]#{info.title}[/URL]." when "stop" - q = - uid: invoker.uid - await request "http://127.0.0.1:16444/stop?#{qs.stringify q}", defer(err, response) - switch response.statusCode - when 200 then ts3query.sendtextmessage args.targetmode, invoker.id, "Stopped playback." - when 403 then ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, you're not allowed to do that." - else - log.warn "API reported error", response.statusCode, err - ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, an error occurred. Try again later." + vlc.status.stop() + vlc.status.empty() + + ts3query.sendtextmessage args.targetmode, invoker.id, "Stopped playback." when "setvolume" - q = - uid: invoker.uid - volume: parseFloat paramline - await request "http://127.0.0.1:16444/setvolume?#{qs.stringify q}", defer(err, response) - switch response.statusCode - when 200 then ts3query.sendtextmessage args.targetmode, invoker.id, "Set volume to #{q.volume}" - when 400 then ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with what you wrote. Maybe check the volume? It's supposed to be a floating-point number between 0 and 2." - when 403 then ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, you're not allowed to do that." - else - log.warn "API reported error", response.statusCode, err - ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, an error occurred. Try again later." + ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, that's not implemented yet." when "changenick" nick = if paramline.length > params[0].length then paramline else params[0] if nick.length < 1 or nick.length > 32 @@ -185,10 +214,3 @@ if err log.error "TeamSpeak3 could not start, shutting down." await module.exports.shutdown defer() process.exit 1 - -# HTTP API -await services.find("api").start defer err -if err - log.error "API could not start up, shutting down!" - await module.exports.shutdown defer() - process.exit 1 \ No newline at end of file diff --git a/services.iced b/services.iced index 917021b..2d6168a 100644 --- a/services.iced +++ b/services.iced @@ -56,7 +56,6 @@ module.exports.Service = require "./service_template" # register services services = [ - new(require "./services/api") new(require "./services/pulseaudio") new(require "./services/ts3client") new(require "./services/vlc") diff --git a/services/api.iced b/services/api.iced deleted file mode 100644 index 3d18085..0000000 --- a/services/api.iced +++ /dev/null @@ -1,111 +0,0 @@ -express = require "express" -url = require "url" -path = require "path" -spawn = require("child_process").spawn -net = require "net" -Socket = net.Socket -getLogger = require "../logger" -config = require "../config" -log = getLogger "API" -youtubedl = require "youtube-dl" -#PulseAudio = require "pulseaudio" -isValidUrl = (require "valid-url").isWebUri - -services = require "../services" - -module.exports = class APIService extends services.Service - dependencies: [ - "pulseaudio" - "vlc" - "ts3client" - ] - constructor: () -> super "API", - start: (cb) -> - if @httpServer - cb? null - return - - vlc = services.find("vlc").instance - ts3query = services.find("ts3client").query - - # set up HTTP server - log.debug "Starting up HTTP API..." - app = express() - app.get "/play", (req, res) => - if not req.query.uid - log.debug "Didn't get a UID, sending forbidden" - res.status(400).send("Forbidden") - return - if not req.query.input - log.debug "Didn't get an input URI/alias, sending bad request" - res.status(400).send("Bad request") - return - - input = null - # only allow playback from file if it's a preconfigured alias - if isValidUrl req.query.input - log.debug "Got input URL:", req.query.input - input = req.query.input - else - input = config.get("aliases:#{req.query.input}") - if not(isValidUrl input) and not(fs.existsSync input) - log.debug "Got neither valid URL nor valid alias:", req.query.input - res.status(403).send("Forbidden") - return - - # TODO: permission system to check if uid is allowed to play this url or alias - - await vlc.status.empty defer(err) - if err - res.status(503).send("Something went wrong") - log.warn "VLC API returned an error when trying to empty", err - return - - # let's give youtube-dl a shot! - await youtubedl.getInfo input, [ - "--format=bestaudio" - ], defer(err, info) - if err or not info? - 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 - - await vlc.status.play info.url, defer(err) - if err - vlc.status.empty() - res.status(503).send("Something went wrong") - log.warn "VLC API returned an error when trying to play", err - return - - res.send JSON.stringify info - - app.get "/stop", (req, res) => - if not req.query.uid - log.debug "Didn't get a UID, sending forbidden" - res.status(403).send("Forbidden - missing UID") - return - - # TODO: permission system to check if uid is allowed to stop playback - - vlc.status.stop() - vlc.status.empty() - - res.send("OK") - - app.get "/setvolume", (req, res) => - throw new "Not implemented yet" # FIXME below, still need to implement audio - - @httpServer = app.listen 16444 - - cb? null - - stop: (cb) -> - @httpServer.close() - - cb?() \ No newline at end of file From 4bfc0cabf49f28b95e17907c0c3423c7a15fe4ab Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 15:10:28 +0200 Subject: [PATCH 02/71] Wait for VLC to stop playback on demand. --- app.iced | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app.iced b/app.iced index 6f25ac5..9f44b77 100644 --- a/app.iced +++ b/app.iced @@ -191,7 +191,8 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "Now playing [URL=#{input}]#{info.title}[/URL]." when "stop" - vlc.status.stop() + await vlc.status.stop defer(err) + vlc.status.empty() ts3query.sendtextmessage args.targetmode, invoker.id, "Stopped playback." From e0379895d77c17a7eac1bd36b7b819f34b102970 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 15:13:53 +0200 Subject: [PATCH 03/71] Implemented "vol" command to set volume. By default it's 50% (127). The range is as-is in VLC, something between 0 (0%) and 511 (200%). You get this info as well if you just type "vol". --- app.iced | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app.iced b/app.iced index 9f44b77..a349f8f 100644 --- a/app.iced +++ b/app.iced @@ -53,6 +53,7 @@ if err await module.exports.shutdown defer() process.exit 1 vlc = services.find("vlc").instance +vlc.status.volume 127 # that's 50% (about half of 0xFF) # TeamSpeak3 ts3clientService = services.find("ts3client") @@ -196,8 +197,21 @@ ts3clientService.on "started", (ts3proc) => vlc.status.empty() ts3query.sendtextmessage args.targetmode, invoker.id, "Stopped playback." - when "setvolume" - ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, that's not implemented yet." + when "vol" + vol = parseInt paramline + + if paramline.trim().length <= 0 or vol > 511 or vol < 0 + ts3query.sendtextmessage args.targetmode, invoker.id, "The [b]vol[/b] command takes a number between 0 (0%) and 511 (200%) to set the volume. 100% is 127." + return + + await vlc.status.volume paramline, defer(err) + + if err + log.warn "Failed to set volume", err + ts3query.sendtextmessage args.targetmode, invoker.id, "That unfortunately didn't work out." + return + + ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set." when "changenick" nick = if paramline.length > params[0].length then paramline else params[0] if nick.length < 1 or nick.length > 32 From f368221b3b5a3864212561361e5d3f3018a9b699 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 15:37:02 +0200 Subject: [PATCH 04/71] Implement "enqueue", "next" and a URL-less "play" to resume from paused tracks. --- app.iced | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/app.iced b/app.iced index a349f8f..3818f98 100644 --- a/app.iced +++ b/app.iced @@ -146,8 +146,14 @@ ts3clientService.on "started", (ts3proc) => switch name.toLowerCase() when "play" - inputBB = paramline - input = removeBB paramline + inputBB = paramline.trim() + + # we gonna interpret play without a url as an attempt to unpause the current song + if inputBB.length <= 0 + vlc.status.resume() + return + + input = removeBB inputBB # only allow playback from file if it's a preconfigured alias if isValidUrl input @@ -190,6 +196,55 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with that media. Maybe check the URL/sound name you provided?" return + ts3query.sendtextmessage args.targetmode, invoker.id, "Now playing [URL=#{input}]#{info.title}[/URL]." + when "next" + await vlc.status.next defer(err) + if err + vlc.status.empty() + log.warn "VLC API returned an error when trying to skip current song", err + ts3query.sendtextmessage args.targetmode, invoker.id, "This unfortunately didn't work out, I'm sorry." + return + + ts3query.sendtextmessage args.targetmode, invoker.id, "Going to the next playlist entry." + when "enqueue" + inputBB = paramline + input = removeBB paramline + + # 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 + + await vlc.status.enqueue info.url, defer(err) + if err + vlc.status.empty() + log.warn "VLC API returned an error when trying to play", err + ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with that media. Maybe check the URL/sound name you provided?" + return + ts3query.sendtextmessage args.targetmode, invoker.id, "Now playing [URL=#{input}]#{info.title}[/URL]." when "stop" await vlc.status.stop defer(err) From 69b6deb9be2c1ca9f390059630c366d37a05e1d4 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 15:37:58 +0200 Subject: [PATCH 05/71] Implement "pause" command to pause current track. --- app.iced | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app.iced b/app.iced index 3818f98..5ec734f 100644 --- a/app.iced +++ b/app.iced @@ -145,6 +145,9 @@ ts3clientService.on "started", (ts3proc) => params = [] switch name.toLowerCase() + when "pause" + vlc.status.pause() + return when "play" inputBB = paramline.trim() From 8964db5afa6445ed375b75af66cedd3690e480b7 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 16:31:53 +0200 Subject: [PATCH 06/71] Command aliases for "add" and "append" -> "enqueue". --- app.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index 5ec734f..c865c3a 100644 --- a/app.iced +++ b/app.iced @@ -209,7 +209,7 @@ ts3clientService.on "started", (ts3proc) => return ts3query.sendtextmessage args.targetmode, invoker.id, "Going to the next playlist entry." - when "enqueue" + when "enqueue", "add", "append" inputBB = paramline input = removeBB paramline From e4f3e4f2c2cfa23697197be5db5afdca8c8f4bc8 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 16:32:10 +0200 Subject: [PATCH 07/71] Prevent running the "enqueue" command without a URL. --- app.iced | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app.iced b/app.iced index c865c3a..9a8a5d8 100644 --- a/app.iced +++ b/app.iced @@ -211,6 +211,11 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "Going to the next playlist entry." when "enqueue", "add", "append" inputBB = paramline + + if inputBB.length <= 0 + ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name}[/B] takes a URL as a parameter that should be appended to the playlist." + return + input = removeBB paramline # only allow playback from file if it's a preconfigured alias From 2dd16830ecce9b7b1c3b4cb7dd2be7d38abe7d7c Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 16:32:31 +0200 Subject: [PATCH 08/71] Fix default volume. --- app.iced | 1 - services/vlc.iced | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index 9a8a5d8..99d4f4f 100644 --- a/app.iced +++ b/app.iced @@ -53,7 +53,6 @@ if err await module.exports.shutdown defer() process.exit 1 vlc = services.find("vlc").instance -vlc.status.volume 127 # that's 50% (about half of 0xFF) # TeamSpeak3 ts3clientService = services.find("ts3client") diff --git a/services/vlc.iced b/services/vlc.iced index 7a3757c..d0be6c4 100644 --- a/services/vlc.iced +++ b/services/vlc.iced @@ -32,6 +32,7 @@ module.exports = class VLCService extends services.Service "--http-port", config.get("vlc-port"), "--http-password", config.get("vlc-password") "--aout", "pulse", + "--volume", "128", # 50% volume "--no-video" ], stdio: ['ignore', 'pipe', 'pipe'] From 1b57bd36ce7c65874b85485c582b131d605a1fa2 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 16:32:58 +0200 Subject: [PATCH 09/71] Fix response messages for volume and adding to playlist. --- app.iced | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.iced b/app.iced index 99d4f4f..587fbef 100644 --- a/app.iced +++ b/app.iced @@ -252,7 +252,7 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with that media. Maybe check the URL/sound name you provided?" return - ts3query.sendtextmessage args.targetmode, invoker.id, "Now playing [URL=#{input}]#{info.title}[/URL]." + ts3query.sendtextmessage args.targetmode, invoker.id, "Added [URL=#{input}]#{info.title}[/URL] to the playlist." when "stop" await vlc.status.stop defer(err) @@ -263,7 +263,7 @@ ts3clientService.on "started", (ts3proc) => vol = parseInt paramline if paramline.trim().length <= 0 or vol > 511 or vol < 0 - ts3query.sendtextmessage args.targetmode, invoker.id, "The [b]vol[/b] command takes a number between 0 (0%) and 511 (200%) to set the volume. 100% is 127." + ts3query.sendtextmessage args.targetmode, invoker.id, "The [b]vol[/b] command takes a number between 0 (0%) and 1024 (400%) to set the volume. 100% is 256. Defaults to 128 (50%) on startup." return await vlc.status.volume paramline, defer(err) From a2c4406f09724c7d43ddfbdd93e1e8b67201dfc4 Mon Sep 17 00:00:00 2001 From: avail Date: Wed, 21 Oct 2015 16:50:36 +0200 Subject: [PATCH 10/71] string iprovements --- app.iced | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app.iced b/app.iced index 587fbef..8da20c4 100644 --- a/app.iced +++ b/app.iced @@ -195,7 +195,7 @@ ts3clientService.on "started", (ts3proc) => if err vlc.status.empty() log.warn "VLC API returned an error when trying to play", err - ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with that media. Maybe check the URL/sound name you provided?" + ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with the specified media. Try checking the URL/path you provided?" return ts3query.sendtextmessage args.targetmode, invoker.id, "Now playing [URL=#{input}]#{info.title}[/URL]." @@ -204,15 +204,15 @@ ts3clientService.on "started", (ts3proc) => if err vlc.status.empty() log.warn "VLC API returned an error when trying to skip current song", err - ts3query.sendtextmessage args.targetmode, invoker.id, "This unfortunately didn't work out, I'm sorry." + ts3query.sendtextmessage args.targetmode, invoker.id, "This didn't work. Does the playlist have multiple songs?" return - ts3query.sendtextmessage args.targetmode, invoker.id, "Going to the next playlist entry." + ts3query.sendtextmessage args.targetmode, invoker.id, "Playing next song in the playlist." when "enqueue", "add", "append" inputBB = paramline if inputBB.length <= 0 - ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name}[/B] takes a URL as a parameter that should be appended to the playlist." + ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} *url*[/B] - adds the specified URL to the current playlist" return input = removeBB paramline @@ -249,7 +249,7 @@ ts3clientService.on "started", (ts3proc) => if err vlc.status.empty() log.warn "VLC API returned an error when trying to play", err - ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with that media. Maybe check the URL/sound name you provided?" + ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with the specified media. Try checking the URL/path you provided?" return ts3query.sendtextmessage args.targetmode, invoker.id, "Added [URL=#{input}]#{info.title}[/URL] to the playlist." @@ -263,7 +263,7 @@ ts3clientService.on "started", (ts3proc) => vol = parseInt paramline if paramline.trim().length <= 0 or vol > 511 or vol < 0 - ts3query.sendtextmessage args.targetmode, invoker.id, "The [b]vol[/b] command takes a number between 0 (0%) and 1024 (400%) to set the volume. 100% is 256. Defaults to 128 (50%) on startup." + ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol *number*[/B] - takes a number between 0 (0%) and 1024 (400%) to set the volume. 100% is 256. Defaults to 128 (50%) on startup." return await vlc.status.volume paramline, defer(err) From 1846e96806fe59fdaa3a4d02d46adaee74c4ebb8 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 16:56:16 +0200 Subject: [PATCH 11/71] Use <> instead of ** for marking parameters in the text. --- app.iced | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.iced b/app.iced index 8da20c4..2970b0c 100644 --- a/app.iced +++ b/app.iced @@ -212,7 +212,7 @@ ts3clientService.on "started", (ts3proc) => inputBB = paramline if inputBB.length <= 0 - ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} *url*[/B] - adds the specified URL to the current playlist" + ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} [/B] - Adds the specified URL to the current playlist" return input = removeBB paramline @@ -263,7 +263,7 @@ ts3clientService.on "started", (ts3proc) => vol = parseInt paramline if paramline.trim().length <= 0 or vol > 511 or vol < 0 - ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol *number*[/B] - takes a number between 0 (0%) and 1024 (400%) to set the volume. 100% is 256. Defaults to 128 (50%) on startup." + ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol [/B] - takes a number between 0 (0%) and 1024 (400%) to set the volume. 100% is 256. Defaults to 128 (50%) on startup." return await vlc.status.volume paramline, defer(err) From 6568e1e5ebddd79fda199602d41f18beb84faf78 Mon Sep 17 00:00:00 2001 From: avail Date: Wed, 21 Oct 2015 19:38:13 +0200 Subject: [PATCH 12/71] fix nickname letter limit error message --- app.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index 2970b0c..fc5f057 100644 --- a/app.iced +++ b/app.iced @@ -276,7 +276,7 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set." when "changenick" nick = if paramline.length > params[0].length then paramline else params[0] - if nick.length < 1 or nick.length > 32 + if nick.length < 3 or nick.length > 30 ts3query.sendtextmessage args.targetmode, invoker.id, "Invalid nickname." return Sync () => From 0de22eb9e1629a758314b4aafb0f2259a4ecb8b9 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 20:58:36 +0200 Subject: [PATCH 13/71] Trim parameters so whitespace doesn't invalidate URLs. --- app.iced | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app.iced b/app.iced index fc5f057..02782f8 100644 --- a/app.iced +++ b/app.iced @@ -149,14 +149,13 @@ ts3clientService.on "started", (ts3proc) => 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 inputBB.length <= 0 + if input.length <= 0 vlc.status.resume() return - input = removeBB inputBB - # only allow playback from file if it's a preconfigured alias if isValidUrl input log.debug "Got input URL:", input @@ -209,14 +208,13 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "Playing next song in the playlist." when "enqueue", "add", "append" - inputBB = paramline + inputBB = paramline.trim() + input = (removeBB paramline).trim() if inputBB.length <= 0 ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} [/B] - Adds the specified URL to the current playlist" return - input = removeBB paramline - # only allow playback from file if it's a preconfigured alias if isValidUrl input log.debug "Got input URL:", input From 6695fbd83ee07677e6ef920d7687de6a24011d90 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 21 Oct 2015 20:59:25 +0200 Subject: [PATCH 14/71] Log updates on identities. --- ts3settings.iced | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ts3settings.iced b/ts3settings.iced index c673aac..1364464 100644 --- a/ts3settings.iced +++ b/ts3settings.iced @@ -211,8 +211,10 @@ module.exports = class SettingsFile select: () -> settingsObj.defaultIdentity = @id update: () -> + @log.silly "Requested update of #{id.id}" for own index, identity of settingsObj.identities if identity.id == id.id + @log.silly "Updating identity #{id.id}" settingsObj.identities[index] = merge identity, id return remove: () -> From 1892b56e4cffc583e7faa1ca7fe7cf2534b30c1b Mon Sep 17 00:00:00 2001 From: icedream Date: Sat, 24 Oct 2015 10:26:01 +0200 Subject: [PATCH 15/71] @log -> settingsObj. --- ts3settings.iced | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts3settings.iced b/ts3settings.iced index 1364464..f9d2472 100644 --- a/ts3settings.iced +++ b/ts3settings.iced @@ -211,10 +211,10 @@ module.exports = class SettingsFile select: () -> settingsObj.defaultIdentity = @id update: () -> - @log.silly "Requested update of #{id.id}" + settingsObj.silly "Requested update of #{id.id}" for own index, identity of settingsObj.identities if identity.id == id.id - @log.silly "Updating identity #{id.id}" + settingsObj.silly "Updating identity #{id.id}" settingsObj.identities[index] = merge identity, id return remove: () -> From ed10e875b3bf07f1ddfc3cca2d324fb64ec6d9ed Mon Sep 17 00:00:00 2001 From: icedream Date: Sat, 24 Oct 2015 11:28:44 +0200 Subject: [PATCH 16/71] I'm on a roll with this today. --- ts3settings.iced | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts3settings.iced b/ts3settings.iced index f9d2472..81f9e3b 100644 --- a/ts3settings.iced +++ b/ts3settings.iced @@ -211,10 +211,10 @@ module.exports = class SettingsFile select: () -> settingsObj.defaultIdentity = @id update: () -> - settingsObj.silly "Requested update of #{id.id}" + settingsObj.log.silly "Requested update of #{id.id}" for own index, identity of settingsObj.identities if identity.id == id.id - settingsObj.silly "Updating identity #{id.id}" + settingsObj.log.silly "Updating identity #{id.id}" settingsObj.identities[index] = merge identity, id return remove: () -> From ac3531a8753599c1fd2e712807df29fa187ff213 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 00:13:10 +0100 Subject: [PATCH 17/71] Add webchimera.js to dependencies - we're now switching! See issue #17. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index bc41172..6e5a85e 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "sync": "^0.2.5", "valid-url": "^1.0.9", "vlc-api": "0.0.0", + "webchimera.js": "^0.1.38", "which": "^1.1.2", "winston": "^1.0.1", "xvfb": "git://github.com/icedream/node-xvfb.git", From 394b16ab5eee9780d71961f6c3a0674f5ca69861 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 01:14:55 +0100 Subject: [PATCH 18/71] We're now completely getting rid of vlc-api. - Hopefully fixes audio volume not being set to 50% at startup. - Volume now is in the range of 0 to 200 (percentage, so goes from 0% to 200% for those numbers). - Made message displaying volume change display the actual level set. - We're no longer providing an HTTP interface. - Now stores metadata info in a separate variable for later retrieval by input MRL. - Now prints when player reaches end of playlist. - Now supports toggle-pausing using "pause" command. - Should fix playlist not being able to kick off on "play" if stopped. - Removed vlc-api dependency. Relates to issues #17, #10 and #7 (partially). --- app.iced | 85 +++++++++++++++++++++-------------------------- package.json | 1 - services/vlc.iced | 83 +++++++++++---------------------------------- 3 files changed, 56 insertions(+), 113 deletions(-) diff --git a/app.iced b/app.iced index 02782f8..7c48de4 100644 --- a/app.iced +++ b/app.iced @@ -46,13 +46,16 @@ await services.find("pulseaudio").start defer err if err log.warn "PulseAudio could not start up, audio may not act as expected!" -# VLC HTTP API -await services.find("vlc").start defer err +# VLC via WebChimera.js +vlcService = services.find("vlc") +await vlcService.start defer err, vlc if err log.warn "VLC could not start up!" await module.exports.shutdown defer() process.exit 1 -vlc = services.find("vlc").instance + +# Cached information for tracks in playlist +vlcMediaInfo = {} # TeamSpeak3 ts3clientService = services.find("ts3client") @@ -60,6 +63,17 @@ ts3clientService = services.find("ts3client") ts3clientService.on "started", (ts3proc) => ts3query = ts3clientService.query + # VLC event handling + vlc.onPlaying () => + info = vlcMediaInfo[vlc.playlist.items[vlc.playlist.currentItem].mrl] + ts3query.sendtextmessage 2, 0, "Now playing [URL=#{info.originalUrl}]#{info.title}[/URL]." + 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.onEndReached () => ts3query.sendtextmessage 2, 0, "End of playlist reached." + vlc.onStopped () => ts3query.sendtextmessage 2, 0, "Stopped." + ts3query.currentScHandlerID = 1 ts3query.mydata = {} @@ -145,7 +159,8 @@ ts3clientService.on "started", (ts3proc) => switch name.toLowerCase() when "pause" - vlc.status.pause() + # now we can toggle-pause playback this easily! yay! + vlc.togglePause() return when "play" inputBB = paramline.trim() @@ -153,7 +168,7 @@ ts3clientService.on "started", (ts3proc) => # we gonna interpret play without a url as an attempt to unpause the current song if input.length <= 0 - vlc.status.resume() + vlc.play() return # only allow playback from file if it's a preconfigured alias @@ -168,11 +183,7 @@ ts3clientService.on "started", (ts3proc) => # TODO: permission system to check if uid is allowed to play this url or alias - await vlc.status.empty defer(err) - if err - log.warn "Couldn't empty VLC playlist", err - ts3query.sendtextmessage args.targetmode, invoker.id, "Sorry, an error occurred. Try again later." - return + vlc.playlist.clear() # let's give youtube-dl a shot! await youtubedl.getInfo input, [ @@ -189,24 +200,13 @@ ts3clientService.on "started", (ts3proc) => if not info.url? info.url = input info.title = input # URL as title + info.originalUrl = input + vlcMediaInfo[info.url] = info - await vlc.status.play info.url, defer(err) - if err - vlc.status.empty() - log.warn "VLC API returned an error when trying to play", err - ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with the specified media. Try checking the URL/path you provided?" - return - - ts3query.sendtextmessage args.targetmode, invoker.id, "Now playing [URL=#{input}]#{info.title}[/URL]." + # play it in VLC + vlc.play info.url when "next" - await vlc.status.next defer(err) - if err - vlc.status.empty() - log.warn "VLC API returned an error when trying to skip current song", err - ts3query.sendtextmessage args.targetmode, invoker.id, "This didn't work. Does the playlist have multiple songs?" - return - - ts3query.sendtextmessage args.targetmode, invoker.id, "Playing next song in the playlist." + vlc.playlist.next() when "enqueue", "add", "append" inputBB = paramline.trim() input = (removeBB paramline).trim() @@ -242,36 +242,25 @@ ts3clientService.on "started", (ts3proc) => if not info.url? info.url = input info.title = input # URL as title + info.originalUrl = input + vlcMediaInfo[info.url] = info - await vlc.status.enqueue info.url, defer(err) - if err - vlc.status.empty() - log.warn "VLC API returned an error when trying to play", err - ts3query.sendtextmessage args.targetmode, invoker.id, "Something seems to be wrong with the specified media. Try checking the URL/path you provided?" - return - + # 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" - await vlc.status.stop defer(err) - - vlc.status.empty() - - ts3query.sendtextmessage args.targetmode, invoker.id, "Stopped playback." + vlc.stop() when "vol" vol = parseInt paramline - if paramline.trim().length <= 0 or vol > 511 or vol < 0 - ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol [/B] - takes a number between 0 (0%) and 1024 (400%) to set the volume. 100% is 256. Defaults to 128 (50%) on startup." + if paramline.trim().length <= 0 or vol > 200 or vol < 0 + ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol [/B] - takes a number between 0 (0%) and 200 (200%) to set the volume. 100% is 100. Defaults to 50 (50%) on startup." return - await vlc.status.volume paramline, defer(err) - - if err - log.warn "Failed to set volume", err - ts3query.sendtextmessage args.targetmode, invoker.id, "That unfortunately didn't work out." - return - - ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set." + vlc.audio.volume = vol + ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set to #{vol}%." when "changenick" nick = if paramline.length > params[0].length then paramline else params[0] if nick.length < 3 or nick.length > 30 diff --git a/package.json b/package.json index 6e5a85e..78ac299 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "string.prototype.startswith": "^0.2.0", "sync": "^0.2.5", "valid-url": "^1.0.9", - "vlc-api": "0.0.0", "webchimera.js": "^0.1.38", "which": "^1.1.2", "winston": "^1.0.1", diff --git a/services/vlc.iced b/services/vlc.iced index d0be6c4..b52ee83 100644 --- a/services/vlc.iced +++ b/services/vlc.iced @@ -1,88 +1,43 @@ spawn = require("child_process").spawn services = require("../services") config = require("../config") -VLCApi = require("vlc-api") +wc = require("webchimera.js") StreamSplitter = require("stream-splitter") -require_bin = require("../require_bin") - -vlcBinPath = require_bin "vlc" module.exports = class VLCService extends services.Service dependencies: [ "pulseaudio" ] constructor: -> super "VLC", + ### + # Starts an instance of VLC and keeps it ready for service. + ### start: (cb) -> - if @_process - cb? null, @_process + if @_instance + cb? null, @_instance return calledCallback = false - proc = null - doStart = null - doStart = () => - await services.find("pulseaudio").start defer(err) - if err - throw new Error "Dependency pulseaudio failed." + instance = wc.createPlayer [ + "--aout", "pulse", + "--no-video" + ] + instance.audio.volume = 50 - proc = spawn vlcBinPath, [ - "-I", "http", - "--http-host", config.get("vlc-host"), - "--http-port", config.get("vlc-port"), - "--http-password", config.get("vlc-password") - "--aout", "pulse", - "--volume", "128", # 50% volume - "--no-video" - ], - stdio: ['ignore', 'pipe', 'pipe'] - detached: true - - # logging - stdoutTokenizer = proc.stdout.pipe StreamSplitter "\n" - stdoutTokenizer.encoding = "utf8"; - stdoutTokenizer.on "token", (token) => - token = token.trim() # get rid of \r - @log.debug token - - stderrTokenizer = proc.stderr.pipe StreamSplitter "\n" - stderrTokenizer.encoding = "utf8"; - stderrTokenizer.on "token", (token) => - token = token.trim() # get rid of \r - @log.debug token - - proc.on "exit", () => - if @state == "stopping" - return - if not calledCallback - calledCallback = true - @log.warn "VLC terminated unexpectedly during startup." - cb? new Error "VLC terminated unexpectedly." - @log.warn "VLC terminated unexpectedly, restarting." - doStart() - - @_process = proc - - doStart() - - setTimeout (() => - if not calledCallback - calledCallback = true - - @instance = new VLCApi - host: ":#{encodeURIComponent config.get("vlc-password")}@#{config.get("vlc-host")}", - port: config.get("vlc-port") - cb? null, @instance), 1500 # TODO: Use some more stable condition + @_instance = instance + cb? null, @_instance + ### + # Shuts down the VLC instance. + ### stop: (cb) -> - if not @_process + if not @_instance cb?() return - @instance = null - - @_process.kill() - await @_process.once "exit", defer() + # TODO: Is there even a proper way to shut this down? + @_instance = null cb?() From 852052388df2c469e26a9455d5fd21f5db78c065 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 22:40:39 +0100 Subject: [PATCH 19/71] VLC event members are actually properties, not functions. Got the documentation wrong here, I was looking up examples and it looked as if calling the on* fields as functions was the right way to go but it was actually not. https://github.com/RSATom/WebChimera.js/blob/64bf4ffc362898e309c8a444c8888ad7b4bfae92/src/JsVlcPlayer.cpp#L90-L102 clearly defines as set-properties here. --- app.iced | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app.iced b/app.iced index 7c48de4..21826f9 100644 --- a/app.iced +++ b/app.iced @@ -64,15 +64,15 @@ ts3clientService.on "started", (ts3proc) => ts3query = ts3clientService.query # VLC event handling - vlc.onPlaying () => + vlc.onPlaying = () => info = vlcMediaInfo[vlc.playlist.items[vlc.playlist.currentItem].mrl] ts3query.sendtextmessage 2, 0, "Now playing [URL=#{info.originalUrl}]#{info.title}[/URL]." - 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.onEndReached () => ts3query.sendtextmessage 2, 0, "End of playlist reached." - vlc.onStopped () => ts3query.sendtextmessage 2, 0, "Stopped." + 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.onEndReached = () => ts3query.sendtextmessage 2, 0, "End of playlist reached." + vlc.onStopped = () => ts3query.sendtextmessage 2, 0, "Stopped." ts3query.currentScHandlerID = 1 ts3query.mydata = {} From 8ab524467bb56e90e17b9c9d3da887dd743c8c76 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 22:47:29 +0100 Subject: [PATCH 20/71] Fix non-numeric volume value being passed through to VLC as NaN. Those values are not supposed to be accepted in the first place... --- app.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index 21826f9..95c94f1 100644 --- a/app.iced +++ b/app.iced @@ -255,7 +255,7 @@ ts3clientService.on "started", (ts3proc) => when "vol" vol = parseInt paramline - if paramline.trim().length <= 0 or vol > 200 or vol < 0 + if paramline.trim().length <= 0 or vol == NaN or vol > 200 or vol < 0 ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol [/B] - takes a number between 0 (0%) and 200 (200%) to set the volume. 100% is 100. Defaults to 50 (50%) on startup." return From 824b4b94bd1dfbe12df6fefebe83342b33c27721 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 22:59:17 +0100 Subject: [PATCH 21/71] Implement "prev"/"previous" commands. These commands allow going to the previous entry in the playlist. --- app.iced | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app.iced b/app.iced index 95c94f1..4d94018 100644 --- a/app.iced +++ b/app.iced @@ -207,6 +207,8 @@ ts3clientService.on "started", (ts3proc) => vlc.play info.url when "next" vlc.playlist.next() + when "prev", "previous" + vlc.playlist.prev() when "enqueue", "add", "append" inputBB = paramline.trim() input = (removeBB paramline).trim() From 4eed972a235325b26276ad4a6f040b79081019f1 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:01:23 +0100 Subject: [PATCH 22/71] Implement "empty"/"clear" commands. These commands allow emptying the current playlist. Here another reminder to implement a proper permission system soon! --- app.iced | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app.iced b/app.iced index 4d94018..89e5a8d 100644 --- a/app.iced +++ b/app.iced @@ -209,6 +209,9 @@ ts3clientService.on "started", (ts3proc) => vlc.playlist.next() when "prev", "previous" 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() From cc783d0bc0db4a0b1d69463607a3f0751cb4dd64 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:09:28 +0100 Subject: [PATCH 23/71] Implement "list"/"playlist" commands. These commands make the bot print out the current playlist in the channel chat. The current track being played back is printed in bold green font. --- app.iced | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app.iced b/app.iced index 89e5a8d..a4cf592 100644 --- a/app.iced +++ b/app.iced @@ -212,6 +212,16 @@ ts3clientService.on "started", (ts3proc) => when "empty", "clear" vlc.playlist.clear() ts3query.sendtextmessage args.targetmode, invoker.id, "Cleared the playlist." + when "list", "playlist" + message = "Currently these tracks are in the playlist:\n" + for i in [ 0 .. vlc.playlist.items.count ] + if vlc.playlist.currentItem == i + message += "[COLOR=green][B]" + info = vlcMediaInfo[vlc.playlist.items[i].mrl] + message += "#{i + 1}. [URL=#{info.originalUrl}]#{info.title}[/URL]" + if vlc.playlist.currentItem == i + message += "[/B][/COLOR]" + ts3query.sendtextmessage args.targetmode, invoker.id, message when "enqueue", "add", "append" inputBB = paramline.trim() input = (removeBB paramline).trim() From ab18c3cd8f2824f94f818550ac77a9e0702dc76d Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:12:47 +0100 Subject: [PATCH 24/71] According to the WebChimera documentation this is not zero-based... --- app.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index a4cf592..da79b65 100644 --- a/app.iced +++ b/app.iced @@ -214,7 +214,7 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "Cleared the playlist." when "list", "playlist" message = "Currently these tracks are in the playlist:\n" - for i in [ 0 .. vlc.playlist.items.count ] + for i in [ 1 .. vlc.playlist.items.count ] if vlc.playlist.currentItem == i message += "[COLOR=green][B]" info = vlcMediaInfo[vlc.playlist.items[i].mrl] From d61d85e020cfaab777ad7aef978b204df776485f Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:18:13 +0100 Subject: [PATCH 25/71] Got the right CoffeeScript syntax for this loop now. --- app.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index da79b65..ac0a8b1 100644 --- a/app.iced +++ b/app.iced @@ -214,7 +214,7 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "Cleared the playlist." when "list", "playlist" message = "Currently these tracks are in the playlist:\n" - for i in [ 1 .. vlc.playlist.items.count ] + for i in [ 0 ... vlc.playlist.items.count ] if vlc.playlist.currentItem == i message += "[COLOR=green][B]" info = vlcMediaInfo[vlc.playlist.items[i].mrl] From eb00dc2f8795d15710d368f2fdb2f0df56509c93 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:19:40 +0100 Subject: [PATCH 26/71] While we're on it, fine-tuning the playlist message. --- app.iced | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app.iced b/app.iced index ac0a8b1..3f6cd34 100644 --- a/app.iced +++ b/app.iced @@ -213,7 +213,8 @@ ts3clientService.on "started", (ts3proc) => vlc.playlist.clear() ts3query.sendtextmessage args.targetmode, invoker.id, "Cleared the playlist." when "list", "playlist" - message = "Currently these tracks are in the playlist:\n" + message = "Currently there are #{vlc.playlist.items.count} tracks are in the playlist" + message += if vlc.playlist.items.count > 0 then ":\n" else "." for i in [ 0 ... vlc.playlist.items.count ] if vlc.playlist.currentItem == i message += "[COLOR=green][B]" From 0762d6d0bdd182a2b0949ab74202df7c3cea9222 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:38:14 +0100 Subject: [PATCH 27/71] Fix line breaks and tabs not being properly escaped in ts3query.iced. --- ts3query.iced | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/ts3query.iced b/ts3query.iced index a9505e6..3b1b076 100644 --- a/ts3query.iced +++ b/ts3query.iced @@ -10,9 +10,23 @@ merge = require "merge" parserLog = getLogger "parser" -escape = (value) => value.toString().replace(///\\///g, "\\\\").replace(/\//g, "\\/").replace(/\|/g, "\\|").replace(///\ ///g, "\\s") +escape = (value) => value.toString()\ + .replace(/\\/g, "\\\\")\ + .replace(/\//g, "\\/")\ + .replace(/\|/g, "\\|")\ + .replace(/\n/g, "\\n")\ + .replace(/\r/g, "\\r")\ + .replace(/\t/g, "\\t")\ + .replace(/\ /g, "\\s") -unescape = (value) => value.toString().replace(/\\s/g, " ").replace(/\\\//g, "/").replace(/\\\|/g, "|").replace(/\\\\/g, "\\") +unescape = (value) => value.toString()\ + .replace(/\\s/g, " ")\ + .replace(/\\t/g, "\t")\ + .replace(/\\r/g, "\r")\ + .replace(/\\n/g, "\n")\ + .replace(/\\\|/g, "|")\ + .replace(/\\\//g, "/")\ + .replace(/\\\\/g, "\\") buildCmd = (name, namedArgs, posArgs) => # TODO: Add support for collected arguments (aka lists) From d82721128f6319d4382daf0ee9adf9cf010137c1 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:54:01 +0100 Subject: [PATCH 28/71] Implement "loop" command. This command allows looping the playlist by just passing the command "loop on". Respectively, "loop off" turns off looping again. --- app.iced | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app.iced b/app.iced index 3f6cd34..e009421 100644 --- a/app.iced +++ b/app.iced @@ -205,6 +205,24 @@ ts3clientService.on "started", (ts3proc) => # play it in VLC vlc.play info.url + 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" vlc.playlist.next() when "prev", "previous" From 18b7d8296447e6dbfd8dfa82e7810e602294e40d Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:54:36 +0100 Subject: [PATCH 29/71] Implement "stop-after" command. This command allows stopping the playlist after the current playlist item is finished. --- app.iced | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app.iced b/app.iced index e009421..b8e8fb1 100644 --- a/app.iced +++ b/app.iced @@ -205,6 +205,9 @@ ts3clientService.on "started", (ts3proc) => # play it in VLC vlc.play info.url + 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 From 7966bfb0562403214fa673f113fc1664ae30ee3e Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:55:54 +0100 Subject: [PATCH 30/71] Playlist display (command "list"/"playlist") can generate too long messages, commenting out for now. --- app.iced | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app.iced b/app.iced index b8e8fb1..1a751ca 100644 --- a/app.iced +++ b/app.iced @@ -233,6 +233,7 @@ ts3clientService.on "started", (ts3proc) => when "empty", "clear" vlc.playlist.clear() ts3query.sendtextmessage args.targetmode, invoker.id, "Cleared the playlist." + ### when "list", "playlist" message = "Currently there are #{vlc.playlist.items.count} tracks are in the playlist" message += if vlc.playlist.items.count > 0 then ":\n" else "." @@ -244,6 +245,7 @@ ts3clientService.on "started", (ts3proc) => if vlc.playlist.currentItem == i message += "[/B][/COLOR]" ts3query.sendtextmessage args.targetmode, invoker.id, message + ### when "enqueue", "add", "append" inputBB = paramline.trim() input = (removeBB paramline).trim() From b238a655b9b13b1504cdb0f8a0f1823d5904c4f1 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 27 Oct 2015 23:57:50 +0100 Subject: [PATCH 31/71] This indenting stuff still sometimes doesn't really want to work out I guess... --- app.iced | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/app.iced b/app.iced index 1a751ca..f56ee7f 100644 --- a/app.iced +++ b/app.iced @@ -233,19 +233,6 @@ ts3clientService.on "started", (ts3proc) => when "empty", "clear" vlc.playlist.clear() ts3query.sendtextmessage args.targetmode, invoker.id, "Cleared the playlist." - ### - when "list", "playlist" - message = "Currently there are #{vlc.playlist.items.count} tracks are in the playlist" - message += if vlc.playlist.items.count > 0 then ":\n" else "." - for i in [ 0 ... vlc.playlist.items.count ] - if vlc.playlist.currentItem == i - message += "[COLOR=green][B]" - info = vlcMediaInfo[vlc.playlist.items[i].mrl] - message += "#{i + 1}. [URL=#{info.originalUrl}]#{info.title}[/URL]" - if vlc.playlist.currentItem == i - message += "[/B][/COLOR]" - ts3query.sendtextmessage args.targetmode, invoker.id, message - ### when "enqueue", "add", "append" inputBB = paramline.trim() input = (removeBB paramline).trim() From a5a8ae1046b453a24c54af816e3e7e66795ef8ec Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 28 Oct 2015 00:12:50 +0100 Subject: [PATCH 32/71] Implement some easy checks for "next" and "prev" commands. --- app.iced | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app.iced b/app.iced index f56ee7f..834b5f2 100644 --- a/app.iced +++ b/app.iced @@ -227,8 +227,14 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "[B]#{name} on|off[/B] - Turns playlist looping on or off" return when "next" + 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.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() From 9417e337087c360b9a32243960d98cbc8d3d6283 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 28 Oct 2015 00:14:44 +0100 Subject: [PATCH 33/71] Small fix for "prev" not giving an error when in empty playlist. --- app.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index 834b5f2..cf5006f 100644 --- a/app.iced +++ b/app.iced @@ -232,7 +232,7 @@ ts3clientService.on "started", (ts3proc) => return vlc.playlist.next() when "prev", "previous" - if vlc.playlist.mode != vlc.playlist.Loop and vlc.playlist.currentItem == 0 + 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() From 4d25c2b822d1660546a48b89db34645285408dd7 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 28 Oct 2015 00:16:10 +0100 Subject: [PATCH 34/71] Small fixes for empty playlists. --- app.iced | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app.iced b/app.iced index cf5006f..e71ef00 100644 --- a/app.iced +++ b/app.iced @@ -227,11 +227,17 @@ ts3clientService.on "started", (ts3proc) => 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 From f3157857c488621ca2d172bf505af47e3cf1249b Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 28 Oct 2015 00:21:54 +0100 Subject: [PATCH 35/71] Completely remove handler for vlc.onEndReached. onEndReached does not just trigger the callback at the end of the playlist but actually at the end of each track. This should have been better documented. --- app.iced | 1 - 1 file changed, 1 deletion(-) diff --git a/app.iced b/app.iced index e71ef00..a05428b 100644 --- a/app.iced +++ b/app.iced @@ -71,7 +71,6 @@ ts3clientService.on "started", (ts3proc) => 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.onEndReached = () => ts3query.sendtextmessage 2, 0, "End of playlist reached." vlc.onStopped = () => ts3query.sendtextmessage 2, 0, "Stopped." ts3query.currentScHandlerID = 1 From adeccf6d57da68d61f98190d4a102f7a68486198 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 29 Oct 2015 02:23:53 +0100 Subject: [PATCH 36/71] Allow "vol" to display current volume if no arguments were given. --- app.iced | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app.iced b/app.iced index a05428b..b08eea6 100644 --- a/app.iced +++ b/app.iced @@ -290,7 +290,14 @@ ts3clientService.on "started", (ts3proc) => when "stop" vlc.stop() when "vol" - vol = parseInt paramline + inputBB = paramline.trim() + input = (removeBB paramline).trim() + + if inputBB.length <= 0 + ts3query.sendtextmessage args.targetmode, invoker.id, "Volume is currently set to #{vlc.audio.volume}%." + return + + vol = parseInt input if paramline.trim().length <= 0 or vol == NaN or vol > 200 or vol < 0 ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol [/B] - takes a number between 0 (0%) and 200 (200%) to set the volume. 100% is 100. Defaults to 50 (50%) on startup." From c20462272f370ed1e88a5227860583fe3e0559a3 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 29 Oct 2015 02:24:11 +0100 Subject: [PATCH 37/71] Stop audio playback when shutting down TS3Bot. --- services/vlc.iced | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/vlc.iced b/services/vlc.iced index b52ee83..e5e1762 100644 --- a/services/vlc.iced +++ b/services/vlc.iced @@ -36,6 +36,8 @@ module.exports = class VLCService extends services.Service cb?() return + @_instance.stop() + # TODO: Is there even a proper way to shut this down? @_instance = null From 82c19a2196770c463d8c94fc9e5842dfe8697c8d Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 29 Oct 2015 03:08:44 +0100 Subject: [PATCH 38/71] Added explicit process.exit after app shutdown. Since introducing WebChimera.js the bot is no longer shutting down by itself after all services are shut down since the instances that WebChimera.js generates would still be alive after deletion. There is no way to get around this except if WebChimera.js reveals an explicit release function which it doesn't. The only release function gets called before the VM gets killed. --- app.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app.js b/app.js index 325d330..ec938f7 100644 --- a/app.js +++ b/app.js @@ -33,6 +33,7 @@ doShutdownAsync = function(cb) { process.on("uncaughtException", function(err) { log.error("Shutting down due to an uncaught exception!", err); app.shutdownSync(); + process.exit(0xFF); }); process.on("exit", function(e) { @@ -43,24 +44,29 @@ process.on("exit", function(e) { process.on("SIGTERM", function(e) { log.debug("Caught SIGTERM signal"); app.shutdown(); + process.exit(0); }); process.on("SIGINT", function() { log.debug("Caught SIGINT signal"); app.shutdown(); + process.exit(0); }); process.on("SIGHUP", function() { log.debug("Caught SIGHUP signal"); app.shutdown(); + process.exit(0); }); process.on("SIGQUIT", function() { log.debug("Caught SIGQUIT signal"); app.shutdown(); + process.exit(0); }); process.on("SIGABRT", function() { log.debug("Caught SIGABRT signal"); app.shutdown(); + process.exit(0); }); From f650c3296b1ffdcf538cc5ffa690408282c5868c Mon Sep 17 00:00:00 2001 From: Icedream Jenkins Date: Mon, 2 Nov 2015 12:18:36 +0100 Subject: [PATCH 39/71] Make xdotool optional. --- require_bin.iced | 12 +++++++++--- x11.iced | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/require_bin.iced b/require_bin.iced index 66158f2..1a6a77f 100644 --- a/require_bin.iced +++ b/require_bin.iced @@ -2,7 +2,9 @@ which = require("which").sync path = require "path" log = require("./logger")("RequireBin") -module.exports = (binName) => +module.exports = (binName, doErrorIfNotFound) => + doErrorIfNotFound = true unless doErrorIfNotFound? + # check if xvfb is findable from here if path.resolve(binName) == path.normalize(binName) # this is an absolute path @@ -14,5 +16,9 @@ module.exports = (binName) => log.debug "#{binName} detected:", binPath return binPath catch err - log.error "#{binName} could not be found.", err - throw new Error "#{binName} could not be found." + if doErrorIfNotFound + log.error "#{binName} could not be found." + throw new Error "#{binName} could not be found." + else + log.warn "#{binName} could not be found." + return null diff --git a/x11.iced b/x11.iced index c679dca..05ce1f1 100644 --- a/x11.iced +++ b/x11.iced @@ -6,13 +6,18 @@ services = require("./services") StreamSplitter = require("stream-splitter") require_bin = require("./require_bin") -xdotoolBinPath = require_bin "xdotool" +xdotoolBinPath = require_bin "xdotool", false # Just some tools to work with the X11 windows module.exports = getWindowIdByProcessId: (pid, cb) => wid = null + # Return null instantly if xdotool is not available + if not xdotoolBinPath? + cb? new Error "xdotool is not available" + return + # We provide --name due to the bug mentioned at https://github.com/jordansissel/xdotool/issues/14 xdoproc = spawn xdotoolBinPath, [ "search", "--any", "--pid", pid, "--name", "xdosearch" ], env: @@ -43,6 +48,11 @@ module.exports = getWindowIdByProcessIdSync: (pid) => Sync() => @getWindowIdByProcessId.sync @, pid sendKeys: (wid, keys, cb) => + # Do not bother trying if xdotool is not available + if not xdotoolBinPath? + cb? new Error "xdotool not available." + return + # blackbox needs to be running for windowactivate to work blackboxService = services.find("BlackBox") if blackboxService.state != "started" @@ -73,4 +83,4 @@ module.exports = err = new Error "Failed to send keys." cb? err - sendKeysSync: (keys) => Sync () => @sendKeys.sync @, keys \ No newline at end of file + sendKeysSync: (keys) => Sync () => @sendKeys.sync @, keys From b6a70ebdc5af5c6a48220fa5c45a03ba4d798940 Mon Sep 17 00:00:00 2001 From: Icedream Jenkins Date: Mon, 2 Nov 2015 12:20:40 +0100 Subject: [PATCH 40/71] Delay error that Xvfb is not available so main code can handle it instead. --- services/xvfb.iced | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/xvfb.iced b/services/xvfb.iced index b3f1027..4edc696 100644 --- a/services/xvfb.iced +++ b/services/xvfb.iced @@ -4,11 +4,15 @@ config = require("../config") services = require("../services") require_bin = require("../require_bin") -require_bin "Xvfb" +xvfbPath = require_bin "Xvfb", false module.exports = class XvfbService extends services.Service constructor: -> super "Xvfb", start: (cb) -> + if not xvfbPath? + cb? new Error "Xvfb is not available." + return + if @instance cb? null, @instance return From ae4288d44a22c0fc31c0ced8fe03b05b4c516141 Mon Sep 17 00:00:00 2001 From: Carl Kittelberger Date: Mon, 2 Nov 2015 12:42:37 +0100 Subject: [PATCH 41/71] Remove left-over explicit API shutdown code. The code removed in this commit was not removed in the process of migrating to the new HTTP-server-less code. It causes additional errors and is generally useless now. --- app.iced | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app.iced b/app.iced index b08eea6..d4e6c52 100644 --- a/app.iced +++ b/app.iced @@ -18,13 +18,6 @@ removeBB = (str) -> str.replace /\[(\w+)[^\]]*](.*?)\[\/\1]/g, "$2" module.exports = shutdown: (cb) => - apiService = services.find("api") - if apiService and apiService.state == "started" - await apiService.stop defer(err) - if err - cb? new Error "Could not stop API" - return - ts3clientService = services.find("ts3client") if ts3clientService and ts3clientService.state == "started" await ts3clientService.stop defer(err) From ee9bd9c0fc1f8fe2d0b446567a4695ce5de0ec1e Mon Sep 17 00:00:00 2001 From: Carl Kittelberger Date: Mon, 2 Nov 2015 12:44:12 +0100 Subject: [PATCH 42/71] Delay Blackbox availability handling. Allow the main code to handle this case instead. --- services/blackbox.iced | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/blackbox.iced b/services/blackbox.iced index e73fdb4..a645200 100644 --- a/services/blackbox.iced +++ b/services/blackbox.iced @@ -4,7 +4,7 @@ services = require("../services") StreamSplitter = require("stream-splitter") require_bin = require("../require_bin") -blackboxBinPath = require_bin "blackbox" +blackboxBinPath = require_bin "blackbox", false module.exports = class BlackBoxService extends services.Service dependencies: [ @@ -12,6 +12,10 @@ module.exports = class BlackBoxService extends services.Service ] constructor: -> super "BlackBox", start: (cb) -> + if not blackboxBinPath? + cb? new Error "Blackbox not available." + return + if @process cb? null, @process return From ce801fecbb3490ceafa9f833495fa8b8ddcafc05 Mon Sep 17 00:00:00 2001 From: Carl Kittelberger Date: Mon, 2 Nov 2015 12:45:27 +0100 Subject: [PATCH 43/71] Use custom XDG runtime dirs for better isolation. --- app.iced | 4 ++++ package.json | 1 + services/ts3client.iced | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/app.iced b/app.iced index d4e6c52..fc4fe4d 100644 --- a/app.iced +++ b/app.iced @@ -8,6 +8,7 @@ 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 @@ -34,6 +35,9 @@ module.exports = 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" + # PulseAudio daemon await services.find("pulseaudio").start defer err if err diff --git a/package.json b/package.json index 78ac299..9f31387 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "stream-splitter": "^0.3.2", "string.prototype.startswith": "^0.2.0", "sync": "^0.2.5", + "temp": "^0.8.3", "valid-url": "^1.0.9", "webchimera.js": "^0.1.38", "which": "^1.1.2", diff --git a/services/ts3client.iced b/services/ts3client.iced index ca662c4..093ba57 100644 --- a/services/ts3client.iced +++ b/services/ts3client.iced @@ -22,6 +22,10 @@ module.exports = class TS3ClientService extends services.Service ] constructor: -> super "TS3Client", start: (args, cb) => + if not process.env.XDG_RUNTIME_DIR? or process.env.XDG_RUNTIME_DIR.trim() == "" + cb? new Error "XDG runtime directory needs to be set." + return + if typeof args == "function" cb = args args = null From 2e246a1a9888bd9318dbb3f5c9f800e0c964c330 Mon Sep 17 00:00:00 2001 From: Carl Kittelberger Date: Mon, 2 Nov 2015 12:46:30 +0100 Subject: [PATCH 44/71] Do not require an isolated GUI anymore. This commit allows the TS3Bot to boot in an environment where there is no way to boot an isolated graphical environment. In that case TS3Bot will try to use the already existing display and the desktop manager that runs on it already. This adds some undefined error cases which still need to be found and fixed, so be warned: This commit is very, VERY experimental! --- services/pulseaudio.iced | 1 - services/ts3client.iced | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/services/pulseaudio.iced b/services/pulseaudio.iced index fc10f9b..3f1c1eb 100644 --- a/services/pulseaudio.iced +++ b/services/pulseaudio.iced @@ -10,7 +10,6 @@ pacmdPath = require_bin "pacmd" module.exports = class PulseAudioService extends services.Service dependencies: [ - "xvfb" ] constructor: -> super "PulseAudio", start: (cb) -> diff --git a/services/ts3client.iced b/services/ts3client.iced index 093ba57..a179b09 100644 --- a/services/ts3client.iced +++ b/services/ts3client.iced @@ -16,8 +16,6 @@ ts3client_binpath = require_bin path.join(config.get("ts3-install-path"), "ts3cl module.exports = class TS3ClientService extends services.Service dependencies: [ - "xvfb", - "blackbox", "pulseaudio" ] constructor: -> super "TS3Client", @@ -26,6 +24,10 @@ module.exports = class TS3ClientService extends services.Service cb? new Error "XDG runtime directory needs to be set." return + if not process.env.DISPLAY? or process.env.DISPLAY.trim() == "" + cb? new Error "There is no display to run TeamSpeak3 on." + return + if typeof args == "function" cb = args args = null From 8afc967796c19400555cb37fc73daf4566a8aee7 Mon Sep 17 00:00:00 2001 From: icedream Date: Mon, 2 Nov 2015 13:26:49 +0100 Subject: [PATCH 45/71] Print more details on service startup failures. --- app.iced | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app.iced b/app.iced index fc4fe4d..eba9cd2 100644 --- a/app.iced +++ b/app.iced @@ -41,13 +41,13 @@ process.env.XDG_RUNTIME_DIR = temp.mkdirSync "ts3bot-xdg" # PulseAudio daemon await services.find("pulseaudio").start defer err if err - log.warn "PulseAudio could not start up, audio may not act as expected!" + 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!" + log.warn "VLC could not start up!", err await module.exports.shutdown defer() process.exit 1 @@ -316,6 +316,6 @@ ts3clientService.on "started", (ts3proc) => await ts3clientService.start [ config.get("ts3-server") ], defer(err, ts3proc) if err - log.error "TeamSpeak3 could not start, shutting down." + log.error "TeamSpeak3 could not start, shutting down.", err await module.exports.shutdown defer() process.exit 1 From 02e6f0c4891474fb0497405e3223ae9ad91531a5 Mon Sep 17 00:00:00 2001 From: icedream Date: Mon, 2 Nov 2015 13:38:09 +0100 Subject: [PATCH 46/71] Boot Xvfb via app.iced. --- app.iced | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app.iced b/app.iced index eba9cd2..b4396cb 100644 --- a/app.iced +++ b/app.iced @@ -38,6 +38,16 @@ module.exports = # 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 From b1cc9f6255d8130e861d8f7b901a5d45df3ed47a Mon Sep 17 00:00:00 2001 From: icedream Date: Mon, 2 Nov 2015 15:42:37 +0100 Subject: [PATCH 47/71] Fix pipes from TS3 query not being decoded, fixes #22. --- ts3query.iced | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts3query.iced b/ts3query.iced index 3b1b076..4ff1230 100644 --- a/ts3query.iced +++ b/ts3query.iced @@ -13,7 +13,7 @@ parserLog = getLogger "parser" escape = (value) => value.toString()\ .replace(/\\/g, "\\\\")\ .replace(/\//g, "\\/")\ - .replace(/\|/g, "\\|")\ + .replace(/\|/g, "\\p")\ .replace(/\n/g, "\\n")\ .replace(/\r/g, "\\r")\ .replace(/\t/g, "\\t")\ @@ -24,7 +24,7 @@ unescape = (value) => value.toString()\ .replace(/\\t/g, "\t")\ .replace(/\\r/g, "\r")\ .replace(/\\n/g, "\n")\ - .replace(/\\\|/g, "|")\ + .replace(/\\p/g, "|")\ .replace(/\\\//g, "/")\ .replace(/\\\\/g, "\\") From 2ee4dc32c30e61e25f13a7062850f8481dfd86a1 Mon Sep 17 00:00:00 2001 From: icedream Date: Mon, 2 Nov 2015 16:06:34 +0100 Subject: [PATCH 48/71] Quick fix for the no-metadata crash, closes #23. --- app.iced | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app.iced b/app.iced index b4396cb..828b76d 100644 --- a/app.iced +++ b/app.iced @@ -72,8 +72,9 @@ ts3clientService.on "started", (ts3proc) => # VLC event handling vlc.onPlaying = () => + # TODO: Check why info is sometimes null, something must be wrong with the "add"/"play" commands here! info = vlcMediaInfo[vlc.playlist.items[vlc.playlist.currentItem].mrl] - ts3query.sendtextmessage 2, 0, "Now playing [URL=#{info.originalUrl}]#{info.title}[/URL]." + ts3query.sendtextmessage 2, 0, "Now playing [URL=#{info?.originalUrl or vlc.playlist.items[vlc.playlist.currentItem].mrl}]#{info?.title or vlc.playlist.items[vlc.playlist.currentItem].mrl}[/URL]." vlc.onPaused = () => ts3query.sendtextmessage 2, 0, "Paused." vlc.onForward = () => ts3query.sendtextmessage 2, 0, "Fast-forwarding..." vlc.onBackward = () => ts3query.sendtextmessage 2, 0, "Rewinding..." From 4d7550865e59a95e7bae73f2c1fd7f99ecdfb8a0 Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 3 Nov 2015 01:28:30 +0100 Subject: [PATCH 49/71] Store wanted volume in a variable and set on VLC when playback starts. This gets rid of a configuration issue in the Docker image where VLC would reset the volume to 100% on each new track. --- app.iced | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app.iced b/app.iced index 828b76d..905cd55 100644 --- a/app.iced +++ b/app.iced @@ -61,6 +61,9 @@ if 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 = {} @@ -72,6 +75,9 @@ ts3clientService.on "started", (ts3proc) => # VLC event handling vlc.onPlaying = () => + # Restore audio volume + vlc.audio.volume = vlcVolume + # TODO: Check why info is sometimes null, something must be wrong with the "add"/"play" commands here! info = vlcMediaInfo[vlc.playlist.items[vlc.playlist.currentItem].mrl] ts3query.sendtextmessage 2, 0, "Now playing [URL=#{info?.originalUrl or vlc.playlist.items[vlc.playlist.currentItem].mrl}]#{info?.title or vlc.playlist.items[vlc.playlist.currentItem].mrl}[/URL]." @@ -302,7 +308,7 @@ ts3clientService.on "started", (ts3proc) => input = (removeBB paramline).trim() if inputBB.length <= 0 - ts3query.sendtextmessage args.targetmode, invoker.id, "Volume is currently set to #{vlc.audio.volume}%." + ts3query.sendtextmessage args.targetmode, invoker.id, "Volume is currently set to #{vlcVolume}%." return vol = parseInt input @@ -311,7 +317,7 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol [/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 = vol + vlc.audio.volume = vlcVolume = vol ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set to #{vol}%." when "changenick" nick = if paramline.length > params[0].length then paramline else params[0] From cbb6204b527bec9b04ac41204728753c153851fc Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 3 Nov 2015 03:55:08 +0100 Subject: [PATCH 50/71] Fix process exiting before all services shut down. Fixes #24 and potentially #25. --- app.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/app.js b/app.js index ec938f7..8e7cd8f 100644 --- a/app.js +++ b/app.js @@ -43,30 +43,35 @@ process.on("exit", function(e) { process.on("SIGTERM", function(e) { log.debug("Caught SIGTERM signal"); - app.shutdown(); - process.exit(0); + app.shutdown(function() { + process.exit(0); + }); }); process.on("SIGINT", function() { log.debug("Caught SIGINT signal"); - app.shutdown(); - process.exit(0); + app.shutdown(function() { + process.exit(0); + }); }); process.on("SIGHUP", function() { log.debug("Caught SIGHUP signal"); - app.shutdown(); - process.exit(0); + app.shutdown(function() { + process.exit(0); + }); }); process.on("SIGQUIT", function() { log.debug("Caught SIGQUIT signal"); - app.shutdown(); - process.exit(0); + app.shutdown(function() { + process.exit(0); + }); }); process.on("SIGABRT", function() { log.debug("Caught SIGABRT signal"); - app.shutdown(); - process.exit(0); + app.shutdown(function() { + process.exit(0); + }); }); From 655659e254940082a3629631fbf8e698bf4689da Mon Sep 17 00:00:00 2001 From: icedream Date: Tue, 3 Nov 2015 03:59:35 +0100 Subject: [PATCH 51/71] Prevent crash on shutdown caused by VLC stop. The code tried to send a "Stopped." message to TeamSpeak3 when VLC shut down. However VLC gets shut down after TeamSpeak3 which causes a null reference on the query interface. --- app.iced | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app.iced b/app.iced index 905cd55..e5aa742 100644 --- a/app.iced +++ b/app.iced @@ -73,6 +73,9 @@ ts3clientService = services.find("ts3client") ts3clientService.on "started", (ts3proc) => ts3query = ts3clientService.query + ts3clientService.once "stopped", () => + ts3query = undefined + # VLC event handling vlc.onPlaying = () => # Restore audio volume @@ -80,12 +83,12 @@ ts3clientService.on "started", (ts3proc) => # TODO: Check why info is sometimes null, something must be wrong with the "add"/"play" commands here! info = vlcMediaInfo[vlc.playlist.items[vlc.playlist.currentItem].mrl] - ts3query.sendtextmessage 2, 0, "Now playing [URL=#{info?.originalUrl or vlc.playlist.items[vlc.playlist.currentItem].mrl}]#{info?.title or vlc.playlist.items[vlc.playlist.currentItem].mrl}[/URL]." - vlc.onPaused = () => ts3query.sendtextmessage 2, 0, "Paused." - vlc.onForward = () => ts3query.sendtextmessage 2, 0, "Fast-forwarding..." - vlc.onBackward = () => ts3query.sendtextmessage 2, 0, "Rewinding..." + ts3query?.sendtextmessage 2, 0, "Now playing [URL=#{info?.originalUrl or vlc.playlist.items[vlc.playlist.currentItem].mrl}]#{info?.title or vlc.playlist.items[vlc.playlist.currentItem].mrl}[/URL]." + 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." + vlc.onStopped = () => ts3query?.sendtextmessage 2, 0, "Stopped." ts3query.currentScHandlerID = 1 ts3query.mydata = {} From 59bdee92296c49090c298b0193d250394c36bbbc Mon Sep 17 00:00:00 2001 From: Icedream Date: Tue, 3 Nov 2015 09:28:15 +0100 Subject: [PATCH 52/71] Fix update routine in ts3settings.iced. Fixes #30. Signed-off-by: icedream --- ts3settings.iced | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ts3settings.iced b/ts3settings.iced index 81f9e3b..db03705 100644 --- a/ts3settings.iced +++ b/ts3settings.iced @@ -214,12 +214,19 @@ module.exports = class SettingsFile settingsObj.log.silly "Requested update of #{id.id}" for own index, identity of settingsObj.identities if identity.id == id.id + # remove functions from this object + cleanIdentity = merge @ + for own k, v of cleanIdentity + if typeof v == "function" + delete cleanIdentity[k] + + # now this is our new identity object! settingsObj.log.silly "Updating identity #{id.id}" - settingsObj.identities[index] = merge identity, id + settingsObj.identities[index] = cleanIdentity return remove: () -> for own index, identity of settingsObj.identities if identity.id == id.id delete settingsObj.identities[index] break - # TODO: Select another identity as default \ No newline at end of file + # TODO: Select another identity as default From d403f93e76ed25fadc2f2a34016e2e3858f73082 Mon Sep 17 00:00:00 2001 From: Carl Kittelberger Date: Tue, 3 Nov 2015 10:36:06 +0100 Subject: [PATCH 53/71] Fix wrong saving of last used service arguments. Affects #31. --- service_template.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service_template.iced b/service_template.iced index 5f863d6..b155c32 100644 --- a/service_template.iced +++ b/service_template.iced @@ -78,7 +78,7 @@ module.exports = class Service extends EventEmitter if not quiet @log.info "Started #{@name}" - @_lastArgs = args + @_lastArgs = serviceArgs @state = "started" @emit "started", service From 95f939de53793a9722b8f26be2f6bfa0021bcaf2 Mon Sep 17 00:00:00 2001 From: Carl Kittelberger Date: Tue, 3 Nov 2015 10:37:05 +0100 Subject: [PATCH 54/71] Rewrite "apply" calls with easier syntax. --- service_template.iced | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service_template.iced b/service_template.iced index b155c32..5ad2630 100644 --- a/service_template.iced +++ b/service_template.iced @@ -19,7 +19,7 @@ module.exports = class Service extends EventEmitter state: "stopped" - start: () => @_start.apply @, [ false ].concat Array.prototype.slice.call(arguments) + start: () => @_start false, arguments... startSync: () => Sync () => @start.sync @ @@ -85,7 +85,7 @@ module.exports = class Service extends EventEmitter cb? null, service - stop: () => @_stop.apply @, [ false ].concat Array.prototype.slice.call(arguments) + stop: () => @_stop false, arguments... stopSync: () => Sync () => @stop.sync @ @@ -151,4 +151,4 @@ module.exports = class Service extends EventEmitter cb? err - restartSync: () => Sync () => @restart.sync @ \ No newline at end of file + restartSync: () => Sync () => @restart.sync @ From eaa0d0076710758a31793c3df404ec2b64650a90 Mon Sep 17 00:00:00 2001 From: Carl Kittelberger Date: Tue, 3 Nov 2015 11:23:20 +0100 Subject: [PATCH 55/71] Create README. Closes #29. README describes a bit about this repository and how to run this code outside of Docker for testing purposes. More information will be added in the future and as requested. Ideas and suggestions are warmly appreciated! --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2f953a --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# TS3Bot + +A new and easy way to set up your own TeamSpeak 3 bot! + +This repository contains the Node.js/IcedCoffeeScript code needed for the TS3Bot Docker image. + +## Running TS3Bot without Docker + +Basically these instructions are derived from [the Docker build files](https://github.com/icedream/ts3bot-docker). +You can adopt these depending on your OS (the Docker image uses Debian Jessie, so the instructions below are for +that OS). + +We assume that we want to run the "develop" branch of TS3Bot here. You can easily replace "develop" with another branch you want to run, like "master" for the stable code. + +Commands being run as your own user are marked with `$` and commands being run as root are marked with `#`. + +- Install the dependencies: + + $ apt-get install blackbox xvfb xdotool pulseaudio pulseaudio-utils cmake libvlc-dev vlc-plugin-pulse + +- Create a new user to run TS3Bot on - do not run this on your own user if you use TeamSpeak3 on it as that will overwrite your configuration! + + # adduser --system --disabled-login --disabled-password ts3bot + +- Download and unpack TeamSpeak3 client for your platform into a folder accessible by the TS3Bot user. Only read access is required. Replace `3.0.18.2` with whatever version of TeamSpeak3 you prefer to install, usually that is the most recent one. Accept the license that shows up in the process. + + # wget -Ots3client.run http://dl.4players.de/ts/releases/3.0.18.2/TeamSpeak3-Client-linux_$(uname -p)-3.0.18.2.run + # chmod +x ts3client.run + # ./ts3client.run --target ~ts3bot/ts3client + # rm ts3client.run + +- Download the TS3Bot control application into your TS3Bot user's home folder. The TS3Bot user itself only needs read access to the code. You can do this in two ways: + o By downloading the tar.gz archive from GitHub and unpacking it. + + # wget -q -O- https://github.com/icedream/ts3bot-control/archive/develop.tar.gz | tar xz -C ~ts3bot + + o By cloning the Git repository from GitHub. + + # git clone https://github.com/icedream/ts3bot-control -b develop ~ts3bot/ts3bot-control-develop + +- Install the Node.js dependencies using `npm`. Note how a simple `npm install` will install the wrong version of WebChimera.js and you need to provide it with correct Node.js information (environment variables `npm_config_wcjs_runtime` and `npm_config_wcjs_runtime_version`) like this: + + # cd ~ts3bot/ts3bot-control-develop + # npm_config_wcjs_runtime="node" npm_config_wcjs_runtime_version="$(node --version | tr -d 'v')" npm install + +- Now set up your TS3Bot configuration files in your TS3Bot user's home folder. For this create a folder `.ts3bot` in the home directory and put a `config.json` with your configuration there. The most minimal configuration consists of: + o `identity-path` - The path to your identity INI file, export a newly generated identity from your own TeamSpeak3 client for that. + o `ts3-install-path` - The path where you installed the TeamSpeak3 client to, you can skip this if you used exactly the same path as in the instructions above. + o `ts3-server` - The URL to the server/channel you want the bot to connect to, you can get from your own TS3 client via "Extras" > "Invite friend", select the checkbox "Channel" and select "ts3server link" as invitation type. + +Running the bot can finally be done like this: + + $ sudo -u ts3bot -H node ~ts3bot/ts3bot-control-develop + +You can provide your configuration as command line arguments instead if you want, that can be useful for just temporary configuration you want to test. For that just append the configuration options to the command line above and prefix every command line option with `--`, for example for `ts3-install-path` you would write `--ts3-install-path=/your/path/to/ts3client` From 8fe17abdf8cec65c69cd5d4c479f910301cfe053 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 4 Nov 2015 19:39:25 +0100 Subject: [PATCH 56/71] Add compatibility with other window managers using x-window-manager. --- services.iced | 2 +- services/{blackbox.iced => xwm.iced} | 20 ++++++++++---------- x11.iced | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) rename services/{blackbox.iced => xwm.iced} (73%) diff --git a/services.iced b/services.iced index 2d6168a..f3e8399 100644 --- a/services.iced +++ b/services.iced @@ -60,7 +60,7 @@ services = [ new(require "./services/ts3client") new(require "./services/vlc") new(require "./services/xvfb") - new(require "./services/blackbox") + new(require "./services/xwm") ] services.sort require("./service_depcomparer") # sort services by dependency for service in services diff --git a/services/blackbox.iced b/services/xwm.iced similarity index 73% rename from services/blackbox.iced rename to services/xwm.iced index a645200..757a135 100644 --- a/services/blackbox.iced +++ b/services/xwm.iced @@ -1,19 +1,19 @@ spawn = require("child_process").spawn -log = require("../logger")("BlackBox") +log = require("../logger")("XWindowManager") services = require("../services") StreamSplitter = require("stream-splitter") require_bin = require("../require_bin") -blackboxBinPath = require_bin "blackbox", false +xwmBinPath = require_bin "x-window-manager", false -module.exports = class BlackBoxService extends services.Service +module.exports = class XWindowManagerService extends services.Service dependencies: [ "xvfb" ] - constructor: -> super "BlackBox", + constructor: -> super "XWindowManager", start: (cb) -> - if not blackboxBinPath? - cb? new Error "Blackbox not available." + if not xwmBinPath? + cb? new Error "A window manager not available." return if @process @@ -29,7 +29,7 @@ module.exports = class BlackBoxService extends services.Service if err throw new Error "Dependency xvfb failed." - proc = spawn blackboxBinPath, [ "-rc", "/dev/null" ], + proc = spawn xwmBinPath, [ "-rc", "/dev/null" ], stdio: ['ignore', 'pipe', 'pipe'] detached: true env: @@ -55,9 +55,9 @@ module.exports = class BlackBoxService extends services.Service return if not calledCallback calledCallback = true - @log.warn "BlackBox terminated unexpectedly during startup." - cb? new Error "BlackBox terminated unexpectedly." - @log.warn "BlackBox terminated unexpectedly, restarting." + @log.warn "Window manager terminated unexpectedly during startup." + cb? new Error "Window manager terminated unexpectedly." + @log.warn "Window manager terminated unexpectedly, restarting." doStart() @process = proc diff --git a/x11.iced b/x11.iced index 05ce1f1..948916f 100644 --- a/x11.iced +++ b/x11.iced @@ -53,12 +53,12 @@ module.exports = cb? new Error "xdotool not available." return - # blackbox needs to be running for windowactivate to work - blackboxService = services.find("BlackBox") - if blackboxService.state != "started" - await blackboxService.start defer(err) + # a window manager needs to be running for windowactivate to work + xwmService = services.find("XWindowManager") + if xwmService.state != "started" + await xwmService.start defer(err) if err - cb? new Error "Could not start compatible window manager." + cb? new Error "Could not start a window manager." return xdoproc = spawn xdotoolBinPath, [ "windowactivate", "--sync", wid, "key", "--clearmodifiers", "--delay", "100" ].concat(keys), From 39f5ec1ec0725634c46a64c5f0cb1a3ea6a9fd91 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 4 Nov 2015 22:08:52 +0100 Subject: [PATCH 57/71] Also check vol against null. --- app.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index e5aa742..b2f947c 100644 --- a/app.iced +++ b/app.iced @@ -316,7 +316,7 @@ ts3clientService.on "started", (ts3proc) => vol = parseInt input - if paramline.trim().length <= 0 or vol == NaN or vol > 200 or vol < 0 + if paramline.trim().length <= 0 or vol == NaN or vol == null or vol > 200 or vol < 0 ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol [/B] - takes a number between 0 (0%) and 200 (200%) to set the volume. 100% is 100. Defaults to 50 (50%) on startup." return From bfb0ff154b6db35a40b0915be06c2cd925c51414 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 4 Nov 2015 23:23:34 +0100 Subject: [PATCH 58/71] Remove left-over parameters for blackbox for compatibility with other WMs. --- services/xwm.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/xwm.iced b/services/xwm.iced index 757a135..6be4219 100644 --- a/services/xwm.iced +++ b/services/xwm.iced @@ -29,7 +29,7 @@ module.exports = class XWindowManagerService extends services.Service if err throw new Error "Dependency xvfb failed." - proc = spawn xwmBinPath, [ "-rc", "/dev/null" ], + proc = spawn xwmBinPath, [], stdio: ['ignore', 'pipe', 'pipe'] detached: true env: From 2b7da8705f1fbb9125f6aa4c1ac84fdca9cb0113 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 4 Nov 2015 23:33:44 +0100 Subject: [PATCH 59/71] Require a DISPLAY to be set instead of Xvfb to be running for the window manager. --- services/xwm.iced | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/services/xwm.iced b/services/xwm.iced index 6be4219..6114e2b 100644 --- a/services/xwm.iced +++ b/services/xwm.iced @@ -8,7 +8,6 @@ xwmBinPath = require_bin "x-window-manager", false module.exports = class XWindowManagerService extends services.Service dependencies: [ - "xvfb" ] constructor: -> super "XWindowManager", start: (cb) -> @@ -16,6 +15,14 @@ module.exports = class XWindowManagerService extends services.Service cb? new Error "A window manager not available." return + if not process.env.XDG_RUNTIME_DIR? or process.env.XDG_RUNTIME_DIR.trim() == "" + cb? new Error "XDG runtime directory needs to be set." + return + + if not process.env.DISPLAY? or process.env.DISPLAY.trim() == "" + cb? new Error "There is no display to run TeamSpeak3 on." + return + if @process cb? null, @process return @@ -25,10 +32,6 @@ module.exports = class XWindowManagerService extends services.Service proc = null doStart = null doStart = () => - await services.find("xvfb").start defer(err) - if err - throw new Error "Dependency xvfb failed." - proc = spawn xwmBinPath, [], stdio: ['ignore', 'pipe', 'pipe'] detached: true From 091f77f352d4b0ca40ee8ee8f802c0630b7a8141 Mon Sep 17 00:00:00 2001 From: icedream Date: Wed, 4 Nov 2015 23:39:25 +0100 Subject: [PATCH 60/71] Remove code checking the nickname length. --- app.iced | 3 --- 1 file changed, 3 deletions(-) diff --git a/app.iced b/app.iced index b2f947c..fc93a2f 100644 --- a/app.iced +++ b/app.iced @@ -324,9 +324,6 @@ ts3clientService.on "started", (ts3proc) => ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set to #{vol}%." when "changenick" nick = if paramline.length > params[0].length then paramline else params[0] - if nick.length < 3 or nick.length > 30 - ts3query.sendtextmessage args.targetmode, invoker.id, "Invalid nickname." - return Sync () => try ts3query.clientupdate.sync ts3query, { client_nickname: nick } From d40ffdc76b91fd056946f78d2a04bcb01f6744e8 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 00:06:58 +0100 Subject: [PATCH 61/71] Fix #34 by removing useless length check for "changenick" command. --- app.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index fc93a2f..bbf599f 100644 --- a/app.iced +++ b/app.iced @@ -323,7 +323,7 @@ ts3clientService.on "started", (ts3proc) => vlc.audio.volume = vlcVolume = vol ts3query.sendtextmessage args.targetmode, invoker.id, "Volume set to #{vol}%." when "changenick" - nick = if paramline.length > params[0].length then paramline else params[0] + nick = paramline Sync () => try ts3query.clientupdate.sync ts3query, { client_nickname: nick } From 9929807cd35653fffb5038c7f8ca097c7eb4da08 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 00:07:25 +0100 Subject: [PATCH 62/71] Proper error message for nicknames that fail TS3Client checks. --- app.iced | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app.iced b/app.iced index bbf599f..e23010d 100644 --- a/app.iced +++ b/app.iced @@ -328,8 +328,11 @@ ts3clientService.on "started", (ts3proc) => try ts3query.clientupdate.sync ts3query, { client_nickname: nick } catch err - ts3query.sendtextmessage args.targetmode, invoker.id, "That unfortunately didn't work out." 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 From 7077c8425de69f5fd9af52c1f446e1daa999c483 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 00:17:29 +0100 Subject: [PATCH 63/71] Do not handle messages that don't have a proper text. Fixes #34. --- app.iced | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app.iced b/app.iced index e23010d..4d4f4a4 100644 --- a/app.iced +++ b/app.iced @@ -154,6 +154,9 @@ ts3clientService.on "started", (ts3proc) => 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 From b71c2be88ff76445d2859047dfbdd5c117e74523 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 00:32:07 +0100 Subject: [PATCH 64/71] Fix NaN check for "vol" command. --- app.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.iced b/app.iced index 4d4f4a4..af3d2ba 100644 --- a/app.iced +++ b/app.iced @@ -319,7 +319,7 @@ ts3clientService.on "started", (ts3proc) => vol = parseInt input - if paramline.trim().length <= 0 or vol == NaN or vol == null or vol > 200 or vol < 0 + if paramline.trim().length <= 0 or isNaN(vol) or vol > 200 or vol < 0 ts3query.sendtextmessage args.targetmode, invoker.id, "[B]vol [/B] - takes a number between 0 (0%) and 200 (200%) to set the volume. 100% is 100. Defaults to 50 (50%) on startup." return From 38c248e6433b0328efcb07ca0639490d5ad6a45f Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 02:49:31 +0100 Subject: [PATCH 65/71] Bumping version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f31387..8d3fe8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ts3bot", - "version": "0.2.1", + "version": "0.3.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.", "main": "app.js", "keywords": [ From 1b1f4c0f373f25f322d458c2ac35999cadb1bff4 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 03:02:03 +0100 Subject: [PATCH 66/71] Rewrite vlc.onPlaying handler, fixes #35. --- app.iced | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/app.iced b/app.iced index af3d2ba..3e6c834 100644 --- a/app.iced +++ b/app.iced @@ -78,12 +78,21 @@ ts3clientService.on "started", (ts3proc) => # VLC event handling vlc.onPlaying = () => - # Restore audio volume - vlc.audio.volume = vlcVolume + 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 - # TODO: Check why info is sometimes null, something must be wrong with the "add"/"play" commands here! - info = vlcMediaInfo[vlc.playlist.items[vlc.playlist.currentItem].mrl] - ts3query?.sendtextmessage 2, 0, "Now playing [URL=#{info?.originalUrl or vlc.playlist.items[vlc.playlist.currentItem].mrl}]#{info?.title or vlc.playlist.items[vlc.playlist.currentItem].mrl}[/URL]." vlc.onPaused = () => ts3query?.sendtextmessage 2, 0, "Paused." vlc.onForward = () => ts3query?.sendtextmessage 2, 0, "Fast-forwarding..." vlc.onBackward = () => ts3query?.sendtextmessage 2, 0, "Rewinding..." From 003ec4876352cf70f471d286543a88a3210df7a3 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 03:02:57 +0100 Subject: [PATCH 67/71] Provide "ts3bot" global executable. --- app.js | 2 ++ package.json | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app.js b/app.js index 8e7cd8f..26af737 100644 --- a/app.js +++ b/app.js @@ -1,3 +1,5 @@ +#!/usr/bin/env node + require("iced-coffee-script/register"); Sync = require("sync"); diff --git a/package.json b/package.json index 8d3fe8a..f2fba59 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "0.3.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.", "main": "app.js", + "bin": { + "ts3bot": "app.js" + }, "keywords": [ "teamspeak", "teamspeak3", From e8da814e5946996a8f2670fc5575c89b3a2bd42f Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 18:40:49 +0100 Subject: [PATCH 68/71] Implement "current" command, see #7. --- app.iced | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app.iced b/app.iced index 3e6c834..94c4f4c 100644 --- a/app.iced +++ b/app.iced @@ -186,6 +186,15 @@ ts3clientService.on "started", (ts3proc) => params = [] switch name.toLowerCase() + when "current" + 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, "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() From ed9e3605a76c225c59d950d2b4f7d7df0ef93d39 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 19:04:23 +0100 Subject: [PATCH 69/71] Validate "nickname" configuration option. Fixes #14. --- config.iced | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config.iced b/config.iced index d4bf2de..92f9554 100644 --- a/config.iced +++ b/config.iced @@ -28,6 +28,9 @@ nconf.defaults if not nconf.get("ts3-server") throw new Error "You need to provide a TeamSpeak3 server URL (starts with ts3server:// and can be generated from any TS3 client GUI)." +if nconf.get("nickname")? and (nconf.get("nickname").length < 3 or nconf.get("nickname").length > 30 + throw new Error "Nickname must be between 3 and 30 characters long." + console.log "Configuration loaded." if nconf.get "dump-config" From e93b3f5fff953176ab26b9da360fbddea8c33721 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 19:28:32 +0100 Subject: [PATCH 70/71] Missing newline in config.iced. --- config.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.iced b/config.iced index 92f9554..db31045 100644 --- a/config.iced +++ b/config.iced @@ -38,4 +38,4 @@ if nconf.get "dump-config" process.exit 0 module.exports = merge true, nconf, - isProduction: -> @get("environment").toUpperCase() == "PRODUCTION" \ No newline at end of file + isProduction: -> @get("environment").toUpperCase() == "PRODUCTION" From 95fe3249cc0858b2fff2c6c7843b1320a1bdc846 Mon Sep 17 00:00:00 2001 From: icedream Date: Thu, 5 Nov 2015 19:41:48 +0100 Subject: [PATCH 71/71] Missing ) in config.iced. --- config.iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.iced b/config.iced index db31045..5cddbfd 100644 --- a/config.iced +++ b/config.iced @@ -28,7 +28,7 @@ nconf.defaults if not nconf.get("ts3-server") throw new Error "You need to provide a TeamSpeak3 server URL (starts with ts3server:// and can be generated from any TS3 client GUI)." -if nconf.get("nickname")? and (nconf.get("nickname").length < 3 or nconf.get("nickname").length > 30 +if nconf.get("nickname")? and (nconf.get("nickname").length < 3 or nconf.get("nickname").length > 30) throw new Error "Nickname must be between 3 and 30 characters long." console.log "Configuration loaded."