Newer
Older
const CONTENT_REGEX = /^https:\/\/open\.spotify\.com\/(track|episode)\/([a-zA-Z0-9]*)($|\/)/;
const PLAYLIST_REGEX = /^https:\/\/open\.spotify\.com\/(album|playlist|collection)\/([a-zA-Z0-9]*|your-episodes|tracks)($|\/)/;
const CHANNEL_REGEX = /^https:\/\/open\.spotify\.com\/(show|artist|user|genre|section|content-feed)\/(section|)([a-zA-Z0-9]*|recently-played)($|\/)/;
const SONG_URL_PREFIX = "https://open.spotify.com/track/";
const EPISODE_URL_PREFIX = "https://open.spotify.com/episode/";
const SHOW_URL_PREFIX = "https://open.spotify.com/show/";
const ARTIST_URL_PREFIX = "https://open.spotify.com/artist/";
const USER_URL_PREFIX = "https://open.spotify.com/user/";
const ALBUM_URL_PREFIX = "https://open.spotify.com/album/";
const PAGE_URL_PREFIX = "https://open.spotify.com/genre/";
const SECTION_URL_PREFIX = "https://open.spotify.com/section/";
const PLAYLIST_URL_PREFIX = "https://open.spotify.com/playlist/";
const COLLECTION_UR_PREFIX = "https://open.spotify.com/collection/";
const QUERY_URL = "https://api-partner.spotify.com/pathfinder/v1/query";
const IMAGE_URL_PREFIX = "https://i.scdn.co/image/";
const PLATFORM = "Spotify";
// const USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0" as const
const PLATFORM_IDENTIFIER = "web_player linux undefined;firefox 126.0;desktop";
const SPOTIFY_CONNECT_NAME = "Web Player (Grayjay)";
const CLIENT_VERSION = "harmony:4.42.0-2780565f";
const HARDCODED_ZERO = 0;
const HARDCODED_EMPTY_STRING = "";
const EMPTY_AUTHOR = new PlatformAuthorLink(new PlatformID(PLATFORM, "", plugin.config.id), "", "");
const local_http = http;
// const local_utility = utility
// set missing constants
Type.Order.Chronological = "Latest releases";
Type.Order.Views = "Most played";
Type.Order.Favorites = "Most favorited";
Type.Feed.Playlists = "PLAYLISTS";
Type.Feed.Albums = "ALBUMS";
/** State */
let local_state;
//#region source methods
source.enable = enable;
source.disable = disable;
source.saveState = saveState;
source.getHome = getHome;
source.getSearchCapabilities = getSearchCapabilities;
source.search = search;
source.searchChannels = searchChannels;
source.isChannelUrl = isChannelUrl;
source.getChannel = getChannel;
source.getChannelCapabilities = getChannelCapabilities;
source.getChannelContents = getChannelContents;
source.isContentDetailsUrl = isContentDetailsUrl;
source.getContentDetails = getContentDetails;
source.getUserSubscriptions = getUserSubscriptions;
source.getUserPlaylists = getUserPlaylists;
source.getPlaybackTracker = getPlaybackTracker;
if (IS_TESTING) {
enable,
disable,
saveState,
getHome,
search,
getSearchCapabilities,
isContentDetailsUrl,
getContentDetails,
isChannelUrl,
getChannel,
getChannelContents,
getChannelCapabilities,
searchChannels,
isPlaylistUrl,
getPlaylist,
searchPlaylists,
getUserPlaylists,
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
getUserSubscriptions,
getPlaybackTracker
};
if (source.enable === undefined) {
assert_never(source.enable);
}
if (source.disable === undefined) {
assert_never(source.disable);
}
if (source.saveState === undefined) {
assert_never(source.saveState);
}
if (source.getHome === undefined) {
assert_never(source.getHome);
}
if (source.search === undefined) {
assert_never(source.search);
}
if (source.getSearchCapabilities === undefined) {
assert_never(source.getSearchCapabilities);
}
if (source.isContentDetailsUrl === undefined) {
assert_never(source.isContentDetailsUrl);
}
if (source.getContentDetails === undefined) {
assert_never(source.getContentDetails);
}
if (source.isChannelUrl === undefined) {
assert_never(source.isChannelUrl);
}
if (source.getChannel === undefined) {
assert_never(source.getChannel);
}
if (source.getChannelContents === undefined) {
assert_never(source.getChannelContents);
}
if (source.getChannelCapabilities === undefined) {
assert_never(source.getChannelCapabilities);
}
if (source.searchChannels === undefined) {
assert_never(source.searchChannels);
}
if (source.isPlaylistUrl === undefined) {
assert_never(source.isPlaylistUrl);
}
if (source.getPlaylist === undefined) {
assert_never(source.getPlaylist);
}
if (source.searchPlaylists === undefined) {
assert_never(source.searchPlaylists);
}
if (source.getUserPlaylists === undefined) {
assert_never(source.getUserPlaylists);
}
if (source.getUserSubscriptions === undefined) {
assert_never(source.getUserSubscriptions);
}
if (source.getPlaybackTracker === undefined) {
assert_never(source.getPlaybackTracker);
}
if (IS_TESTING) {
}
}
//#endregion
//#region enable
function enable(conf, settings, savedState) {
if (IS_TESTING) {
log("IS_TESTING true");
log("logging configuration");
log(conf);
log("logging settings");
log(settings);
log("logging savedState");
log(savedState);
}
if (savedState !== null) {
const state = JSON.parse(savedState);
local_state = state;
// the token stored in state might be old
check_and_update_token();
}
else {
const home_page = "https://open.spotify.com";
const token_regex = /<script id="config" data-testid="config" type="application\/json">({.*?})<\/script><script id="session" data-testid="session" type="application\/json">({.*?})<\/script>/;
const web_player_js_regex = /https:\/\/open\.spotifycdn\.com\/cdn\/build\/web-player\/web-player\..{8}\.js/;
// use the authenticated client to get a logged in bearer token
const html = local_http.GET(home_page, {}, true).body;
const web_player_js_match_result = html.match(web_player_js_regex);
if (web_player_js_match_result === null || web_player_js_match_result[0] === undefined) {
throw new ScriptException("regex error");
}
const token_match_result = html.match(token_regex);
if (token_match_result === null || token_match_result[1] === undefined || token_match_result[2] === undefined) {
throw new ScriptException("regex error");
}
const user_data = JSON.parse(token_match_result[1]);
const token_response = JSON.parse(token_match_result[2]);
const bearer_token = token_response.accessToken;
// download license uri and get logged in user
const get_license_url_url = "https://gue1-spclient.spotify.com/melody/v1/license_url?keysystem=com.widevine.alpha&sdk_name=harmony&sdk_version=4.41.0";
const profile_attributes_url = "https://api-partner.spotify.com/pathfinder/v1/query?operationName=profileAttributes&variables=%7B%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%2253bcb064f6cd18c23f752bc324a791194d20df612d8e1239c735144ab0399ced%22%7D%7D";
const web_player_js_url = web_player_js_match_result[0];
const responses = local_http
.batch()
.GET(get_license_url_url, { Authorization: `Bearer ${bearer_token}` }, false)
.GET(profile_attributes_url, { Authorization: `Bearer ${bearer_token}` }, false)
.GET(web_player_js_url, { Authorization: `Bearer ${bearer_token}` }, false)
if (responses[0] === undefined || responses[1] === undefined || responses[2] === undefined) {
throw new ScriptException("unreachable");
}
const get_license_response = JSON.parse(responses[0].body);
const license_uri = `https://gue1-spclient.spotify.com/${get_license_response.uri}`;
const profile_attributes_response = JSON.parse(responses[1].body);
const feature_version_match_result = responses[2].body.match(/"(web-player_(.*?))"/);
if (feature_version_match_result === null) {
throw new ScriptException("regex error");
}
const feature_version = feature_version_match_result[1];
if (feature_version === undefined) {
throw new ScriptException("regex error");
}
expiration_timestamp_ms: token_response.accessTokenExpirationTimestampMs,
Loading
Loading full blame...