Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import from spotify extended streaming history #1800

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
96 changes: 76 additions & 20 deletions frontend/js/src/lastfm/LastFMImporter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,31 @@ export default class LastFmImporter extends React.Component<
this.startImport();
};

submitSpotifyStreams = async (text: string) => {
const streams: Array<SpotifyStream> = JSON.parse(text);
const listens = streams
.filter(
(stream) =>
stream.ms_played > 30000 &&

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the ListenBrainz documentation a listen should only be submitted if it was played for more than half the track or for at least 4 minutes.

stream.master_metadata_track_name &&
stream.master_metadata_album_artist_name
)
.map((stream) => {
return {
listened_at: new Date(stream.ts).getTime() / 1000,
track_metadata: {
track_name: stream.master_metadata_track_name,
artist_name: stream.master_metadata_album_artist_name,
release_name: stream.master_metadata_album_album_name,
additional_info: {
spotify_id: stream.spotify_track_uri,
},
},
} as Listen;
});
await this.APIService.submitListens(this.userToken, "import", listens);
};

importFeedback = async () => {
const { lastfmUsername, service } = this.state;
if (!lastfmUsername || service !== "lastfm") {
Expand Down Expand Up @@ -354,26 +379,23 @@ export default class LastFmImporter extends React.Component<
}
};

progressBarPercentage() {
if (this.totalPages >= this.numCompleted)
return Math.ceil((this.numCompleted / this.totalPages) * 100);
return 50;
}

async submitPage(payload: Array<Listen>) {
const delay = this.getRateLimitDelay();
// Halt execution for some time
await new Promise((resolve) => {
setTimeout(resolve, delay);
});

const response = await this.APIService.submitListens(
this.userToken,
"import",
payload
);
this.updateRateLimitParameters(response);
}
handleSpotifyImport = (event: React.ChangeEvent<HTMLInputElement>) => {
const fr = new FileReader();
fr.onload = (e) => {
this.submitSpotifyStreams(e?.target?.result as string);
};
if (
!event ||
!event.target ||
!event.target.files ||
!event.target.files[0]
) {
// eslint-disable-next-line no-console
console.log("Invalid file!");
return;
}
fr.readAsText(event.target.files[0]);
};

async importLoop() {
while (this.page > 0) {
Expand Down Expand Up @@ -575,6 +597,27 @@ export default class LastFmImporter extends React.Component<
this.rlOrigin = new Date().getTime() / 1000;
}

progressBarPercentage() {
if (this.totalPages >= this.numCompleted)
return Math.ceil((this.numCompleted / this.totalPages) * 100);
return 50;
}

async submitPage(payload: Array<Listen>) {
const delay = this.getRateLimitDelay();
// Halt execution for some time
await new Promise((resolve) => {
setTimeout(resolve, delay);
});

const response = await this.APIService.submitListens(
this.userToken,
"import",
payload
);
this.updateRateLimitParameters(response);
}

render() {
const { show, canClose, lastfmUsername, msg, service } = this.state;
return (
Expand Down Expand Up @@ -648,6 +691,19 @@ export default class LastFmImporter extends React.Component<
<br />
</LastFMImporterModal>
)}
<form onSubmit={(e) => e.preventDefault()}>
<span className="btn btn-default btn-primary">
Spotify Extended Streaming
</span>
<input
id="spotify-import-file"
type="file"
onChange={this.handleSpotifyImport}
/>
<button className="btn btn-success" type="submit">
Import Now!
</button>
</form>
</div>
);
}
Expand Down
24 changes: 24 additions & 0 deletions frontend/js/src/utils/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ declare type Listen = BaseListenFormat & {
playing_now?: boolean | null;
};

declare type SpotifyStream = {
ts: string; // Date string
username: string;
platform: string;
ms_played: number;
conn_country: string;
ip_addr_decrypted: string;
user_agent_decrypted: string | null;
master_metadata_track_name: string;
master_metadata_album_artist_name: string;
master_metadata_album_album_name: string;
spotify_track_uri: string;
episode_name: string | null;
episode_show_name: string | null;
spotify_episode_uri: string | null;
reason_start: string;
reason_end: string;
shuffle: boolean;
skipped: boolean;
offline: boolean;
offline_timestamp: number;
incognito_mode: boolean;
};

declare type Recommendation = Listen & {
latest_listened_at?: string;
};
Expand Down