From 7c6241c2037cd7b74b177da58e1b68a778c546e5 Mon Sep 17 00:00:00 2001 From: girst Date: Tue, 13 Oct 2020 14:36:58 +0200 Subject: [PATCH 1/3] Allow reverse-proxying Try to use the quasi-standard X-Forwarded-For header to get the IP address of the client, in case we are being reverse proxied. This is technically spoofable, but given that the client has access to the Mopidy WebSocket, they can much easier skip tracks this way: mopidy = new Mopidy(); /*wait 1sec*/ mopidy.playback.next(); --- mopidy_party/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mopidy_party/__init__.py b/mopidy_party/__init__.py index b68c745..9016e41 100644 --- a/mopidy_party/__init__.py +++ b/mopidy_party/__init__.py @@ -14,6 +14,9 @@ def initialize(self, core, data, config): self.data = data self.requiredVotes = config["party"]["votes_to_skip"] + def _getip(self): + return self.request.headers.get("X-Forwarded-For", self.request.remote_ip) + def get(self): currentTrack = self.core.playback.get_current_track().get() if (currentTrack == None): return @@ -24,10 +27,10 @@ def get(self): self.data["track"] = currentTrackURI self.data["votes"] = [] - if (self.request.remote_ip in self.data["votes"]): # User has already voted + if (self._getip() in self.data["votes"]): # User has already voted self.write("You have already voted to skip this song =)") else: # Valid vote - self.data["votes"].append(self.request.remote_ip) + self.data["votes"].append(self._getip()) if (len(self.data["votes"]) == self.requiredVotes): self.core.playback.next() self.write("Skipping...") From 88a8adb738c5bc72042fc7848387c689119462ac Mon Sep 17 00:00:00 2001 From: girst Date: Tue, 13 Oct 2020 15:27:33 +0200 Subject: [PATCH 2/3] Limit how many tracks a user can add back-to-back --- README.rst | 1 + mopidy_party/__init__.py | 43 ++++++++++++++++++++++++++++--- mopidy_party/ext.conf | 1 + mopidy_party/static/controller.js | 36 +++++++------------------- 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/README.rst b/README.rst index f9dc87d..b19bb76 100644 --- a/README.rst +++ b/README.rst @@ -39,6 +39,7 @@ Configuration [party] enabled = true votes_to_skip = 3 + max_tracks = 0 Project resources ================= diff --git a/mopidy_party/__init__.py b/mopidy_party/__init__.py index 9016e41..2316a19 100644 --- a/mopidy_party/__init__.py +++ b/mopidy_party/__init__.py @@ -7,7 +7,7 @@ __version__ = '1.0.0' -class PartyRequestHandler(tornado.web.RequestHandler): +class VoteRequestHandler(tornado.web.RequestHandler): def initialize(self, core, data, config): self.core = core @@ -38,11 +38,47 @@ def get(self): self.write("You have voted to skip this song. ("+str(self.requiredVotes-len(self.data["votes"]))+" more votes needed)") +class AddRequestHandler(tornado.web.RequestHandler): + + def initialize(self, core, data, config): + self.core = core + self.data = data + + def _getip(self): + return self.request.headers.get("X-Forwarded-For", self.request.remote_ip) + + def post(self): + # when the last n tracks were added by the same user, abort. + if self.data["queue"] and all([e == self._getip() for e in self.data["queue"]]): + self.write("You have requested too many songs") + self.set_status(403) + return + + track_uri = self.request.body.decode() + if not track_uri: + self.set_status(400) + return + + self.data["queue"].append(self._getip()) + self.data["queue"].pop(0) + + try: + self.core.tracklist.add(uris=[track_uri]) + except: + self.write("Unable to add track, please try again...") + self.set_status(400) + return + + self.core.tracklist.set_consume(True) + if self.core.playback.get_state().get() == "stopped": + self.core.playback.play() + def party_factory(config, core): - data = {'track':"", 'votes':[]} + data = {'track':"", 'votes':[], 'queue': [None] * config["party"]["max_tracks"]} return [ - ('/vote', PartyRequestHandler, {'core': core, 'data':data, 'config':config}) + ('/vote', VoteRequestHandler, {'core': core, 'data':data, 'config':config}), + ('/add', AddRequestHandler, {'core': core, 'data':data, 'config':config}) ] @@ -59,6 +95,7 @@ def get_default_config(self): def get_config_schema(self): schema = super(Extension, self).get_config_schema() schema['votes_to_skip'] = config.Integer(minimum=0) + schema['max_tracks'] = config.Integer(minimum=0) return schema def setup(self, registry): diff --git a/mopidy_party/ext.conf b/mopidy_party/ext.conf index 675bff8..1fbb822 100644 --- a/mopidy_party/ext.conf +++ b/mopidy_party/ext.conf @@ -1,3 +1,4 @@ [party] enabled = true votes_to_skip = 3 +max_tracks = 0 diff --git a/mopidy_party/static/controller.js b/mopidy_party/static/controller.js index e73075a..614d289 100644 --- a/mopidy_party/static/controller.js +++ b/mopidy_party/static/controller.js @@ -182,33 +182,17 @@ angular.module('partyApp', []) track.disabled = true; - mopidy.tracklist - .index() - .then(function(index){ - return mopidy.tracklist.add({uris: [track.uri]}); - }) - .then(function(){ - // Notify user - $scope.message = ['success', 'Next track: ' + track.name]; - $scope.$apply(); - return mopidy.tracklist.setConsume([true]); - }) - .then(function(){ - return mopidy.playback.getState(); - }) - .then(function(state){ - // Get current state - if(state !== 'stopped') - return; - // If stopped, start music NOW! - return mopidy.playback.play(); - }) - .catch(function(){ + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open( "POST", "/party/add", false ); // false for synchronous request + xmlHttp.send(track.uri); + var msgtype = 'success' + if (xmlHttp.status >= 400) { track.disabled = false; - $scope.message = ['error', 'Unable to add track, please try again...']; - $scope.$apply(); - }) - .done(); + $scope.message = ['error', xmlHttp.responseText]; + } else { + $scope.message = ['success', 'Next track: ' + track.name]; + } + $scope.$apply(); }; $scope.nextTrack = function(){ From 79a05390513d6a0b5f8812827672e4867ebe604b Mon Sep 17 00:00:00 2001 From: girst Date: Tue, 13 Oct 2020 20:27:07 +0200 Subject: [PATCH 3/3] allow enqueueing tracks outside of mopidy-party to be used as fallback appending tracks e.g. via mpd will have lower precedence and be used when user-submitted songs have ran out. new user tracks are inserted before fallback tracks. --- mopidy_party/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mopidy_party/__init__.py b/mopidy_party/__init__.py index 2316a19..cc55a94 100644 --- a/mopidy_party/__init__.py +++ b/mopidy_party/__init__.py @@ -62,8 +62,14 @@ def post(self): self.data["queue"].append(self._getip()) self.data["queue"].pop(0) + pos = 0 + if self.data["last"]: + queue = self.core.tracklist.index(self.data["last"]).get() or 0 + current = self.core.tracklist.index().get() or 0 + pos = max(queue, current) # after lastly enqueued and after current track + try: - self.core.tracklist.add(uris=[track_uri]) + self.data["last"] = self.core.tracklist.add(uris=[track_uri], at_position=pos+1).get()[0] except: self.write("Unable to add track, please try again...") self.set_status(400) @@ -75,7 +81,7 @@ def post(self): def party_factory(config, core): - data = {'track':"", 'votes':[], 'queue': [None] * config["party"]["max_tracks"]} + data = {'track':"", 'votes':[], 'queue': [None] * config["party"]["max_tracks"], 'last':None} return [ ('/vote', VoteRequestHandler, {'core': core, 'data':data, 'config':config}), ('/add', AddRequestHandler, {'core': core, 'data':data, 'config':config})