From 2a352645f30262347c6d7fd58f5cba56cf64a251 Mon Sep 17 00:00:00 2001 From: Krateng Date: Tue, 27 Nov 2018 16:08:14 +0100 Subject: [PATCH] Added basic scrobbler for Plex Web on Chromium/Vivaldi --- database.py | 12 +++ scrobbler-vivaldi-plex/background.js | 133 ++++++++++++++++++++++++ scrobbler-vivaldi-plex/contentScript.js | 42 ++++++++ scrobbler-vivaldi-plex/icon128.png | Bin 0 -> 980 bytes scrobbler-vivaldi-plex/icon256.png | Bin 0 -> 1804 bytes scrobbler-vivaldi-plex/icon48.png | Bin 0 -> 493 bytes scrobbler-vivaldi-plex/manifest.json | 48 +++++++++ server.py | 28 +++-- 8 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 scrobbler-vivaldi-plex/background.js create mode 100644 scrobbler-vivaldi-plex/contentScript.js create mode 100644 scrobbler-vivaldi-plex/icon128.png create mode 100644 scrobbler-vivaldi-plex/icon256.png create mode 100644 scrobbler-vivaldi-plex/icon48.png create mode 100644 scrobbler-vivaldi-plex/manifest.json diff --git a/database.py b/database.py index e03499c..eb5f723 100644 --- a/database.py +++ b/database.py @@ -4,6 +4,7 @@ import waitress import os import datetime import cleanup +import sys SCROBBLES = [] # Format: tuple(track_ref,timestamp,saved) @@ -114,7 +115,18 @@ def post_scrobble(): (artists,title) = cleanup.fullclean(artists,title) time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp()) + ## this is necessary for localhost testing + response.set_header("Access-Control-Allow-Origin","*") + createScrobble(artists,title,time) + + return "" + +@route("/flush") +def abouttoshutdown(): + flush() + print("Database saved to disk.") + #sys.exit() # Starts the server def runserver(DATABASE_PORT): diff --git a/scrobbler-vivaldi-plex/background.js b/scrobbler-vivaldi-plex/background.js new file mode 100644 index 0000000..43f368f --- /dev/null +++ b/scrobbler-vivaldi-plex/background.js @@ -0,0 +1,133 @@ + + +chrome.tabs.onUpdated.addListener(onTabUpdated); +chrome.tabs.onRemoved.addListener(onTabRemoved); +chrome.tabs.onActivated.addListener(onTabChanged); +chrome.runtime.onMessage.addListener(onPlaybackUpdate); + +function onTabUpdated(tabId, changeInfo, tab) { + chrome.tabs.get(tabId,party) +} + +function onTabRemoved() { + +} + +function onTabChanged(activeInfo) { +} + + +function party(tab) { + + var patterns = [ + "https://app.plex.tv", + "http://app.plex.tv", + "https://plex.", + "http://plex." + ]; + + importantPage = false + + for (var i=0;i currentLength/2 && !alreadyScrobbled) { + console.log("Enough to scrobble: " + currentArtist + " - " + currentTitle) + scrobble(currentArtist,currentTitle) + + } + else if (alreadyScrobbled) { + console.log("We already scrobbled this track tho.") + alreadyScrobbled = false + } + + + console.log("But now, new track!") + d = new Date() + t = Math.floor(d.getTime()/1000) + lastUpdate = t + alreadyPlayed = 0 + currentTitle = title + currentArtist = artist + currentLength = seconds + console.log(artist + " - " + title + " is playing!") + currentlyPlaying = true + } +} + +function stopPlayback() { + currentlyPlaying = false + console.log("Playback stopped!") + d = new Date() + t = Math.floor(d.getTime()/1000) + delta = t - lastUpdate + console.log("Since the last update, " + delta + " seconds of music have been played") + alreadyPlayed = alreadyPlayed + delta + console.log(alreadyPlayed + " seconds of this track have been played overall") + if ((alreadyPlayed > currentLength/2) && !alreadyScrobbled) { + console.log("Enough to scrobble: " + currentArtist + " - " + currentTitle) + scrobble(currentArtist,currentTitle) + alreadyScrobbled = true + } +} + +function scrobble(artist,title) { + artiststring = encodeURIComponent(artist) + titlestring = encodeURIComponent(title) + var xhttp = new XMLHttpRequest(); + xhttp.open("GET","http://localhost:12345/db/newscrobble?artist=" + artiststring + "&title=" + titlestring,true); + xhttp.send() +} + +/// i have to add content scripts to the specific tab and then send messages to the extension diff --git a/scrobbler-vivaldi-plex/contentScript.js b/scrobbler-vivaldi-plex/contentScript.js new file mode 100644 index 0000000..a9fb50a --- /dev/null +++ b/scrobbler-vivaldi-plex/contentScript.js @@ -0,0 +1,42 @@ +//activeLibrary = document.querySelector("[data-qa-id*=sidebarLibrariesList]").querySelector("[class*=Link-isSelected]") + +//currentArtist = "" +//currentTitle = "" +//alreadyPlayed = 0 +//maxLength = 0 +//lastUpdate = 0 + + + +bar = document.querySelector("div[class*=PlayerControls]") +if (bar == null) { + console.log("Nothing playing right now!") + chrome.runtime.sendMessage({type:"stopPlayback"}) + exit() +} + +metadata = bar.querySelector("div[class*=PlayerControlsMetadata-container]") + +title = metadata.querySelector("a[class*=MetadataPosterTitle-singleLineTitle]").getAttribute("title") +artist = metadata.querySelector("span[class*=MetadataPosterTitle-title] > a:nth-child(1)").getAttribute("title") +duration = metadata.querySelector("[data-qa-id=mediaDuration]").innerHTML.split("/")[1] +durationSeconds = parseInt(duration.split(":")[0]) * 60 + parseInt(duration.split(":")[1]) + +control = bar.querySelector("div[class*=PlayerControls-buttonGroupCenter] > button:nth-child(2)").getAttribute("title") +if (control == "Play") { + console.log("Not playing right now") + chrome.runtime.sendMessage({type:"stopPlayback"}) + //stopPlayback() +} +else if (control == "Pause") { + console.log("Playing " + artist + " - " + title) + chrome.runtime.sendMessage({type:"startPlayback",artist:artist,title:title,duration:durationSeconds}) + //startPlayback(artist,title,durationSeconds) +} + + + + + + + diff --git a/scrobbler-vivaldi-plex/icon128.png b/scrobbler-vivaldi-plex/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..949d7afc957193bcac339b7b3ec5696e8d2b2d15 GIT binary patch literal 980 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 literal 0 HcmV?d00001 diff --git a/scrobbler-vivaldi-plex/icon48.png b/scrobbler-vivaldi-plex/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..c2da2d6ad3d681dc492b95a509a2607de4d0140f GIT binary patch literal 493 zcmVWUA>I*( z^IWX5Z;MY^$IVp07_|#rfeqfCqdCo)66Ke?n&;YAx^pZ{F$HV9USRek>uH{|G{-sE z;oTaKvKH~j8Z;7;grrZLzNgPBn-6%g^Vp2^6oW@}$^BD8546nd4Z>sEvNv6~IQ`Yw~K8QEd_*Lf8JiB5A9&-O1pP@@9jW2R< zd$2jXd`RP!92*BY6cDqWz9&On=2!8&jr=(f3Mg7AplF#|TAzMr&@#99k~Kc^@;JzH zJyd>Iog>FM(W3%A;v(je*(GlFhAw*7xsx?lAuCWK+C%h%_%2JWq5IKDC{vr@^caVt jf1ncvK@bE%FtUCEF4LXmUYNs-00000NkvXXu0mjf&_Uh* literal 0 HcmV?d00001 diff --git a/scrobbler-vivaldi-plex/manifest.json b/scrobbler-vivaldi-plex/manifest.json new file mode 100644 index 0000000..b217284 --- /dev/null +++ b/scrobbler-vivaldi-plex/manifest.json @@ -0,0 +1,48 @@ +{ + "name": "Plexoja", + "version": "0.1", + "description": "Scrobbles tracks from Plex Web to your Maloja server", + "manifest_version": 2, + "permissions": ["activeTab", "declarativeContent","tabs","http://app.plex.tv/*"], + "background": + { + "scripts": + [ + "background.js" + ] + }, + "content_scripts": + [ + { + "matches": + [ + "http://app.plex.tv/*", + "https://app.plex.tv/*" + ], + "include_globs": + [ + "http://plex.*", + "https://plex.*" + ], + "js": + [ + "contentScript.js" + ] + } + + ], + + "browser_action": + { + "default_icon": + { + "128":"icon128.png", + "48":"icon48.png" + } + }, + "icons": + { + "128":"icon128.png", + "48":"icon48.png" + } +} diff --git a/server.py b/server.py index be4d100..55bd736 100755 --- a/server.py +++ b/server.py @@ -1,7 +1,5 @@ from bottle import route, run, template, static_file, request, response -#import os from importlib.machinery import SourceFileLoader -#from serverutil import log, db_remove, createVideoFile import _thread import waitress import urllib.request @@ -11,18 +9,6 @@ import urllib.parse MAIN_PORT = 12345 DATABASE_PORT = 12349 -#@route("//") -#@route("//") -#@route("//") -#@route("//") -#@route("//") -#@route("//") -#@route("//") -#@route("/") -def static(pth): - - return static_file(pth,root="") - @route("") @route("/") @@ -42,13 +28,25 @@ def database(pth): keystring += urllib.parse.quote(k) + "=" + urllib.parse.quote(keys[k]) + "&" contents = urllib.request.urlopen("http://localhost:" + str(DATABASE_PORT) + "/" + pth + keystring).read() response.content_type = "application/json" + response.set_header("Access-Control-Allow-Origin","*") #print("Returning " + "http://localhost:" + str(DATABASE_PORT) + "/" + pth) return contents +@route("/exit") +def shutdown(): + urllib.request.urlopen("http://localhost:" + str(DATABASE_PORT) + "/flush") + print("Server shutting down...") + sys.exit() + + +@route("/") +def static(pth): + + return static_file(pth,root="") -## other programs to always run with the server +## start database server _thread.start_new_thread(SourceFileLoader("database","database.py").load_module().runserver,(DATABASE_PORT,)) run(host='0.0.0.0', port=MAIN_PORT, server='waitress')