mirror of
https://github.com/krateng/maloja.git
synced 2023-08-10 21:12:55 +03:00
Added basic scrobbler for Plex Web on Chromium/Vivaldi
This commit is contained in:
parent
bdf114d7fe
commit
2a352645f3
12
database.py
12
database.py
@ -4,6 +4,7 @@ import waitress
|
|||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
import cleanup
|
import cleanup
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
SCROBBLES = [] # Format: tuple(track_ref,timestamp,saved)
|
SCROBBLES = [] # Format: tuple(track_ref,timestamp,saved)
|
||||||
@ -114,8 +115,19 @@ def post_scrobble():
|
|||||||
(artists,title) = cleanup.fullclean(artists,title)
|
(artists,title) = cleanup.fullclean(artists,title)
|
||||||
time = int(datetime.datetime.now(tz=datetime.timezone.utc).timestamp())
|
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)
|
createScrobble(artists,title,time)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
@route("/flush")
|
||||||
|
def abouttoshutdown():
|
||||||
|
flush()
|
||||||
|
print("Database saved to disk.")
|
||||||
|
#sys.exit()
|
||||||
|
|
||||||
# Starts the server
|
# Starts the server
|
||||||
def runserver(DATABASE_PORT):
|
def runserver(DATABASE_PORT):
|
||||||
|
|
||||||
|
133
scrobbler-vivaldi-plex/background.js
Normal file
133
scrobbler-vivaldi-plex/background.js
Normal file
@ -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<patterns.length;i++) {
|
||||||
|
if (tab.url.startsWith(patterns[i])) {
|
||||||
|
importantPage = true
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importantPage) {
|
||||||
|
chrome.tabs.executeScript(tab.id,{"file":"contentScript.js"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPlaybackUpdate(request,sender) {
|
||||||
|
//console.log("Got update from Plex Web!")
|
||||||
|
if (request.type == "stopPlayback" && currentlyPlaying) {
|
||||||
|
stopPlayback();
|
||||||
|
}
|
||||||
|
else if (request.type == "startPlayback") {
|
||||||
|
startPlayback(request.artist,request.title,request.duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentTitle;
|
||||||
|
var currentArtist;
|
||||||
|
var currentLength;
|
||||||
|
var alreadyPlayed;
|
||||||
|
var currentlyPlaying = false;
|
||||||
|
var lastUpdate = 0;
|
||||||
|
var alreadyScrobbled = false;
|
||||||
|
|
||||||
|
|
||||||
|
function startPlayback(artist,title,seconds) {
|
||||||
|
|
||||||
|
console.log("Playback started!")
|
||||||
|
if (artist == currentArtist && title == currentTitle && !currentlyPlaying) {
|
||||||
|
console.log("Still previous track!")
|
||||||
|
d = new Date()
|
||||||
|
t = Math.floor(d.getTime()/1000)
|
||||||
|
lastUpdate = t
|
||||||
|
currentlyPlaying = true
|
||||||
|
}
|
||||||
|
else if (artist != currentArtist || title != currentTitle) {
|
||||||
|
console.log("New track!")
|
||||||
|
if (currentlyPlaying) {
|
||||||
|
console.log("We were playing another track before, so let's check if we should scrobble that.")
|
||||||
|
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("The previous track was played for " + alreadyPlayed + " seconds, that's " + Math.floor(alreadyPlayed/currentLength * 100) + "% of its length.")
|
||||||
|
if (alreadyPlayed > 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
|
42
scrobbler-vivaldi-plex/contentScript.js
Normal file
42
scrobbler-vivaldi-plex/contentScript.js
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
scrobbler-vivaldi-plex/icon128.png
Normal file
BIN
scrobbler-vivaldi-plex/icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 980 B |
BIN
scrobbler-vivaldi-plex/icon256.png
Normal file
BIN
scrobbler-vivaldi-plex/icon256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
scrobbler-vivaldi-plex/icon48.png
Normal file
BIN
scrobbler-vivaldi-plex/icon48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 493 B |
48
scrobbler-vivaldi-plex/manifest.json
Normal file
48
scrobbler-vivaldi-plex/manifest.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
28
server.py
28
server.py
@ -1,7 +1,5 @@
|
|||||||
from bottle import route, run, template, static_file, request, response
|
from bottle import route, run, template, static_file, request, response
|
||||||
#import os
|
|
||||||
from importlib.machinery import SourceFileLoader
|
from importlib.machinery import SourceFileLoader
|
||||||
#from serverutil import log, db_remove, createVideoFile
|
|
||||||
import _thread
|
import _thread
|
||||||
import waitress
|
import waitress
|
||||||
import urllib.request
|
import urllib.request
|
||||||
@ -11,18 +9,6 @@ import urllib.parse
|
|||||||
MAIN_PORT = 12345
|
MAIN_PORT = 12345
|
||||||
DATABASE_PORT = 12349
|
DATABASE_PORT = 12349
|
||||||
|
|
||||||
#@route("/<pth:path>/<file:re:.*\\.html>")
|
|
||||||
#@route("/<pth:path>/<file:re:.*\\.css>")
|
|
||||||
#@route("/<pth:path>/<file:re:.*\\.js>")
|
|
||||||
#@route("/<pth:path>/<file:re:.*\\.jpg>")
|
|
||||||
#@route("/<pth:path>/<file:re:.*\\.png>")
|
|
||||||
#@route("/<pth:path>/<file:re:.*\\.mp4>")
|
|
||||||
#@route("/<pth:path>/<file:re:.*\\.mkv>")
|
|
||||||
#@route("/<pth:path>")
|
|
||||||
def static(pth):
|
|
||||||
|
|
||||||
return static_file(pth,root="")
|
|
||||||
|
|
||||||
|
|
||||||
@route("")
|
@route("")
|
||||||
@route("/")
|
@route("/")
|
||||||
@ -42,13 +28,25 @@ def database(pth):
|
|||||||
keystring += urllib.parse.quote(k) + "=" + urllib.parse.quote(keys[k]) + "&"
|
keystring += urllib.parse.quote(k) + "=" + urllib.parse.quote(keys[k]) + "&"
|
||||||
contents = urllib.request.urlopen("http://localhost:" + str(DATABASE_PORT) + "/" + pth + keystring).read()
|
contents = urllib.request.urlopen("http://localhost:" + str(DATABASE_PORT) + "/" + pth + keystring).read()
|
||||||
response.content_type = "application/json"
|
response.content_type = "application/json"
|
||||||
|
response.set_header("Access-Control-Allow-Origin","*")
|
||||||
#print("Returning " + "http://localhost:" + str(DATABASE_PORT) + "/" + pth)
|
#print("Returning " + "http://localhost:" + str(DATABASE_PORT) + "/" + pth)
|
||||||
return contents
|
return contents
|
||||||
|
|
||||||
|
@route("/exit")
|
||||||
|
def shutdown():
|
||||||
|
urllib.request.urlopen("http://localhost:" + str(DATABASE_PORT) + "/flush")
|
||||||
|
print("Server shutting down...")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
@route("/<pth:path>")
|
||||||
|
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,))
|
_thread.start_new_thread(SourceFileLoader("database","database.py").load_module().runserver,(DATABASE_PORT,))
|
||||||
|
|
||||||
run(host='0.0.0.0', port=MAIN_PORT, server='waitress')
|
run(host='0.0.0.0', port=MAIN_PORT, server='waitress')
|
||||||
|
Loading…
Reference in New Issue
Block a user