mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-22 01:43:29 +01:00
Scrobble on completion of song if song was listened to
This commit is contained in:
@@ -164,7 +164,7 @@ function server(
|
||||
${SONOS_RECOMMENDED_IMAGE_SIZES.map(
|
||||
(size) =>
|
||||
`<sizeEntry size="${size}" substitution="/art/size/${size}"/>`
|
||||
)}
|
||||
).join("")}
|
||||
</imageSizeMap>
|
||||
</Match>
|
||||
</PresentationMap>
|
||||
|
||||
58
src/smapi.ts
58
src/smapi.ts
@@ -242,6 +242,10 @@ export const album = (
|
||||
title: album.name,
|
||||
albumArtURI: defaultAlbumArtURI(webAddress, accessToken, album),
|
||||
canPlay: true,
|
||||
// defaults
|
||||
// canScroll: false,
|
||||
// canEnumerate: true,
|
||||
// canAddToFavorites: true
|
||||
});
|
||||
|
||||
export const track = (
|
||||
@@ -487,6 +491,26 @@ function bindSmapiSoapServiceToExpress(
|
||||
},
|
||||
},
|
||||
}));
|
||||
case "album":
|
||||
return musicLibrary.album(typeId).then((it) => ({
|
||||
getExtendedMetadataResult: {
|
||||
mediaCollection: {
|
||||
attributes: {
|
||||
readOnly: true,
|
||||
userContent: false,
|
||||
renameable: false,
|
||||
},
|
||||
...album(webAddress, accessToken, it),
|
||||
},
|
||||
// <mediaCollection readonly="true">
|
||||
// </mediaCollection>
|
||||
// <relatedText>
|
||||
// <id>AL:123456</id>
|
||||
// <type>ALBUM_NOTES</type>
|
||||
// </relatedText>
|
||||
// </getExtendedMetadataResult>
|
||||
},
|
||||
}));
|
||||
default:
|
||||
throw `Unsupported getExtendedMetadata id=${id}`;
|
||||
}
|
||||
@@ -784,10 +808,42 @@ function bindSmapiSoapServiceToExpress(
|
||||
}
|
||||
})
|
||||
.then((_) => ({ removeFromContainerResult: { updateId: "" } })),
|
||||
setPlayedSeconds: async (
|
||||
{ id, seconds }: { id: string; seconds: string },
|
||||
_,
|
||||
headers?: SoapyHeaders
|
||||
) =>
|
||||
auth(musicService, accessTokens, headers)
|
||||
.then(splitId(id))
|
||||
.then(({ musicLibrary, type, typeId }) => {
|
||||
switch (type) {
|
||||
case "track":
|
||||
musicLibrary.track(typeId).then(({ duration }) => {
|
||||
if (
|
||||
(duration < 30 && +seconds >= 10) ||
|
||||
(duration >= 30 && +seconds >= 30)
|
||||
) {
|
||||
musicLibrary.scrobble(typeId);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
logger.info("Unsupported scrobble", { id, seconds });
|
||||
break;
|
||||
}
|
||||
})
|
||||
.then((_) => ({
|
||||
setPlayedSecondsResult: {},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
readFileSync(WSDL_FILE, "utf8")
|
||||
readFileSync(WSDL_FILE, "utf8"),
|
||||
(err: any, res: any) => {
|
||||
if (err) {
|
||||
logger.error("BOOOOM", { err, res });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
soapyService.log = (type, data) => {
|
||||
|
||||
30
src/sonos.ts
30
src/sonos.ts
@@ -5,10 +5,12 @@ import { MusicService } from "@svrooij/sonos/lib/services";
|
||||
import { head } from "underscore";
|
||||
import logger from "./logger";
|
||||
import { SOAP_PATH, STRINGS_ROUTE, PRESENTATION_MAP_ROUTE } from "./smapi";
|
||||
import qs from "querystring"
|
||||
import qs from "querystring";
|
||||
|
||||
export const PRESENTATION_AND_STRINGS_VERSION = "18";
|
||||
|
||||
// NOTE: manifest requires https for the URL,
|
||||
// otherwise you will get an error trying to register
|
||||
export type Capability =
|
||||
| "search"
|
||||
| "trFavorites"
|
||||
@@ -16,7 +18,9 @@ export type Capability =
|
||||
| "ucPlaylists"
|
||||
| "extendedMD"
|
||||
| "contextHeaders"
|
||||
| "authorizationHeader";
|
||||
| "authorizationHeader"
|
||||
| "logging"
|
||||
| "manifest";
|
||||
|
||||
export const BONOB_CAPABILITIES: Capability[] = [
|
||||
"search",
|
||||
@@ -24,6 +28,7 @@ export const BONOB_CAPABILITIES: Capability[] = [
|
||||
// "alFavorites",
|
||||
"ucPlaylists",
|
||||
"extendedMD",
|
||||
"logging",
|
||||
];
|
||||
|
||||
export type Device = {
|
||||
@@ -38,13 +43,13 @@ export type Service = {
|
||||
sid: number;
|
||||
uri: string;
|
||||
secureUri: string;
|
||||
strings: { uri?: string; version?: string };
|
||||
presentation: { uri?: string; version?: string };
|
||||
strings?: { uri?: string; version?: string };
|
||||
presentation?: { uri?: string; version?: string };
|
||||
pollInterval?: number;
|
||||
authType: "Anonymous" | "AppLink" | "DeviceLink" | "UserId";
|
||||
};
|
||||
|
||||
const stripTailingSlash = (url: string) =>
|
||||
export const stripTailingSlash = (url: string) =>
|
||||
url.endsWith("/") ? url.substring(0, url.length - 1) : url;
|
||||
|
||||
export const bonobService = (
|
||||
@@ -113,12 +118,10 @@ export const asCustomdForm = (csrfToken: string, service: Service) => ({
|
||||
secureUri: service.secureUri,
|
||||
pollInterval: `${service.pollInterval || 1200}`,
|
||||
authType: service.authType,
|
||||
stringsVersion: service.strings.version || "",
|
||||
stringsUri: service.strings.uri || "",
|
||||
presentationMapVersion: service.presentation.version || "",
|
||||
presentationMapUri: service.presentation.uri || "",
|
||||
manifestVersion: "0",
|
||||
manifestUri: "",
|
||||
stringsVersion: service.strings?.version || "0",
|
||||
stringsUri: service.strings?.uri || "",
|
||||
presentationMapVersion: service.presentation?.version || "0",
|
||||
presentationMapUri: service.presentation?.uri || "",
|
||||
containerType: "MService",
|
||||
caps: BONOB_CAPABILITIES,
|
||||
});
|
||||
@@ -193,9 +196,10 @@ export function autoDiscoverySonos(sonosSeedHost?: string): Sonos {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const customdForm = asCustomdForm(csrfToken, service);
|
||||
logger.info(`Registering with sonos @ ${customd}`, { customdForm });
|
||||
return axios
|
||||
.post(customd, new URLSearchParams(qs.stringify(asCustomdForm(csrfToken, service))), {
|
||||
.post(customd, new URLSearchParams(qs.stringify(customdForm)), {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user