1
0
mirror of https://github.com/krateng/maloja.git synced 2023-08-10 21:12:55 +03:00

Moved extra paackages

This commit is contained in:
krateng
2021-12-24 07:06:15 +01:00
parent 73a6c18b17
commit 5651626c39
19 changed files with 28 additions and 22 deletions

Binary file not shown.

View File

@@ -0,0 +1,327 @@
chrome.tabs.onUpdated.addListener(onTabUpdated);
chrome.tabs.onRemoved.addListener(onTabRemoved);
//chrome.tabs.onActivated.addListener(onTabChanged);
chrome.runtime.onMessage.addListener(onInternalMessage);
tabManagers = {}
pages = {
"Plex Web":{
"patterns":[
"https://app.plex.tv",
"http://app.plex.tv",
"https://plex.",
"http://plex."
],
"script":"plex.js"
},
"YouTube Music":{
"patterns":[
"https://music.youtube.com"
],
"script":"ytmusic.js"
},
"Spotify Web":{
"patterns":[
"https://open.spotify.com"
],
"script":"spotify.js"
},
"Bandcamp":{
"patterns":[
"bandcamp.com"
],
"script":"bandcamp.js"
},
"Soundcloud":{
"patterns":[
"https://soundcloud.com"
],
"script":"soundcloud.js"
}
}
function updateTabNum() {
var amount = Object.keys(tabManagers).length;
chrome.browserAction.setBadgeText({"text":amount.toString()});
chrome.browserAction.setBadgeBackgroundColor({"color":"#333337"});
}
function onTabUpdated(tabId, changeInfo, tab) {
// still same page?
//console.log("Update to tab " + tabId + "!")
if (tabManagers.hasOwnProperty(tabId)) {
//console.log("Yes!")
page = tabManagers[tabId].page;
patterns = pages[page]["patterns"];
//console.log("Page was managed by a " + page + " manager")
for (var i=0;i<patterns.length;i++) {
if (tab.url.includes(patterns[i])) {
//console.log("Still on same page!")
tabManagers[tabId].update();
return
}
}
console.log("Page on tab " + tabId + " changed, removing old " + page + " manager!");
delete tabManagers[tabId];
}
//check if pattern matches
for (var key in pages) {
if (pages.hasOwnProperty(key)) {
patterns = pages[key]["patterns"];
for (var i=0;i<patterns.length;i++) {
if (tab.url.includes(patterns[i])) {
console.log("New page on tab " + tabId + " will be handled by new " + key + " manager!");
tabManagers[tabId] = new Controller(tabId,key);
updateTabNum();
return
//chrome.tabs.executeScript(tab.id,{"file":"sitescripts/" + pages[key]["script"]})
}
}
}
}
}
function onTabRemoved(tabId,removeInfo) {
if (tabManagers.hasOwnProperty(tabId)) {
page = tabManagers[tabId].page;
console.log("closed tab was " + page + ", now removing manager");
tabManagers[tabId].stopPlayback("",""); //in case we want to scrobble the playing track
delete tabManagers[tabId];
updateTabNum();
}
}
function onInternalMessage(request,sender) {
// message from settings menu
if (request.type == "query") {
answer = [];
for (tabId in tabManagers) {
manager = tabManagers[tabId]
if (manager.currentlyPlaying) {
answer.push([manager.page,manager.currentArtist,manager.currentTitle]);
}
else {
answer.push([manager.page,null]);
}
}
chrome.runtime.sendMessage({"type":"response","content":answer})
}
//message from content script
if (request.type == "startPlayback" || request.type == "stopPlayback") {
tabId = sender.tab.id
//console.log("Message was sent from tab id " + tabId)
if (tabManagers.hasOwnProperty(tabId)) {
//console.log("This is managed! Seems to be " + tabManagers[tabId].page)
tabManagers[tabId].playbackUpdate(request);
}
}
}
class Controller {
constructor(tabId,page) {
this.tabId = tabId;
this.page = page;
this.currentTitle;
this.currentArtist;
this.currentLength;
this.alreadyPlayed;
this.currentlyPlaying = false;
this.lastUpdate = 0;
this.alreadyScrobbled = false;
this.messageID = 0;
this.lastMessage = 0;
this.alreadyQueued = false;
// we reject update requests when we're already planning to run an update!
this.update();
}
// the tab has been updated, we need to run the script
update() {
if (this.alreadyQueued) {
}
else {
this.alreadyQueued = true;
setTimeout(() => { this.actuallyupdate(); },800);
//this.actuallyupdate();
}
}
actuallyupdate() {
this.messageID++;
//console.log("Update! Our page is " + this.page + ", our tab id " + this.tabId)
try {
chrome.tabs.executeScript(this.tabId,{"file":"sites/" + pages[this.page]["script"]});
chrome.tabs.executeScript(this.tabId,{"file":"sitescript.js"});
}
catch (e) {
console.log("Could not run site script. Tab probably closed or something idk.")
}
this.alreadyQueued = false;
}
// an actual update message from the script has arrived
playbackUpdate(request) {
if (request.time < self.lastMessage) {
console.log("Got message out of order, discarding!")
return
}
self.lastMessage = request.time
//console.log("Update message from our tab " + this.tabId + " (" + this.page + ")")
if (request.type == "stopPlayback" && this.currentlyPlaying) {
this.stopPlayback(request.artist,request.title);
}
else if (request.type == "startPlayback") {
this.startPlayback(request.artist,request.title,request.duration);
}
}
backlog_scrobble() {
while (this.alreadyPlayed > this.currentLength) {
this.alreadyPlayed = this.alreadyPlayed - this.currentLength
var secondsago = this.alreadyPlayed
scrobble(this.currentArtist,this.currentTitle,this.currentLength,secondsago)
}
}
startPlayback(artist,title,seconds) {
// CASE 1: Resuming playback of previously played title
if (artist == this.currentArtist && title == this.currentTitle && !this.currentlyPlaying) {
console.log("Resuming playback of " + this.currentTitle)
// Already played full song?
this.backlog_scrobble()
this.setUpdate()
this.currentlyPlaying = true
}
// CASE 2: New track is being played
else if (artist != this.currentArtist || title != this.currentTitle) {
//first inform ourselves that the previous track has now been stopped for good
this.stopPlayback(artist,title);
//then initialize new playback
console.log("New track");
this.setUpdate();
this.alreadyPlayed = 0;
this.currentTitle = title;
this.currentArtist = artist;
if (Number.isInteger(seconds)) {
this.currentLength = seconds;
}
else {
this.currentLength = 300;
// avoid excessive scrobbling when the selector breaks
}
console.log(artist + " - " + title + " is playing! (" + this.currentLength + " seconds)");
this.currentlyPlaying = true;
}
}
// the artist and title arguments are not attributes of the track being stopped, but of the track active now
// they are here to recognize whether the playback has been paused or completely ended / replaced
stopPlayback(artist,title) {
//CASE 1: Playback just paused OR CASE 2: Playback ended
if (this.currentlyPlaying) {
var d = this.setUpdate()
this.alreadyPlayed = this.alreadyPlayed + d
console.log(d + " seconds played since last update, " + this.alreadyPlayed + " seconds played overall")
}
// Already played full song?
this.backlog_scrobble()
this.currentlyPlaying = false
//ONLY CASE 2: Playback ended
if (artist != this.currentArtist || title != this.currentTitle) {
if (this.alreadyPlayed > this.currentLength / 2) {
scrobble(this.currentArtist,this.currentTitle,this.alreadyPlayed)
this.alreadyPlayed = 0
}
}
}
// sets last updated to now and returns how long since then
setUpdate() {
var d = new Date()
var t = Math.floor(d.getTime()/1000)
var delta = t - this.lastUpdate
this.lastUpdate = t
return delta
}
}
function scrobble(artist,title,seconds,secondsago=0) {
console.log("Scrobbling " + artist + " - " + title + "; " + seconds + " seconds playtime, " + secondsago + " seconds ago")
var d = new Date()
var time = Math.floor(d.getTime()/1000) - secondsago
//console.log("Time: " + time)
var payload = {
"artist":artist,
"title":title,
"duration":seconds,
"time":time
}
chrome.storage.local.get(["serverurl","apikey"],function(result) {
payload["key"] = result["apikey"];
var xhttp = new XMLHttpRequest();
xhttp.open("POST",result["serverurl"] + "/apis/mlj_1/newscrobble",true);
xhttp.setRequestHeader("Content-Type", "application/json");
//xhttp.send(requestbody + "&key=" + APIKEY)
var body = JSON.stringify(payload);
xhttp.send(body)
//console.log("Sent: " + body)
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,36 @@
{
"name": "Maloja Scrobbler",
"version": "1.7",
"description": "Scrobbles tracks from various sites to your Maloja server",
"manifest_version": 2,
"permissions": [
"tabs",
"storage",
"http://*/",
"https://*/"
],
"background":
{
"scripts":
[
"background.js"
]
},
"browser_action":
{
"default_icon":
{
"128":"icon128.png",
"48":"icon48.png"
},
"default_popup": "settings.html",
"default_title": "Maloja Scrobbler"
},
"icons":
{
"128":"icon128.png",
"48":"icon48.png"
}
}

View File

@@ -0,0 +1,42 @@
<!doctype html />
<html>
<head>
<title>Wat</title>
<script type="text/javascript" src="settings.js"></script>
<meta charset="UTF-8">
<style>
@import url('https://fonts.googleapis.com/css?family=Ubuntu');
body {
width:300px;
background-color:#333337;
color:beige;
font-family:'Ubuntu';
}
input {
width:270px;
font-family:'Ubuntu';
outline:none;
border: 0px solid;
padding:2px;
}
span {
line-height: 20px;
}
</style>
</head>
<body>
<div id="wat">
<span id="checkmark_url"></span> <span>Server:</span><br />
<input type="text" id="serverurl" />
<br /><br />
<span id="checkmark_key"></span> <span>API key:</span><br />
<input type="text" id="apikey" />
<br/><br/>
<span>Tabs:</span>
<list id="playinglist">
</list>
</div>
</body>
</html>

View File

@@ -0,0 +1,99 @@
var config_defaults = {
serverurl:"http://localhost:42010",
apikey:"BlackPinkInYourArea"
}
document.addEventListener("DOMContentLoaded",function() {
document.getElementById("serverurl").addEventListener("change",checkServer);
document.getElementById("apikey").addEventListener("change",checkServer);
document.getElementById("serverurl").addEventListener("focusout",checkServer);
document.getElementById("apikey").addEventListener("focusout",checkServer);
document.getElementById("serverurl").addEventListener("input",saveConfig);
document.getElementById("apikey").addEventListener("input",saveConfig);
chrome.runtime.onMessage.addListener(onInternalMessage);
chrome.storage.local.get(config_defaults,function(result){
for (var key in result) {
document.getElementById(key).value = result[key];
}
checkServer();
})
chrome.runtime.sendMessage({"type":"query"})
});
function onInternalMessage(request,sender) {
if (request.type == "response") {
players = request.content
html = "";
for (var i=0;i<players.length;i++) {
if (players[i][1]) {
html += "<li>" + players[i][0] + ": " + players[i][1] + " - " + players[i][2]
}
else {
html += "<li>" + players[i][0] + ": Playing nothing"
}
}
document.getElementById("playinglist").innerHTML = html;
}
}
function saveConfig() {
for (var key in config_defaults) {
var value = document.getElementById(key).value;
chrome.storage.local.set({ [key]: value });
}
}
function checkServer() {
url = document.getElementById("serverurl").value + "/api/test?key=" + document.getElementById("apikey").value
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = createCheckmarks;
try {
xhttp.open("GET",url,true);
xhttp.send();
}
catch (e) {
//document.getElementById("checkmark_url").innerHTML = "❌"
//document.getElementById("checkmark_key").innerHTML = "❌"
document.getElementById("serverurl").style.backgroundColor = "red"
document.getElementById("apikey").style.backgroundColor = "red"
}
}
function createCheckmarks() {
if (this.readyState == 4) {
if ((this.status >= 200) && (this.status < 300)) {
//document.getElementById("checkmark_url").innerHTML = "✔️"
//document.getElementById("checkmark_key").innerHTML = "✔️"
document.getElementById("serverurl").style.backgroundColor = "lawngreen"
document.getElementById("apikey").style.backgroundColor = "lawngreen"
}
else if (this.status == 403) {
//document.getElementById("checkmark_url").innerHTML = "✔️"
//document.getElementById("checkmark_key").innerHTML = "❌"
document.getElementById("serverurl").style.backgroundColor = "lawngreen"
document.getElementById("apikey").style.backgroundColor = "red"
}
else {
//document.getElementById("checkmark_url").innerHTML = "❌"
//document.getElementById("checkmark_key").innerHTML = "❌"
document.getElementById("serverurl").style.backgroundColor = "red"
document.getElementById("apikey").style.backgroundColor = "red"
}
}
}

View File

@@ -0,0 +1,15 @@
maloja_scrobbler_selector_playbar = "//div[contains(@class,'trackView')]"
maloja_scrobbler_selector_metadata = "."
// need to select everything as bar / metadata block because artist isn't shown in the inline player
maloja_scrobbler_selector_title = ".//span[@class='title']/text()"
maloja_scrobbler_selector_artist = ".//span[contains(@itemprop,'byArtist')]/a/text()"
maloja_scrobbler_selector_duration = ".//span[@class='time_total']/text()"
maloja_scrobbler_selector_control = ".//td[@class='play_cell']/a[@role='button']/div[contains(@class,'playbutton')]/@class"
maloja_scrobbler_label_playing = "playbutton playing"
maloja_scrobbler_label_paused = "playbutton"

View File

@@ -0,0 +1,11 @@
maloja_scrobbler_selector_playbar = "//div[contains(@class,'PlayerControls')]"
maloja_scrobbler_selector_metadata = ".//div[contains(@class,'PlayerControlsMetadata-container')]"
maloja_scrobbler_selector_title = ".//a[@data-qa-id='metadataTitleLink']/@title"
maloja_scrobbler_selector_artist = ".//span[contains(@class,'MetadataPosterTitle-title')]/a[1]/@title"
maloja_scrobbler_selector_duration = ".//button[@data-qa-id='mediaDuration']/text()[3]"
maloja_scrobbler_selector_control = ".//div[contains(@class,'PlayerControls-buttonGroupCenter')]/button[2]/@title"

View File

@@ -0,0 +1,14 @@
maloja_scrobbler_selector_playbar = "//div[contains(@class,'playControls')]"
maloja_scrobbler_selector_metadata = ".//div[contains(@class,'playControls__soundBadge')]//div[contains(@class,'playbackSoundBadge__titleContextContainer')]"
maloja_scrobbler_selector_title = ".//div/a/@title"
maloja_scrobbler_selector_artist = ".//a/text()"
maloja_scrobbler_selector_duration = ".//div[contains(@class,'playbackTimeline__duration')]//span[@aria-hidden='true']/text()"
maloja_scrobbler_selector_control = ".//button[contains(@class,'playControl')]/@title"
maloja_scrobbler_label_playing = "Pause current"
maloja_scrobbler_label_paused = "Play current"

View File

@@ -0,0 +1,12 @@
maloja_scrobbler_selector_playbar = "//div[@class='now-playing-bar']"
maloja_scrobbler_selector_metadata = ".//div[@class='now-playing-bar__left']"
maloja_scrobbler_selector_title = ".//a[@data-testid='nowplaying-track-link']/text()"
maloja_scrobbler_selector_artists = ".//a[contains(@href,'/artist/')]"
maloja_scrobbler_selector_artist = "./text()"
maloja_scrobbler_selector_duration = ".//div[@class='playback-bar']/div[3]/text()"
maloja_scrobbler_selector_control = ".//div[contains(@class,'player-controls__buttons')]/button[3]/@title"

View File

@@ -0,0 +1,13 @@
maloja_scrobbler_selector_playbar = "//ytmusic-player-bar"
maloja_scrobbler_selector_metadata = ".//div[contains(@class,'middle-controls')]/div[contains(@class,'content-info-wrapper')]"
maloja_scrobbler_selector_title = ".//yt-formatted-string[contains(@class,'title')]/@title"
maloja_scrobbler_selector_artists = ".//span/span[contains(@class,'subtitle')]/yt-formatted-string/a[position()<last()]"
maloja_scrobbler_selector_artist = "./text()"
maloja_scrobbler_selector_duration = ".//div[contains(@class,'left-controls')]/span[contains(@class,'time-info')]/text()"
duration_needs_split = true
maloja_scrobbler_selector_control = ".//div[contains(@class,'left-controls')]/div/paper-icon-button[contains(@class,'play-pause-button')]/@title"

View File

@@ -0,0 +1,91 @@
function getxpath(path,type) {
result = document.evaluate(path, this, null, type, null);
if (type == XPathResult.FIRST_ORDERED_NODE_TYPE) {
return result.singleNodeValue;
}
else if (type == XPathResult.ORDERED_NODE_ITERATOR_TYPE) {
resultarray = [];
while(node = result.iterateNext()) {
resultarray.push(node);
}
return resultarray;
}
else if (type == XPathResult.STRING_TYPE) {
return result.stringValue;
}
// if (path.split("/").slice(-1)[0].startsWith("text()") || path.split("/").slice(-1)[0].startsWith("@")) {
// result = document.evaluate(path, this, null, XPathResult.STRING_TYPE, null);
// return result.stringValue;
// }
// else {
// result = document.evaluate(path, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
// return result.singleNodeValue;
// }
}
Node.prototype.xpath = getxpath;
bar = document.xpath(maloja_scrobbler_selector_playbar, XPathResult.FIRST_ORDERED_NODE_TYPE);
if (bar == null) {
console.log("Nothing playing right now!");
chrome.runtime.sendMessage({type:"stopPlayback",time:Math.floor(Date.now()),artist:"",title:""});
}
else {
metadata = bar.xpath(maloja_scrobbler_selector_metadata, XPathResult.FIRST_ORDERED_NODE_TYPE);
duration = bar.xpath(maloja_scrobbler_selector_duration, XPathResult.STRING_TYPE);
duration = duration + '';
title = metadata.xpath(maloja_scrobbler_selector_title, XPathResult.STRING_TYPE);
if (typeof maloja_scrobbler_selector_artists !== "undefined") {
artistnodes = metadata.xpath(maloja_scrobbler_selector_artists, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
artists = artistnodes.map(x => x.xpath(maloja_scrobbler_selector_artist, XPathResult.STRING_TYPE));
artist = artists.join(";");
}
else {
artist = metadata.xpath(maloja_scrobbler_selector_artist, XPathResult.STRING_TYPE);
}
if (typeof duration_needs_split !== "undefined" && duration_needs_split) {
duration = duration.split("/").slice(-1)[0].trim();
}
if (duration.split(":").length == 2) {
durationSeconds = parseInt(duration.split(":")[0]) * 60 + parseInt(duration.split(":")[1]);
}
else {
durationSeconds = parseInt(duration.split(":")[0]) * 60 * 60 + parseInt(duration.split(":")[1]) * 60 + parseInt(duration.split(":")[2]);
}
control = bar.xpath(maloja_scrobbler_selector_control, XPathResult.STRING_TYPE);
try {
label_playing = maloja_scrobbler_label_playing
}
catch {
label_playing = "Pause"
}
try {
label_paused = maloja_scrobbler_label_paused
}
catch {
label_paused = "Play"
}
if (control == label_paused) {
console.log("Not playing right now");
chrome.runtime.sendMessage({type:"stopPlayback",time:Math.floor(Date.now()),artist:artist,title:title});
//stopPlayback()
}
else if (control == label_playing) {
console.log("Playing " + artist + " - " + title + " (" + durationSeconds + " sec)");
chrome.runtime.sendMessage({type:"startPlayback",time:Math.floor(Date.now()),artist:artist,title:title,duration:durationSeconds});
//startPlayback(artist,title,durationSeconds)
}
}

View File

@@ -0,0 +1,6 @@
convert ../../maloja/web/static/png/favicon_large.png -resize 256 icon256.png
convert ../../maloja/web/static/png/favicon_large.png -resize 128 icon128.png
convert ../../maloja/web/static/png/favicon_large.png -resize 48 icon48.png
convert ../../maloja/web/static/png/favicon_large.png -background none -resize 280 -gravity center -extent 440x280 -background "#232327" -flatten tile.png
rm ../maloja-scrobbler.zip
zip ../maloja-scrobbler.zip sites/* *.js *.json *.html icon*.png

View File

@@ -0,0 +1,12 @@
# maloja-lib
Library for Python music players to allow users to scrobble to [Maloja](https://github.com/krateng/maloja) servers.
```
from malojalib import MalojaInstance
instance = MalojaInstance(user_supplied_url,user_supplied_key)
instance.scrobble(artists=['K/DA','Howard Shore','Blackbeard's Tea Party],title='Grüezi Wohl Frau Stirnimaa')
```

View File

@@ -0,0 +1,27 @@
import requests
class MalojaInstance:
def __init__(self,base_url,key):
self.base_url = base_url
self.key = key
def test(self):
url = self.base_url + '/apis/mlj_1/test'
response = requests.get(url,{'key':self.key})
return (response.status_code == 200)
def scrobble(self,artists,title,timestamp=None,album=None,duration=None):
payload = {
'key':self.key,
'artists':artists,
'title':title,
'time':timestamp,
'album':album,
'duration':duration
}
url = self.base_url + '/apis/mlj_1/newscrobble'
response = requests.post(url,payload)
return response.json()

View File

@@ -0,0 +1,28 @@
[project]
name = "maloja-lib"
version = "1.0.0"
description = "Utilities to interact with Maloja servers"
readme = "./README.md"
requires-python = ">=3.6"
license = { file="../../LICENSE" }
authors = [ { name="Johannes Krattenmacher", email="maloja@dev.krateng.ch" } ]
urls.repository = "https://github.com/krateng/maloja"
urls.documentation = "https://github.com/krateng/maloja"
keywords = ["scrobbling", "music", "library", "api"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent"
]
dependencies = [
"requests"
]
[project.scripts]
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"