From 075cd4bcdeeeb7a89aac30c8790a5b2a66db8476 Mon Sep 17 00:00:00 2001 From: JoshuaDoes Date: Sun, 12 May 2024 07:26:23 -0400 Subject: [PATCH] Status update as of September 2023 --- css/audioplayer.css | 0 css/libremedia.css | 2 +- go.mod | 7 ++ go.sum | 18 ++++ index.html | 17 ++-- js/libremedia/libremedia-artwork.js | 8 +- js/libremedia/libremedia-downloader.js | 20 +++-- js/libremedia/libremedia-navigation.js | 1 + js/navigo-8.11.1.min.js | 2 + main.go | 25 ++++-- objects.go | 112 ++++++++++++++----------- service.go | 36 +++++--- tidal.go | 13 ++- 13 files changed, 168 insertions(+), 93 deletions(-) delete mode 100644 css/audioplayer.css create mode 100644 js/navigo-8.11.1.min.js diff --git a/css/audioplayer.css b/css/audioplayer.css deleted file mode 100644 index e69de29..0000000 diff --git a/css/libremedia.css b/css/libremedia.css index b4b6d42..fd5365e 100644 --- a/css/libremedia.css +++ b/css/libremedia.css @@ -310,4 +310,4 @@ button { button:focus { outline: none; -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index d59db46..f28c7a2 100644 --- a/go.mod +++ b/go.mod @@ -12,17 +12,24 @@ require ( ) require ( + github.com/CAFxX/httpcompression v0.0.8 // indirect + github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/PuerkitoBio/goquery v1.8.1 // indirect + github.com/andybalholm/brotli v1.0.4 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect github.com/badfortrains/mdns v0.0.0-20160325001438-447166384f51 // indirect github.com/brynbellomy/klog v0.0.0-20200414031930-87fbf2e555ae // indirect github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect github.com/eolso/threadsafe v0.0.0-20230304165831-d28da4e4d0d3 // indirect github.com/go-errors/errors v1.4.2 // indirect + github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 // indirect + github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 // indirect + github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/gosimple/slug v1.13.1 // indirect github.com/gosimple/unidecode v1.0.1 // indirect github.com/jfbus/httprs v1.0.1 // indirect + github.com/klauspost/compress v1.14.1 // indirect github.com/miekg/dns v1.1.55 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect diff --git a/go.sum b/go.sum index 944a8df..2f93342 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,15 @@ +github.com/CAFxX/httpcompression v0.0.8 h1:UBWojERnpCS6X7whJkGGZeCC3ruZBRwkwkcnfGfb0ko= +github.com/CAFxX/httpcompression v0.0.8/go.mod h1:bVd1taHK1vYb5SWe9lwNDCqrfj2ka+C1Zx7JHzxuHnU= github.com/JoshuaDoes/json v0.0.0-20200726213358-ec3860544ac0 h1:315Zb0n+8KwZyUiIKbDGvfrQ003c0XfHYNy0M4vv5cA= github.com/JoshuaDoes/json v0.0.0-20200726213358-ec3860544ac0/go.mod h1:vsCdx75bni6k6GIQPPima8KiM7ZjNHeBJ8keBaJOADA= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/PuerkitoBio/goquery v1.4.1 h1:smcIRGdYm/w7JSbcdeLHEMzxmsBQvl8lhf0dSw2nzMI= github.com/PuerkitoBio/goquery v1.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= @@ -37,12 +43,19 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1 h1:zga7zaRE8HCbWjcXMDlfvmQtH0/kMVLo7cQ48dy6kWg= +github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1/go.mod h1:PumS+5d59wmAGsZo6IfRpVNaJUq+6xjC4Utt/k8GO6Q= +github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27 h1:O6yi4xa9b2DMosGsXzlMe2E9qXgXCVkRLCoRX+5amxI= +github.com/go-http-utils/fresh v0.0.0-20161124030543-7231e26a4b27/go.mod h1:AYvN8omj7nKLmbcXS2dyABYU6JB1Lz1bHmkkq1kf4I4= +github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno= +github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/brotli/go/cbrotli v0.0.0-20210623081221-ce222e317e36/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -56,6 +69,9 @@ github.com/jfbus/httprs v1.0.1 h1:kIf3dk5QlEiBDPnY88BRHI6iQ1HepvYD8Fb8zVmiNDU= github.com/jfbus/httprs v1.0.1/go.mod h1:M9fpbEbf1Ns5RSaTkvnykqBCdJkwNtYAoAC73Ie9bEs= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/klauspost/compress v1.14.1 h1:hLQYb23E8/fO+1u53d02A97a8UnsddcvYzq4ERRU4ds= +github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/librespot-org/librespot-golang v0.0.0-20220325184705-31669e5a889f h1:tTMPsyVClxxV+CnlLqzgxTTTFM6J7i//rSejN1Md0b0= github.com/librespot-org/librespot-golang v0.0.0-20220325184705-31669e5a889f/go.mod h1:LeHPXRci2Bg6RBmw8BDEFH5nfb8oGU6mll+qTH8UIzw= github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -67,6 +83,7 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pierrec/lz4/v4 v4.1.12/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -85,6 +102,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/valyala/gozstd v1.11.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= github.com/xlab/portaudio-go v0.0.0-20170905165025-132d041879db h1:sSIQlvfIWUHLDhEWUL2K2CeYv9CDksC00VxuxPUe4lw= github.com/xlab/portaudio-go v0.0.0-20170905165025-132d041879db/go.mod h1:r57mRacDQMS6Fz8ubv1nE8zZ0DbQ/sY0NkCmdxeUXmY= github.com/xlab/vorbis-go v0.0.0-20190125051917-087364aef51d/go.mod h1:AMqfx3jFwPqem3u8mF2lsRodZs30jG/Mag5HZ3mB3sA= diff --git a/index.html b/index.html index c8860b3..c2a3238 100644 --- a/index.html +++ b/index.html @@ -1,19 +1,21 @@ + libremedia - - + + - - - + + + + @@ -28,7 +30,6 @@ - @@ -38,7 +39,7 @@
- +
diff --git a/js/libremedia/libremedia-artwork.js b/js/libremedia/libremedia-artwork.js index 77e2537..06b7349 100644 --- a/js/libremedia/libremedia-artwork.js +++ b/js/libremedia/libremedia-artwork.js @@ -8,11 +8,9 @@ function setBgImg(url) { newbg = ""; } - if (document.body.style.backgroundImage !== newbg) { - //console.log("Setting background " + url + " using " + newbg + " to replace " + document.body.style.backgroundImage); - document.body.style.backgroundImage = newbg; - bgImg = url; - } + //console.log("Setting background " + url + " using " + newbg + " to replace " + document.body.style.backgroundImage); + document.body.style.backgroundImage = newbg; + bgImg = url; } function setBgStream(stream) { diff --git a/js/libremedia/libremedia-downloader.js b/js/libremedia/libremedia-downloader.js index b8922bc..dabac51 100644 --- a/js/libremedia/libremedia-downloader.js +++ b/js/libremedia/libremedia-downloader.js @@ -1,17 +1,27 @@ -function downloadStream(match) { +async function downloadStream(match) { if (match.params == null) { pagePotato(match); return; } var uri = match.params.uri; + + var stream = (await v1GetObject(uri)).object; + if (stream == null) { + displayNotification("Download not ready!", 3000); + return; + } + var hostname = window.location.hostname; - var urlpath = "https://" + hostname + "/v1/download/" + uri; + var port = window.location.port; + if (port != "") + port = ":" + port; + var protocol = location.protocol; + var urlpath = protocol + "//" + hostname + port + "/v1/download/" + uri; window.open(urlpath, "_blank"); pagePotato(match); - var stream = v1GetObject(uri).object; const creator = ''; - const albumObj = v1GetObject(stream.album.object.uri).object; + const albumObj = (await v1GetObject(stream.album.object.uri)).object; const album = '
' + albumObj.name + ''; const name = ''; @@ -29,4 +39,4 @@ function downloadAlbum(albumURI) { function downloadDiscography(creatorURI) { //console.log("Not implemented yet! TODO: Download " + creatorURI + " as ZIP"); -} \ No newline at end of file +} diff --git a/js/libremedia/libremedia-navigation.js b/js/libremedia/libremedia-navigation.js index a613eb2..967f84d 100644 --- a/js/libremedia/libremedia-navigation.js +++ b/js/libremedia/libremedia-navigation.js @@ -56,6 +56,7 @@ function navigoResolve() { .on((match) => { //console.log("Nothing to do!"); render(match, ""); + setBgImg("https://files.joshuadoes.com/randombackground_redirect.php"); }) .resolve(); } diff --git a/js/navigo-8.11.1.min.js b/js/navigo-8.11.1.min.js new file mode 100644 index 0000000..95bea73 --- /dev/null +++ b/js/navigo-8.11.1.min.js @@ -0,0 +1,2 @@ +!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("Navigo",[],n):"object"==typeof exports?exports.Navigo=n():t.Navigo=n()}("undefined"!=typeof self?self:this,(function(){return function(){"use strict";var t={407:function(t,n,e){e.d(n,{default:function(){return N}});var o=/([:*])(\w+)/g,r=/\*/g,i=/\/\?/g;function a(t){return void 0===t&&(t="/"),v()?location.pathname+location.search+location.hash:t}function s(t){return t.replace(/\/+$/,"").replace(/^\/+/,"")}function c(t){return"string"==typeof t}function u(t){return t&&t.indexOf("#")>=0&&t.split("#").pop()||""}function h(t){var n=s(t).split(/\?(.*)?$/);return[s(n[0]),n.slice(1).join("")]}function f(t){for(var n={},e=t.split("&"),o=0;o0?1===t.matches.length?t.matches[0]:t.matches:void 0)}})).concat([function(){return o()}])):o()}else o()}})),{},(function(){return n()})):n()}function P(t,n){d(t.navigateOptions,"updateState")&&t.instance._setCurrent(t.matches),n()}var R=[function(t,n){var e=t.instance.lastResolved();if(e&&e[0]&&e[0].route===t.match.route&&e[0].url===t.match.url&&e[0].queryString===t.match.queryString)return e.forEach((function(n){n.route.hooks&&n.route.hooks.already&&d(t.navigateOptions,"callHooks")&&n.route.hooks.already.forEach((function(n){return n(t.match)}))})),void n(!1);n()},function(t,n){t.match.route.hooks&&t.match.route.hooks.before&&d(t.navigateOptions,"callHooks")?m(t.match.route.hooks.before.map((function(n){return function(e,o){return n((function(n){!1===n?t.instance.__markAsClean(t):o()}),t.match)}})).concat([function(){return n()}])):n()},function(t,n){d(t.navigateOptions,"callHandler")&&t.match.route.handler(t.match),t.instance.updatePageLinks(),n()},function(t,n){t.match.route.hooks&&t.match.route.hooks.after&&d(t.navigateOptions,"callHooks")&&t.match.route.hooks.after.forEach((function(n){return n(t.match)})),n()}],S=[A,function(t,n){var e=t.instance._notFoundRoute;if(e){t.notFoundHandled=!0;var o=h(t.currentLocationPath),r=o[0],i=o[1],a=u(t.to);e.path=s(r);var c={url:e.path,queryString:i,hashString:a,data:null,route:e,params:""!==i?f(i):null};t.matches=[c],t.match=c}n()},m.if((function(t){return t.notFoundHandled}),R.concat([P]),[function(t,n){t.resolveOptions&&!1!==t.resolveOptions.noMatchWarning&&void 0!==t.resolveOptions.noMatchWarning||console.warn('Navigo: "'+t.currentLocationPath+"\" didn't match any of the registered routes."),n()},function(t,n){t.instance._setCurrent(null),n()}])];function E(){return(E=Object.assign||function(t){for(var n=1;n=0&&(t=!0===o.hash?t.split("#")[1]||"/":t.split("#")[0]),t}function E(t){return s(i+"/"+s(t))}function N(t,n,e,o){return t=c(t)?E(t):t,{name:o||s(String(t)),path:t,handler:n,hooks:g(e)}}function U(t,n){if(!r.__dirty){r.__dirty=!0,t=t?s(i)+"/"+s(t):void 0;var e={instance:r,to:t,currentLocationPath:t,navigateOptions:{},resolveOptions:j({},o,n)};return m([y,_,m.if((function(t){var n=t.matches;return n&&n.length>0}),x,S)],e,H),!!e.matches&&e.matches}r.__waiting.push((function(){return r.resolve(t,n)}))}function q(t,n){if(r.__dirty)r.__waiting.push((function(){return r.navigate(t,n)}));else{r.__dirty=!0,t=s(i)+"/"+s(t);var e={instance:r,to:t,navigateOptions:n||{},resolveOptions:n&&n.resolveOptions?n.resolveOptions:o,currentLocationPath:R(t)};m([k,O,_,m.if((function(t){var n=t.matches;return n&&n.length>0}),x,S),b,H],e,H)}}function F(){if(P)return(P?[].slice.call(document.querySelectorAll(o.linksSelector||C)):[]).forEach((function(t){"false"!==t.getAttribute("data-navigo")&&"_blank"!==t.getAttribute("target")?t.hasListenerAttached||(t.hasListenerAttached=!0,t.navigoHandler=function(n){if((n.ctrlKey||n.metaKey)&&"a"===n.target.tagName.toLowerCase())return!1;var e=t.getAttribute("href");if(null==e)return!1;if(e.match(/^(http|https)/)&&"undefined"!=typeof URL)try{var o=new URL(e);e=o.pathname+o.search}catch(t){}var i=function(t){if(!t)return{};var n,e=t.split(","),o={};return e.forEach((function(t){var e=t.split(":").map((function(t){return t.replace(/(^ +| +$)/g,"")}));switch(e[0]){case"historyAPIMethod":o.historyAPIMethod=e[1];break;case"resolveOptionsStrategy":n||(n={}),n.strategy=e[1];break;case"resolveOptionsHash":n||(n={}),n.hash="true"===e[1];break;case"updateBrowserURL":case"callHandler":case"updateState":case"force":o[e[0]]="true"===e[1]}})),n&&(o.resolveOptions=n),o}(t.getAttribute("data-navigo-options"));L||(n.preventDefault(),n.stopPropagation(),r.navigate(s(e),i))},t.addEventListener("click",t.navigoHandler)):t.hasListenerAttached&&t.removeEventListener("click",t.navigoHandler)})),r}function I(t,n,e){var o=w.find((function(n){return n.name===t})),r=null;if(o){if(r=o.path,n)for(var a in n)r=r.replace(":"+a,n[a]);r=r.match(/^\//)?r:"/"+r}return r&&e&&!e.includeRoot&&(r=r.replace(new RegExp("^/"+i),"")),r}function M(t){var n=h(s(t)),o=n[0],r=n[1],i=""===r?null:f(r);return{url:o,queryString:r,hashString:u(t),route:N(o,(function(){}),[e],o),data:null,params:i}}function T(t,n,e){return"string"==typeof n&&(n=z(n)),n?(n.hooks[t]||(n.hooks[t]=[]),n.hooks[t].push(e),function(){n.hooks[t]=n.hooks[t].filter((function(t){return t!==e}))}):(console.warn("Route doesn't exists: "+n),function(){})}function z(t){return"string"==typeof t?w.find((function(n){return n.name===E(t)})):w.find((function(n){return n.handler===t}))}t?i=s(t):console.warn('Navigo requires a root path in its constructor. If not provided will use "/" as default.'),this.root=i,this.routes=w,this.destroyed=L,this.current=d,this.__freezeListening=!1,this.__waiting=[],this.__dirty=!1,this.__markAsClean=function(t){t.instance.__dirty=!1,t.instance.__waiting.length>0&&t.instance.__waiting.shift()()},this.on=function(t,n,o){var r=this;return"object"!=typeof t||t instanceof RegExp?("function"==typeof t&&(o=n,n=t,t=i),w.push(N(t,n,[e,o])),this):(Object.keys(t).forEach((function(n){if("function"==typeof t[n])r.on(n,t[n]);else{var o=t[n],i=o.uses,a=o.as,s=o.hooks;w.push(N(n,i,[e,s],a))}})),this)},this.off=function(t){return this.routes=w=w.filter((function(n){return c(t)?s(n.path)!==s(t):"function"==typeof t?t!==n.handler:String(n.path)!==String(t)})),this},this.resolve=U,this.navigate=q,this.navigateByName=function(t,n,e){var o=I(t,n);return null!==o&&(q(o.replace(new RegExp("^/?"+i),""),e),!0)},this.destroy=function(){this.routes=w=[],A&&window.removeEventListener("popstate",this.__popstateListener),this.destroyed=L=!0},this.notFound=function(t,n){return r._notFoundRoute=N("*",t,[e,n],"__NOT_FOUND__"),this},this.updatePageLinks=F,this.link=function(t){return"/"+i+"/"+s(t)},this.hooks=function(t){return e=t,this},this.extractGETParameters=function(t){return h(R(t))},this.lastResolved=function(){return d},this.generate=I,this.getLinkPath=function(t){return t.getAttribute("href")},this.match=function(t){var n={instance:r,currentLocationPath:t,to:t,navigateOptions:{},resolveOptions:o};return _(n,(function(){})),!!n.matches&&n.matches},this.matchLocation=function(t,n,e){void 0===n||void 0!==e&&!e||(n=E(n));var o={instance:r,to:n,currentLocationPath:n};return y(o,(function(){})),"string"==typeof t&&(t=void 0===e||e?E(t):t),l(o,{name:String(t),path:t,handler:function(){},hooks:{}})||!1},this.getCurrentLocation=function(){return M(s(a(i)).replace(new RegExp("^"+i),""))},this.addBeforeHook=T.bind(this,"before"),this.addAfterHook=T.bind(this,"after"),this.addAlreadyHook=T.bind(this,"already"),this.addLeaveHook=T.bind(this,"leave"),this.getRoute=z,this._pathToMatchObject=M,this._clean=s,this._checkForAHash=R,this._setCurrent=function(t){return d=r.current=t},function(){A&&(this.__popstateListener=function(){r.__freezeListening||U()},window.addEventListener("popstate",this.__popstateListener))}.call(this),F.call(this)}}},n={};function e(o){if(n[o])return n[o].exports;var r=n[o]={exports:{}};return t[o](r,r.exports,e),r.exports}return e.d=function(t,n){for(var o in n)e.o(n,o)&&!e.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:n[o]})},e.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},e(407)}().default})); +//# sourceMappingURL=navigo.min.js.map \ No newline at end of file diff --git a/main.go b/main.go index ee3f8ce..eba6769 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,9 @@ import ( "strings" "time" + "github.com/CAFxX/httpcompression" "github.com/eolso/librespot-golang/librespot/utils" + "github.com/go-http-utils/etag" ) type exporterr struct { @@ -96,17 +98,20 @@ func main() { } //libremedia API v1 - http.HandleFunc("/v1/", v1Handler) - http.HandleFunc("/v1/stream/", v1StreamHandler) - http.HandleFunc("/v1/download/", v1DownloadHandler) + mux := http.NewServeMux() + mux.HandleFunc("/v1/", v1Handler) + mux.HandleFunc("/v1/stream/", v1StreamHandler) + mux.HandleFunc("/v1/download/", v1DownloadHandler) //Built-in utilities that may not be recreatable in some circumstances - http.HandleFunc("/util/gid2id/", gid2id) + mux.HandleFunc("/util/gid2id/", gid2id) //Web interfaces - http.HandleFunc("/", webHandler) + mux.HandleFunc("/", webHandler) - Warning.Fatal(http.ListenAndServe(service.HostAddr, nil)) + //Compression + compress, _ := httpcompression.DefaultAdapter() + Warning.Fatal(http.ListenAndServe(service.HostAddr, etag.Handler(compress(mux), false))) } func v1Handler(w http.ResponseWriter, r *http.Request) { @@ -116,13 +121,15 @@ func v1Handler(w http.ResponseWriter, r *http.Request) { jsonWriteErrorf(w, 404, "no matching object") return } - if !obj.Expanded && !obj.Expanding { - switch obj.Type { + if !obj.Expanded { + /*switch obj.Type { case "album", "creator", "stream": obj.Expand() default: go obj.Expand() - } + }*/ + go obj.Expand() + time.Sleep(time.Second * 1) } jsonWrite(w, obj) } diff --git a/objects.go b/objects.go index 181ea90..667c3a8 100644 --- a/objects.go +++ b/objects.go @@ -20,11 +20,20 @@ type Object struct { Expanded bool `json:"expanded,omitempty"` //Whether or not this object has been expanded internally } +var ( + expandQueue map[string]*Object //Holds the active expanding queue to check for dead objects +) + +func init() { + expandQueue = make(map[string]*Object) +} + // JSON returns this object as serialized JSON func (obj *Object) JSON() ([]byte, error) { return json.Marshal(obj) } +// SearchResults returns this object as *ObjectSearchResults func (obj *Object) SearchResults() *ObjectSearchResults { if obj.Object == nil { return nil @@ -41,6 +50,7 @@ func (obj *Object) SearchResults() *ObjectSearchResults { return nil } +// Creator returns this object as *ObjectCreator func (obj *Object) Creator() *ObjectCreator { if obj.Object == nil { return nil @@ -57,6 +67,7 @@ func (obj *Object) Creator() *ObjectCreator { return nil } +// Album returns this object as *ObjectAlbum func (obj *Object) Album() *ObjectAlbum { if obj.Object == nil { return nil @@ -73,6 +84,7 @@ func (obj *Object) Album() *ObjectAlbum { return nil } +// Stream returns this object as *ObjectStream func (obj *Object) Stream() *ObjectStream { if obj.Object == nil { return nil @@ -138,28 +150,28 @@ func (obj *Object) Sync() { } // Expand fills in all top-level object arrays with completed objects -func (src *Object) Expand() { - if src.URI == "" { +func (obj *Object) Expand() { + if obj.URI == "" { return } //Check if object is being expanded right now - if src.Expanding { - //Sleep and try again + if _, exists := expandQueue[obj.URI]; exists { return } - src.Expanding = true - src.Expanded = false - src.Sync() - Trace.Println("Expanding " + src.URI) - switch src.Type { + obj.Expanding = true + obj.Expanded = false + obj.Sync() + expandQueue[obj.URI] = obj + Trace.Println("Expanding " + obj.URI) + switch obj.Type { case "search": - if search := src.SearchResults(); search != nil { + if search := obj.SearchResults(); search != nil { syncSearch := func() { searchJSON, err := json.Marshal(search) if err == nil { - src.Object = &json.RawMessage{} - src.Object.UnmarshalJSON(searchJSON) - src.Sync() + obj.Object = &json.RawMessage{} + obj.Object.UnmarshalJSON(searchJSON) + obj.Sync() } } for i := 0; i < len(search.Streams); i++ { @@ -167,126 +179,126 @@ func (src *Object) Expand() { continue } search.Streams[i] = GetObject(search.Streams[i].URI) - syncSearch() } + syncSearch() for i := 0; i < len(search.Creators); i++ { if search.Creators[i].URI == "" { continue } search.Creators[i] = GetObject(search.Creators[i].URI) - syncSearch() } + syncSearch() for i := 0; i < len(search.Albums); i++ { if search.Albums[i].URI == "" { continue } search.Albums[i] = GetObject(search.Albums[i].URI) - syncSearch() } + syncSearch() } case "artist", "creator", "user", "channel", "chan", "streamer": - if creator := src.Creator(); creator != nil { + if creator := obj.Creator(); creator != nil { syncCreator := func() { creatorJSON, err := json.Marshal(creator) if err == nil { - src.Object = &json.RawMessage{} - src.Object.UnmarshalJSON(creatorJSON) - src.Sync() + obj.Object = &json.RawMessage{} + obj.Object.UnmarshalJSON(creatorJSON) + obj.Sync() } } - for i := 0; i < len(creator.TopStreams); i++ { + /*for i := 0; i < len(creator.TopStreams); i++ { if creator.TopStreams[i].URI == "" { continue } creator.TopStreams[i] = GetObject(creator.TopStreams[i].URI) - syncCreator() } + syncCreator() for i := 0; i < len(creator.Albums); i++ { if creator.Albums[i].URI == "" { continue } creator.Albums[i] = GetObject(creator.Albums[i].URI) - syncCreator() } + syncCreator() for i := 0; i < len(creator.Appearances); i++ { if creator.Appearances[i].URI == "" { continue } creator.Appearances[i] = GetObject(creator.Appearances[i].URI) - syncCreator() } + syncCreator() for i := 0; i < len(creator.Singles); i++ { if creator.Singles[i].URI == "" { continue } creator.Singles[i] = GetObject(creator.Singles[i].URI) - syncCreator() } + syncCreator() for i := 0; i < len(creator.Related); i++ { if creator.Related[i].URI == "" { continue } creator.Related[i] = GetObject(creator.Related[i].URI) - syncCreator() - } + }*/ + syncCreator() } case "album": - if album := src.Album(); album != nil { + if album := obj.Album(); album != nil { syncAlbum := func() { albumJSON, err := json.Marshal(album) if err == nil { - src.Object = &json.RawMessage{} - src.Object.UnmarshalJSON(albumJSON) - src.Sync() + obj.Object = &json.RawMessage{} + obj.Object.UnmarshalJSON(albumJSON) + obj.Sync() } } - for i := 0; i < len(album.Creators); i++ { + /*for i := 0; i < len(album.Creators); i++ { if album.Creators[i].URI == "" { continue } album.Creators[i] = GetObject(album.Creators[i].URI) syncAlbum() - } + }*/ for i := 0; i < len(album.Discs); i++ { for j := 0; j < len(album.Discs[i].Streams); j++ { if album.Discs[i].Streams[j].URI == "" { continue } album.Discs[i].Streams[j] = GetObject(album.Discs[i].Streams[j].URI) - syncAlbum() } } + syncAlbum() } case "track", "song", "video", "audio", "stream": - if stream := src.Stream(); stream != nil { + if stream := obj.Stream(); stream != nil { syncStream := func() { streamJSON, err := json.Marshal(stream) if err == nil { - src.Object = &json.RawMessage{} - src.Object.UnmarshalJSON(streamJSON) - src.Sync() + obj.Object = &json.RawMessage{} + obj.Object.UnmarshalJSON(streamJSON) + obj.Sync() } } stream.Album = GetObject(stream.Album.URI) - syncStream() - for i := 0; i < len(stream.Creators); i++ { + /*for i := 0; i < len(stream.Creators); i++ { if stream.Creators[i].URI == "" { continue } stream.Creators[i] = GetObject(stream.Creators[i].URI) syncStream() - } + }*/ + syncStream() } } - if src.Object != nil { - src.Expanding = false - src.Expanded = true - Trace.Println("Finished expanding " + src.URI) + obj.Expanding = false + if obj.Object != nil { + obj.Expanded = true + Trace.Println("Finished expanding " + obj.URI) } else { - src.Expanding = false - Trace.Println("Failed to expand " + src.URI) + Trace.Println("Failed to expand " + obj.URI) } - src.Sync() + obj.Sync() + delete(expandQueue, obj.URI) } // GetObjectCached returns a new object from the cache that links to a given URI @@ -344,9 +356,9 @@ func GetObjectCached(uri string) (obj *Object) { // NewObjError returns an error object func NewObjError(msg string) (obj *Object) { obj = &Object{ - Type: "error", + Type: "error", Provider: "libremedia", - Object: &json.RawMessage{}, + Object: &json.RawMessage{}, } errJSON, err := json.Marshal(&exporterr{Error: msg}) if err == nil { diff --git a/service.go b/service.go index cc118b1..be75f45 100644 --- a/service.go +++ b/service.go @@ -7,6 +7,7 @@ import ( ) var ( + //TODO: Order handler priority via configuration handlers = map[string]Handler{ "tidal": &TidalClient{}, "spotify": &SpotifyClient{}, @@ -14,6 +15,7 @@ var ( providers = make([]string, 0) ) +// Handler is an interface to satisfy a libremedia service handler (a media or metadata provider, respond to something) type Handler interface { Provider() string //Used for service identification SetService(*Service) //Provides the handler access to the libremedia service @@ -28,6 +30,7 @@ type Handler interface { ReplaceURI(text string) string //Replaces all instances of a URI with a libremedia-acceptable URI, for dynamic hyperlinking } +// HandlerConfig defines a handler's login credentials and/or active status type HandlerConfig struct { Active bool `json:"active"` Username string `json:"username"` @@ -36,6 +39,7 @@ type HandlerConfig struct { BlobPath string `json:"blobPath"` } +// Service hosts a libremedia service instance's sessions and configuration type Service struct { AccessKeys []string `json:"accessKeys"` BaseURL string `json:"baseURL"` @@ -45,6 +49,7 @@ type Service struct { Grants map[string]*ServiceUser `json:"-"` } +// Login logs into all of the active providers, blocking execution if any require action func (s *Service) Login() error { if s.BaseURL[len(s.BaseURL)-1] != '/' { s.BaseURL += "/" @@ -54,14 +59,14 @@ func (s *Service) Login() error { if config.Active { newHandler, err := handler.Authenticate(config) if err != nil { - Error.Println("Failed to authenticate " + provider + ": ", err) + Error.Println("Failed to authenticate "+provider+": ", err) return err } newHandler.SetService(s) handlers[provider] = newHandler providers = append(providers, provider) } else { - Trace.Println("Skipping authenticating " + provider) + Trace.Println("Skipping authenticating inactive provider " + provider) delete(handlers, provider) } } @@ -69,6 +74,7 @@ func (s *Service) Login() error { return nil } +// Auth checks the authentication key func (s *Service) Auth(accessKey string) (*ServiceUser, error) { allow := false for i := 0; i < len(s.AccessKeys); i++ { @@ -83,6 +89,7 @@ func (s *Service) Auth(accessKey string) (*ServiceUser, error) { return nil, nil } +// Stream transports a media stream for the specified format func (s *Service) Stream(w http.ResponseWriter, r *http.Request, stream *ObjectStream, format int) error { if stream == nil { return fmt.Errorf("stream is nil") @@ -90,23 +97,25 @@ func (s *Service) Stream(w http.ResponseWriter, r *http.Request, stream *ObjectS if stream.Provider == "" { return fmt.Errorf("provider not specified") } -/* if stream.Formats == nil || len(stream.Formats) <= format { - objStream := GetObject(stream.URI, false) - if objStream != nil { - stream = objStream.Stream() - if stream.Formats == nil || len(stream.Formats) <= format { - return fmt.Errorf("format not available to stream") + /* if stream.Formats == nil || len(stream.Formats) <= format { + objStream := GetObject(stream.URI, false) + if objStream != nil { + stream = objStream.Stream() + if stream.Formats == nil || len(stream.Formats) <= format { + return fmt.Errorf("format not available to stream") + } + } else { + return fmt.Errorf("stream not available right now") } - } else { - return fmt.Errorf("stream not available right now") } - } -*/ if handler, exists := handlers[stream.Provider]; exists { + */ + if handler, exists := handlers[stream.Provider]; exists { return handler.StreamFormat(w, r, stream, format) } return fmt.Errorf("no handler for provider " + stream.Provider) } +// Download transports a media download for the specified format func (s *Service) Download(w http.ResponseWriter, r *http.Request, stream *ObjectStream, format int) error { if stream.Provider == "" { return fmt.Errorf("provider not specified") @@ -118,6 +127,7 @@ func (s *Service) Download(w http.ResponseWriter, r *http.Request, stream *Objec return fmt.Errorf("no handler for provider " + stream.Provider) } +// ServiceUser hosts a user's live session type ServiceUser struct { - Expires time.Time + LastPing time.Time } diff --git a/tidal.go b/tidal.go index 6e8ee5c..d5e6892 100644 --- a/tidal.go +++ b/tidal.go @@ -69,6 +69,7 @@ func (terr *TidalError) Error() error { // TidalClient holds a Tidal client type TidalClient struct { sync.Mutex + Ratelimiter int `json:"ratelimiter"` //Increased by one each time the ratelimit is encountered, spreads out requests ClientID string `json:"clientID"` ClientSecret string `json:"clientSecret"` @@ -163,8 +164,16 @@ func (t *TidalClient) GetJSON(endpoint string, query url.Values, target interfac if resp.StatusCode != 200 { return fmt.Errorf("%s: %s", resp.Status, string(body)) } - if target != nil { - return json.Unmarshal(body, target) + switch resp.StatusCode { + case 200: //OK + if target != nil { + return json.Unmarshal(body, target) + } + case 429: //Too Many Requests + t.Ratelimiter++ + fmt.Printf("Sleeping for %d seconds\n", t.Ratelimiter) + time.Sleep(time.Duration(t.Ratelimiter) * time.Second) + return t.GetJSON(endpoint, query, target) } return nil }