diff --git a/README.md b/README.md
index f4cc63aa7a5718f9aac057ed476e1e5d22103825..d8b7158867447a9cb3066bf45609dab18b04aa1d 100644
--- a/README.md
+++ b/README.md
@@ -2,5 +2,13 @@
 1.  `npm run npm-dev` or `bun run bun-dev`
 2.  load `BiliBiliConfig.json` into Grayjay
 
+## TO-DO
+- [ ]   check that share urls/uris work and share into the spotify app
+
 ## Grayjay Bugs
 - [ ]   none
+
+
+
+
+
diff --git a/build/SpotifyConfig.json b/build/SpotifyConfig.json
index 68f3bc94e63761ebd8e521d4e30ec19075c7dd40..10df0b703793d9c2a3ef99efc3136e7e97487fd1 100644
--- a/build/SpotifyConfig.json
+++ b/build/SpotifyConfig.json
@@ -21,11 +21,12 @@
         "open.spotify.com",
         "spclient.wg.spotify.com",
         "gue1-spclient.spotify.com",
-        "seektables.scdn.co"
+        "seektables.scdn.co",
+        "api-partner.spotify.com"
     ],
     "authentication": {
 		"loginUrl": "https://accounts.spotify.com/en/login",
-		"cookiesToFind": ["SESSDATA"],
+		"cookiesToFind": ["sp_dc"],
 		"userAgent": "Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.230 Mobile Safari/537.36"
 	}
 }
diff --git a/build/SpotifyScript.js b/build/SpotifyScript.js
index 5cc2ec1b898c0ea1982b6ba6ad3e0b099eab1109..34b9ecccd63ecfd7c77fa50aa93c6907cc5e95ff 100644
--- a/build/SpotifyScript.js
+++ b/build/SpotifyScript.js
@@ -1,10 +1,11 @@
-const SONG_REGEX = /^https:\/\/open\.spotify\.com\/track\/([a-zA-Z0-9]*)($|\/)/;
+const CONTENT_REGEX = /^https:\/\/open\.spotify\.com\/(track|episode)\/([a-zA-Z0-9]*)($|\/)/;
 const SONG_URL_PREFIX = "https://open.spotify.com/track/";
 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 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
@@ -13,6 +14,7 @@ Type.Order.Views = "Most played";
 Type.Order.Favorites = "Most favorited";
 /** State */
 let local_state;
+//#endregion
 //#region source methods
 source.enable = enable;
 source.disable = disable;
@@ -95,6 +97,8 @@ if (IS_TESTING) {
     }
 }
 */
+//#endregion
+//#region enable
 function enable(conf, settings, savedState) {
     if (IS_TESTING) {
         log("IS_TESTING true");
@@ -110,10 +114,12 @@ function enable(conf, settings, savedState) {
         local_state = state;
     }
     else {
-        const song_uri_id = "6XXxKsu3RJeN3ZvbMYrgQW";
-        const song_url = `${SONG_URL_PREFIX}${song_uri_id}`;
-        const song_html_regex = /<script id="session" data-testid="session" type="application\/json">({.*?})<\/script><script id="features" type="application\/json">/;
-        const match_result = local_http.GET(song_url, {}, false).body.match(song_html_regex);
+        // download bearer token
+        const homepage_url = "https://open.spotify.com";
+        const bearer_token_regex = /<script id="session" data-testid="session" type="application\/json">({.*?})<\/script><script id="features" type="application\/json">/;
+        // use the authenticated client to get a logged in bearer token
+        const homepage_response = local_http.GET(homepage_url, {}, true);
+        const match_result = homepage_response.body.match(bearer_token_regex);
         if (match_result === null) {
             throw new ScriptException("regex error");
         }
@@ -122,19 +128,31 @@ function enable(conf, settings, savedState) {
             throw new ScriptException("regex error");
         }
         const token_response = JSON.parse(maybe_json);
-        local_state = { bearer_token: token_response.accessToken };
+        const bearer_token = token_response.accessToken;
+        // download license uri
+        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 get_license_url_response = local_http.GET(get_license_url_url, { Authorization: `Bearer ${bearer_token}` }, false);
+        const get_license_response = JSON.parse(get_license_url_response.body);
+        const license_uri = `https://gue1-spclient.spotify.com/${get_license_response.uri}`;
+        local_state = {
+            bearer_token,
+            license_uri: license_uri
+        };
     }
 }
+//#endregion
 function disable() {
-    log("BiliBili log: disabling");
+    log("Spotify log: disabling");
 }
 function saveState() {
     return JSON.stringify(local_state);
 }
+//#region home
 function getHome() {
     const song_uri_id = "6XXxKsu3RJeN3ZvbMYrgQW";
     const song_url = `${SONG_URL_PREFIX}${song_uri_id}`;
-    const song_metadata_response = get_song_metadata(song_uri_id);
+    const { url: metadata_url, headers: metadata_headers } = song_metadata_args(song_uri_id);
+    const song_metadata_response = JSON.parse(local_http.GET(metadata_url, metadata_headers, false).body);
     const first_artist = song_metadata_response.artist[0];
     if (first_artist === undefined) {
         throw new ScriptException("missing artist");
@@ -156,91 +174,246 @@ function getHome() {
         })];
     return new VideoPager(songs, false);
 }
+//#endregion
+//#region content
 // https://open.spotify.com/track/6XXxKsu3RJeN3ZvbMYrgQW
+// https://open.spotify.com/episode/3Z88ZE0i3L7AIrymrBwtqg
 function isContentDetailsUrl(url) {
-    return SONG_REGEX.test(url);
+    return CONTENT_REGEX.test(url);
 }
 function getContentDetails(url) {
-    const match_result = url.match(SONG_REGEX);
+    if (!bridge.isLoggedIn()) {
+        throw new LoginRequiredException("login to listen to songs");
+    }
+    const match_result = url.match(CONTENT_REGEX);
     if (match_result === null) {
         throw new ScriptException("regex error");
     }
-    const maybe_song_uri_id = match_result[1];
-    if (maybe_song_uri_id === undefined) {
+    const maybe_content_type = match_result[1];
+    if (maybe_content_type === undefined) {
         throw new ScriptException("regex error");
     }
-    const song_url = `${SONG_URL_PREFIX}${maybe_song_uri_id}`;
-    const song_metadata_response = get_song_metadata(maybe_song_uri_id);
-    const first_artist = song_metadata_response.artist[0];
-    if (first_artist === undefined) {
-        throw new ScriptException("missing artist");
+    const content_type = maybe_content_type;
+    const content_uri_id = match_result[2];
+    if (content_uri_id === undefined) {
+        throw new ScriptException("regex error");
     }
-    const format = "MP4_128";
-    const maybe_file_id = song_metadata_response.file.find(function (file) { return file.format === format; })?.file_id;
-    if (maybe_file_id === undefined) {
-        throw new ScriptException("missing expected format");
+    switch (content_type) {
+        case "track": {
+            const song_url = `${SONG_URL_PREFIX}${content_uri_id}`;
+            const { url: metadata_url, headers: metadata_headers } = song_metadata_args(content_uri_id);
+            const song_metadata_response = JSON.parse(local_http.GET(metadata_url, metadata_headers, false).body);
+            const first_artist = song_metadata_response.artist[0];
+            if (first_artist === undefined) {
+                throw new ScriptException("missing artist");
+            }
+            const artist_url = "https://open.spotify.com/artist/6vWDO969PvNqNYHIOW5v0m";
+            const format = is_premium() ? "MP4_256" : "MP4_128";
+            const maybe_file_id = song_metadata_response.file.find(function (file) { return file.format === format; })?.file_id;
+            if (maybe_file_id === undefined) {
+                throw new ScriptException("missing expected format");
+            }
+            const { url, headers } = file_manifest_args(maybe_file_id);
+            const file_manifest = JSON.parse(local_http.GET(url, headers, false).body);
+            const duration = song_metadata_response.duration / 1000;
+            const file_url = file_manifest.cdnurl[0];
+            if (file_url === undefined) {
+                throw new ScriptException("unreachable");
+            }
+            const audio_sources = [new AudioUrlWidevineSource({
+                    //audio/mp4; codecs="mp4a.40.2
+                    name: format,
+                    bitrate: HARDCODED_ZERO,
+                    container: "audio/mp4",
+                    codecs: "mp4a.40.2",
+                    duration,
+                    url: file_url,
+                    language: Language.UNKNOWN,
+                    bearerToken: local_state.bearer_token,
+                    licenseUri: local_state.license_uri
+                })];
+            return new PlatformVideoDetails({
+                id: new PlatformID(PLATFORM, content_uri_id, plugin.config.id),
+                name: song_metadata_response.name,
+                author: new PlatformAuthorLink(new PlatformID(PLATFORM, first_artist.gid, plugin.config.id), first_artist.name, artist_url),
+                url: song_url,
+                thumbnails: new Thumbnails(song_metadata_response.album.cover_group.image.map(function (image) {
+                    return new Thumbnail(`${IMAGE_URL_PREFIX}${image.file_id}`, image.height);
+                })),
+                duration,
+                viewCount: HARDCODED_ZERO,
+                isLive: false,
+                shareUrl: song_metadata_response.canonical_uri,
+                // readonly uploadDate?: number
+                description: HARDCODED_EMPTY_STRING,
+                video: new UnMuxVideoSourceDescriptor([], audio_sources),
+                rating: new RatingLikes(HARDCODED_ZERO)
+                // readonly subtitles?: ISubtitleSource[]
+            });
+        }
+        case "episode": {
+            const episode_url = `https://open.spotify.com/episode/${content_uri_id}`;
+            const { url: transcript_url, headers: transcript_headers } = transcript_args(content_uri_id);
+            const { url, headers } = episode_metadata_args(content_uri_id);
+            const responses = local_http.batch()
+                .GET(transcript_url, transcript_headers, false)
+                .GET(url, headers, false)
+                .execute();
+            if (responses[0] === undefined || responses[1] === undefined) {
+                throw new ScriptException("unreachable");
+            }
+            const transcript_response = JSON.parse(responses[0].body);
+            const episode_metadata_response = JSON.parse(responses[1].body);
+            const format = "MP4_128";
+            const maybe_file_id = episode_metadata_response.data.episodeUnionV2.audio.items.find(function (file) { return file.format === format; })?.fileId;
+            if (maybe_file_id === undefined) {
+                throw new ScriptException("missing expected format");
+            }
+            const { url: manifest_url, headers: manifest_headers } = file_manifest_args(maybe_file_id);
+            const file_manifest = JSON.parse(local_http.GET(manifest_url, manifest_headers, false).body);
+            const duration = episode_metadata_response.data.episodeUnionV2.duration.totalMilliseconds / 1000;
+            const file_url = file_manifest.cdnurl[0];
+            if (file_url === undefined) {
+                throw new ScriptException("unreachable");
+            }
+            const codecs = "mp4a.40.2";
+            const audio_sources = [new AudioUrlWidevineSource({
+                    //audio/mp4; codecs="mp4a.40.2
+                    name: codecs,
+                    bitrate: 128000,
+                    container: "audio/mp4",
+                    codecs,
+                    duration,
+                    url: file_url,
+                    language: Language.UNKNOWN,
+                    bearerToken: local_state.bearer_token,
+                    licenseUri: local_state.license_uri
+                })];
+            const subtitle_name = function () {
+                switch (transcript_response.language) {
+                    case "en":
+                        return "English";
+                    default:
+                        throw assert_no_fall_through(transcript_response.language, "unreachable");
+                }
+            }();
+            let vtt_text = `WEBVTT ${subtitle_name}\n`;
+            vtt_text += "\n";
+            transcript_response.section.forEach(function (section, index) {
+                if ("title" in section) {
+                    return;
+                }
+                const next = transcript_response.section[index + 1];
+                let end = next?.startMs;
+                if (end === undefined) {
+                    end = episode_metadata_response.data.episodeUnionV2.duration.totalMilliseconds;
+                }
+                vtt_text += `${milliseconds_to_WebVTT_timestamp(section.startMs)} --> ${milliseconds_to_WebVTT_timestamp(end)}\n`;
+                vtt_text += `${section.text.sentence.text}\n`;
+                vtt_text += "\n";
+            });
+            return new PlatformVideoDetails({
+                id: new PlatformID(PLATFORM, content_uri_id, plugin.config.id),
+                name: episode_metadata_response.data.episodeUnionV2.name,
+                author: EMPTY_AUTHOR,
+                url: episode_url,
+                thumbnails: new Thumbnails(episode_metadata_response.data.episodeUnionV2.coverArt.sources.map(function (image) {
+                    return new Thumbnail(image.url, image.height);
+                })),
+                duration,
+                viewCount: HARDCODED_ZERO,
+                isLive: false,
+                shareUrl: episode_metadata_response.data.episodeUnionV2.uri,
+                uploadDate: new Date(episode_metadata_response.data.episodeUnionV2.releaseDate.isoString).getTime() / 1000,
+                description: episode_metadata_response.data.episodeUnionV2.htmlDescription,
+                video: new UnMuxVideoSourceDescriptor([], audio_sources),
+                rating: new RatingLikes(HARDCODED_ZERO),
+                subtitles: [{
+                        url: episode_url,
+                        name: subtitle_name,
+                        getSubtitles() {
+                            return vtt_text;
+                        },
+                        format: "text/vtt",
+                    }]
+            });
+        }
+        default:
+            throw assert_no_fall_through(content_type, "unreachable");
     }
+}
+function transcript_args(episode_uri_id) {
+    const transcript_url_prefix = "https://spclient.wg.spotify.com/transcript-read-along/v2/episode/";
+    const url = new URL(`${transcript_url_prefix}${episode_uri_id}`);
+    url.searchParams.set("format", "json");
+    return {
+        url: url.toString(),
+        headers: { Authorization: `Bearer ${local_state.bearer_token}` }
+    };
+}
+function file_manifest_args(file_id) {
     const file_manifest_url_prefix = "https://gue1-spclient.spotify.com/storage-resolve/v2/files/audio/interactive/10/";
     const file_manifest_params = "?product=9&alt=json";
-    const file_manifest = JSON.parse(local_http.GET(`${file_manifest_url_prefix}${maybe_file_id}${file_manifest_params}`, { Authorization: `Bearer ${local_state.bearer_token}` }, false).body);
-    log(file_manifest);
-    const duration = song_metadata_response.duration / 1000;
-    const audio_sources = file_manifest.cdnurl.map(function (url) {
-        return new AudioUrlSource({
-            //audio/mp4; codecs="mp4a.40.2
-            name: format,
-            bitrate: HARDCODED_ZERO,
-            container: "audio/mp4",
-            codecs: "mp4a.40.2",
-            duration,
-            url,
-            language: Language.UNKNOWN,
-        });
+    return {
+        url: `${file_manifest_url_prefix}${file_id}${file_manifest_params}`,
+        headers: { Authorization: `Bearer ${local_state.bearer_token}` }
+    };
+}
+function episode_metadata_args(episode_uri_id) {
+    const episode_metadata_url_prefix = "https://api-partner.spotify.com/pathfinder/v1/query";
+    const variables = JSON.stringify({
+        uri: `spotify:episode:${episode_uri_id}`
     });
-    //https://seektables.scdn.co/seektable/4c652e57fd36f84d77af2b9d1d1332327a8fd774.json
-    const seektable_url_prefix = "https://seektables.scdn.co/seektable/";
-    const seektable_response = JSON.parse(local_http.GET(`${seektable_url_prefix}${maybe_file_id}.json`, {}, false).body);
-    log(seektable_response);
-    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 get_license_response = JSON.parse(local_http.GET(get_license_url_url, { Authorization: `Bearer ${local_state.bearer_token}` }, false).body);
-    log(get_license_response);
-    const license_url = `https://gue1-spclient.spotify.com/${get_license_response.uri}`;
-    log(license_url);
-    return new PlatformVideoDetails({
-        id: new PlatformID(PLATFORM, maybe_song_uri_id, plugin.config.id),
-        name: song_metadata_response.name,
-        author: new PlatformAuthorLink(new PlatformID(PLATFORM, first_artist.gid, plugin.config.id), first_artist.name, "https://open.spotify.com/artist/6vWDO969PvNqNYHIOW5v0m"),
-        url: song_url,
-        thumbnails: new Thumbnails(song_metadata_response.album.cover_group.image.map(function (image) {
-            return new Thumbnail(`${IMAGE_URL_PREFIX}${image.file_id}`, image.height);
-        })),
-        duration,
-        viewCount: HARDCODED_ZERO,
-        isLive: false,
-        shareUrl: song_metadata_response.canonical_uri,
-        // readonly uploadDate?: number
-        description: HARDCODED_EMPTY_STRING,
-        video: new UnMuxVideoSourceDescriptor([], audio_sources),
-        // readonly live?: IVideoSource
-        rating: new RatingLikes(HARDCODED_ZERO)
-        // readonly subtitles?: ISubtitleSource[]
+    const extensions = JSON.stringify({
+        persistedQuery: {
+            version: 1,
+            sha256Hash: "9697538fe993af785c10725a40bb9265a20b998ccd2383bd6f586e01303824e9"
+        }
     });
+    const url = new URL(episode_metadata_url_prefix);
+    url.searchParams.set("operationName", "getEpisodeOrChapter");
+    url.searchParams.set("variables", variables);
+    url.searchParams.set("extensions", extensions);
+    return { url: url.toString(), headers: { Authorization: `Bearer ${local_state.bearer_token}` } };
 }
-function get_song_metadata(song_uri_id) {
+function song_metadata_args(song_uri_id) {
     const song_metadata_url = "https://spclient.wg.spotify.com/metadata/4/track/";
-    const song_metadata_response = JSON.parse(local_http.GET(`${song_metadata_url}${get_gid(song_uri_id)}`, {
-        Authorization: `Bearer ${local_state.bearer_token}`,
-        Accept: "application/json"
-    }, false).body);
-    return song_metadata_response;
+    return {
+        url: `${song_metadata_url}${get_gid(song_uri_id)}`,
+        headers: {
+            Authorization: `Bearer ${local_state.bearer_token}`,
+            Accept: "application/json"
+        }
+    };
+}
+//#endregion
+//#region utilities
+/**
+ * Converts seconds to the timestamp format used in WebVTT
+ * @param seconds
+ * @returns
+ */
+function milliseconds_to_WebVTT_timestamp(milliseconds) {
+    return new Date(milliseconds).toISOString().substring(11, 23);
+}
+function assert_never(value) {
+    log(value);
+}
+function log_passthrough(value) {
+    log(value);
+    return value;
+}
+function assert_no_fall_through(value, exception_message) {
+    log(["Spotify log:", value]);
+    if (exception_message !== undefined) {
+        return new ScriptException(exception_message);
+    }
+    return;
+}
+//#endregion
+function is_premium() {
+    return false;
 }
-// function assert_never(value: never) {
-//     log(value)
-// }
-// function log_passthrough<T>(value: T): T {
-//     log(value)
-//     return value
-// }
 // https://open.spotifycdn.com/cdn/build/web-player/vendor~web-player.391a2438.js
 const Z = "0123456789abcdef";
 const Q = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
@@ -305,5 +478,7 @@ function get_gid(song_uri_id) {
             c ? null : ee[s >>> 24] + ee[s >>> 16 & 255] + ee[s >>> 8 & 255] + ee[255 & s] + ee[a >>> 24] + ee[a >>> 16 & 255] + ee[a >>> 8 & 255] + ee[255 & a] + ee[r >>> 24] + ee[r >>> 16 & 255] + ee[r >>> 8 & 255] + ee[255 & r] + ee[o >>> 24] + ee[o >>> 16 & 255] + ee[o >>> 8 & 255] + ee[255 & o];
     }(song_uri_id) : song_uri_id;
 }
-// export { get_gid };
+// export statements are removed during build step
+// used for unit testing in SpotifyScript.test.ts
+// export { get_gid, assert_never, log_passthrough };
 //# sourceMappingURL=http://localhost:8080/SpotifyScript.js.map
\ No newline at end of file
diff --git a/build/SpotifyScript.js.map b/build/SpotifyScript.js.map
index 6f8f817d6fdf3679bf335e0444c394bff435cc91..33885938a918941a11d10f677c0afc6fd0c64c5e 100644
--- a/build/SpotifyScript.js.map
+++ b/build/SpotifyScript.js.map
@@ -1 +1 @@
-{"version":3,"file":"SpotifyScript.js","sourceRoot":"http://localhost:8080/","sources":["SpotifyScript.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"SpotifyScript.js","sourceRoot":"http://localhost:8080/","sources":["SpotifyScript.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/build/SpotifyScript.ts b/build/SpotifyScript.ts
index f7be22792c3f03bc2e55fdde8caa5d3ac504a8e9..318168054e1285ee43bed60b5d9be7fe683a786f 100644
--- a/build/SpotifyScript.ts
+++ b/build/SpotifyScript.ts
@@ -1,14 +1,17 @@
+//#region constants
 import {
-    FileManifestResponse,
-    GetLicenseResponse,
-    SeektableResponse,
-    Settings,
-    SongMetadataResponse,
-    // SpotifySource,
-    State
+    type ContentType,
+    type EpisodeMetadataResponse,
+    type FileManifestResponse,
+    type GetLicenseResponse,
+    type Settings,
+    type SongMetadataResponse,
+    // type SpotifySource,
+    type State,
+    type TranscriptResponse,
 } from "./types.js"
 
-const SONG_REGEX = /^https:\/\/open\.spotify\.com\/track\/([a-zA-Z0-9]*)($|\/)/
+const CONTENT_REGEX = /^https:\/\/open\.spotify\.com\/(track|episode)\/([a-zA-Z0-9]*)($|\/)/
 const SONG_URL_PREFIX = "https://open.spotify.com/track/" as const
 const IMAGE_URL_PREFIX = "https://i.scdn.co/image/" as const
 
@@ -17,6 +20,7 @@ const PLATFORM = "Spotify" as const
 
 const HARDCODED_ZERO = 0 as const
 const HARDCODED_EMPTY_STRING = "" as const
+const EMPTY_AUTHOR = new PlatformAuthorLink(new PlatformID(PLATFORM, "", plugin.config.id), "", "")
 
 const local_http = http
 // const local_utility = utility
@@ -28,6 +32,7 @@ Type.Order.Favorites = "Most favorited"
 
 /** State */
 let local_state: State
+//#endregion
 
 //#region source methods
 source.enable = enable
@@ -119,7 +124,9 @@ if (IS_TESTING) {
     }
 }
 */
+//#endregion
 
+//#region enable
 function enable(conf: SourceConfig, settings: Settings, savedState: string | null) {
     if (IS_TESTING) {
         log("IS_TESTING true")
@@ -134,10 +141,14 @@ function enable(conf: SourceConfig, settings: Settings, savedState: string | nul
         const state: State = JSON.parse(savedState)
         local_state = state
     } else {
-        const song_uri_id = "6XXxKsu3RJeN3ZvbMYrgQW"
-        const song_url = `${SONG_URL_PREFIX}${song_uri_id}`
-        const song_html_regex = /<script id="session" data-testid="session" type="application\/json">({.*?})<\/script><script id="features" type="application\/json">/
-        const match_result = local_http.GET(song_url, {}, false).body.match(song_html_regex)
+        // download bearer token
+        const homepage_url = "https://open.spotify.com"
+        const bearer_token_regex = /<script id="session" data-testid="session" type="application\/json">({.*?})<\/script><script id="features" type="application\/json">/
+
+        // use the authenticated client to get a logged in bearer token
+        const homepage_response = local_http.GET(homepage_url, {}, true)
+
+        const match_result = homepage_response.body.match(bearer_token_regex)
         if (match_result === null) {
             throw new ScriptException("regex error")
         }
@@ -146,23 +157,45 @@ function enable(conf: SourceConfig, settings: Settings, savedState: string | nul
             throw new ScriptException("regex error")
         }
         const token_response: { accessToken: string } = JSON.parse(maybe_json)
-        local_state = { bearer_token: token_response.accessToken }
+        const bearer_token = token_response.accessToken
+
+
+        // download license uri
+        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 get_license_url_response = local_http.GET(
+            get_license_url_url,
+            { Authorization: `Bearer ${bearer_token}` },
+            false
+        )
+        const get_license_response: GetLicenseResponse = JSON.parse(
+            get_license_url_response.body
+        )
+        const license_uri = `https://gue1-spclient.spotify.com/${get_license_response.uri}`
+
+
+        local_state = {
+            bearer_token,
+            license_uri: license_uri
+        }
     }
 }
+//#endregion
 
 function disable() {
-    log("BiliBili log: disabling")
+    log("Spotify log: disabling")
 }
 
 function saveState() {
     return JSON.stringify(local_state)
 }
 
+//#region home
 function getHome() {
     const song_uri_id = "6XXxKsu3RJeN3ZvbMYrgQW"
     const song_url = `${SONG_URL_PREFIX}${song_uri_id}`
 
-    const song_metadata_response: SongMetadataResponse = get_song_metadata(song_uri_id)
+    const { url: metadata_url, headers: metadata_headers } = song_metadata_args(song_uri_id)
+    const song_metadata_response: SongMetadataResponse = JSON.parse(local_http.GET(metadata_url, metadata_headers, false).body)
     const first_artist = song_metadata_response.artist[0]
     if (first_artist === undefined) {
         throw new ScriptException("missing artist")
@@ -184,136 +217,282 @@ function getHome() {
     })]
     return new VideoPager(songs, false)
 }
+//#endregion
 
+//#region content
 // https://open.spotify.com/track/6XXxKsu3RJeN3ZvbMYrgQW
+// https://open.spotify.com/episode/3Z88ZE0i3L7AIrymrBwtqg
 function isContentDetailsUrl(url: string) {
-    return SONG_REGEX.test(url)
+    return CONTENT_REGEX.test(url)
 }
 
 function getContentDetails(url: string) {
-    const match_result = url.match(SONG_REGEX)
+    if (!bridge.isLoggedIn()) {
+        throw new LoginRequiredException("login to listen to songs")
+    }
+    const match_result = url.match(CONTENT_REGEX)
     if (match_result === null) {
         throw new ScriptException("regex error")
     }
-    const maybe_song_uri_id = match_result[1]
-    if (maybe_song_uri_id === undefined) {
+    const maybe_content_type = match_result[1]
+    if (maybe_content_type === undefined) {
         throw new ScriptException("regex error")
     }
-    const song_url = `${SONG_URL_PREFIX}${maybe_song_uri_id}`
-
-    const song_metadata_response: SongMetadataResponse = get_song_metadata(maybe_song_uri_id)
-    const first_artist = song_metadata_response.artist[0]
-    if (first_artist === undefined) {
-        throw new ScriptException("missing artist")
+    const content_type: ContentType = maybe_content_type as ContentType
+    const content_uri_id = match_result[2]
+    if (content_uri_id === undefined) {
+        throw new ScriptException("regex error")
     }
+    switch (content_type) {
+        case "track": {
+            const song_url = `${SONG_URL_PREFIX}${content_uri_id}`
+
+            const { url: metadata_url, headers: metadata_headers } = song_metadata_args(content_uri_id)
+            const song_metadata_response: SongMetadataResponse = JSON.parse(local_http.GET(metadata_url, metadata_headers, false).body)
+            const first_artist = song_metadata_response.artist[0]
+            if (first_artist === undefined) {
+                throw new ScriptException("missing artist")
+            }
+            const artist_url = "https://open.spotify.com/artist/6vWDO969PvNqNYHIOW5v0m"
+
+            const format = is_premium() ? "MP4_256" : "MP4_128"
+
+            const maybe_file_id = song_metadata_response.file.find(function (file) { return file.format === format })?.file_id
+            if (maybe_file_id === undefined) {
+                throw new ScriptException("missing expected format")
+            }
+
+            const { url, headers } = file_manifest_args(maybe_file_id)
+            const file_manifest: FileManifestResponse = JSON.parse(local_http.GET(url, headers, false).body)
+
+            const duration = song_metadata_response.duration / 1000
+
+            const file_url = file_manifest.cdnurl[0]
+            if (file_url === undefined) {
+                throw new ScriptException("unreachable")
+            }
+            const audio_sources = [new AudioUrlWidevineSource({
+                //audio/mp4; codecs="mp4a.40.2
+                name: format,
+                bitrate: HARDCODED_ZERO,
+                container: "audio/mp4",
+                codecs: "mp4a.40.2",
+                duration,
+                url: file_url,
+                language: Language.UNKNOWN,
+                bearerToken: local_state.bearer_token,
+                licenseUri: local_state.license_uri
+            })]
+
+            return new PlatformVideoDetails({
+                id: new PlatformID(PLATFORM, content_uri_id, plugin.config.id),
+                name: song_metadata_response.name,
+                author: new PlatformAuthorLink(new PlatformID(PLATFORM, first_artist.gid, plugin.config.id), first_artist.name, artist_url),
+                url: song_url,
+                thumbnails: new Thumbnails(song_metadata_response.album.cover_group.image.map(function (image) {
+                    return new Thumbnail(`${IMAGE_URL_PREFIX}${image.file_id}`, image.height)
+                })),
+                duration,
+                viewCount: HARDCODED_ZERO,
+                isLive: false,
+                shareUrl: song_metadata_response.canonical_uri,
+                // readonly uploadDate?: number
+                description: HARDCODED_EMPTY_STRING,
+                video: new UnMuxVideoSourceDescriptor([], audio_sources),
+                rating: new RatingLikes(HARDCODED_ZERO)
+                // readonly subtitles?: ISubtitleSource[]
+            })
+        }
+
+        case "episode": {
+            const episode_url = `https://open.spotify.com/episode/${content_uri_id}`
+
+            const { url: transcript_url, headers: transcript_headers } = transcript_args(content_uri_id)
+            const { url, headers } = episode_metadata_args(content_uri_id)
+            const responses = local_http.batch()
+                .GET(transcript_url, transcript_headers, false)
+                .GET(url, headers, false)
+                .execute()
+            if (responses[0] === undefined || responses[1] === undefined) {
+                throw new ScriptException("unreachable")
+            }
+            const transcript_response: TranscriptResponse = JSON.parse(responses[0].body)
+            const episode_metadata_response: EpisodeMetadataResponse = JSON.parse(responses[1].body)
+
+            const format = "MP4_128"
+            const maybe_file_id = episode_metadata_response.data.episodeUnionV2.audio.items.find(function (file) { return file.format === format })?.fileId
+            if (maybe_file_id === undefined) {
+                throw new ScriptException("missing expected format")
+            }
+
+            const { url: manifest_url, headers: manifest_headers } = file_manifest_args(maybe_file_id)
+            const file_manifest: FileManifestResponse = JSON.parse(local_http.GET(manifest_url, manifest_headers, false).body)
+
+            const duration = episode_metadata_response.data.episodeUnionV2.duration.totalMilliseconds / 1000
+
+            const file_url = file_manifest.cdnurl[0]
+            if (file_url === undefined) {
+                throw new ScriptException("unreachable")
+            }
+            const codecs = "mp4a.40.2"
+            const audio_sources = [new AudioUrlWidevineSource({
+                //audio/mp4; codecs="mp4a.40.2
+                name: codecs,
+                bitrate: 128000,
+                container: "audio/mp4",
+                codecs,
+                duration,
+                url: file_url,
+                language: Language.UNKNOWN,
+                bearerToken: local_state.bearer_token,
+                licenseUri: local_state.license_uri
+            })]
+
+            const subtitle_name = function () {
+                switch (transcript_response.language) {
+                    case "en":
+                        return "English"
+                    default:
+                        throw assert_no_fall_through(transcript_response.language, "unreachable")
+                }
+            }()
+
+            let vtt_text = `WEBVTT ${subtitle_name}\n`
+            vtt_text += "\n"
+            transcript_response.section.forEach(function (section, index) {
+                if ("title" in section) {
+                    return
+                }
+                const next = transcript_response.section[index + 1]
+                let end = next?.startMs
+                if (end === undefined) {
+                    end = episode_metadata_response.data.episodeUnionV2.duration.totalMilliseconds
+                }
+                vtt_text += `${milliseconds_to_WebVTT_timestamp(section.startMs)} --> ${milliseconds_to_WebVTT_timestamp(end)}\n`
+                vtt_text += `${section.text.sentence.text}\n`
+                vtt_text += "\n"
+            })
+
+            return new PlatformVideoDetails({
+                id: new PlatformID(PLATFORM, content_uri_id, plugin.config.id),
+                name: episode_metadata_response.data.episodeUnionV2.name,
+                author: EMPTY_AUTHOR,
+                url: episode_url,
+                thumbnails: new Thumbnails(episode_metadata_response.data.episodeUnionV2.coverArt.sources.map(function (image) {
+                    return new Thumbnail(image.url, image.height)
+                })),
+                duration,
+                viewCount: HARDCODED_ZERO,
+                isLive: false,
+                shareUrl: episode_metadata_response.data.episodeUnionV2.uri,
+                uploadDate: new Date(episode_metadata_response.data.episodeUnionV2.releaseDate.isoString).getTime() / 1000,
+                description: episode_metadata_response.data.episodeUnionV2.htmlDescription,
+                video: new UnMuxVideoSourceDescriptor([], audio_sources),
+                rating: new RatingLikes(HARDCODED_ZERO),
+                subtitles: [{
+                    url: episode_url,
+                    name: subtitle_name,
+                    getSubtitles() {
+                        return vtt_text
+                    },
+                    format: "text/vtt",
+                }]
+            })
+        }
 
-    const format = "MP4_128"
+        default:
+            throw assert_no_fall_through(content_type, "unreachable")
+    }
+}
 
-    const maybe_file_id = song_metadata_response.file.find(function (file) { return file.format === format })?.file_id
-    if (maybe_file_id === undefined) {
-        throw new ScriptException("missing expected format")
+function transcript_args(episode_uri_id: string): { readonly url: string, readonly headers: { Authorization: string } } {
+    const transcript_url_prefix = "https://spclient.wg.spotify.com/transcript-read-along/v2/episode/"
+    const url = new URL(`${transcript_url_prefix}${episode_uri_id}`)
+    url.searchParams.set("format", "json")
+    return {
+        url: url.toString(),
+        headers: { Authorization: `Bearer ${local_state.bearer_token}` }
     }
+}
 
+function file_manifest_args(file_id: string): { readonly url: string, readonly headers: { Authorization: string } } {
     const file_manifest_url_prefix = "https://gue1-spclient.spotify.com/storage-resolve/v2/files/audio/interactive/10/"
     const file_manifest_params = "?product=9&alt=json"
-    const file_manifest: FileManifestResponse = JSON.parse(
-        local_http.GET(
-            `${file_manifest_url_prefix}${maybe_file_id}${file_manifest_params}`,
-            { Authorization: `Bearer ${local_state.bearer_token}` },
-            false
-        ).body
-    )
-
-    log(file_manifest)
-
-    const duration = song_metadata_response.duration / 1000
-
-    const audio_sources = file_manifest.cdnurl.map(function (url) {
-        return new AudioUrlSource({
-            //audio/mp4; codecs="mp4a.40.2
-            name: format,
-            bitrate: HARDCODED_ZERO,
-            container: "audio/mp4",
-            codecs: "mp4a.40.2",
-            duration,
-            url,
-            language: Language.UNKNOWN,
-        })
-    })
-
-    //https://seektables.scdn.co/seektable/4c652e57fd36f84d77af2b9d1d1332327a8fd774.json
-    const seektable_url_prefix = "https://seektables.scdn.co/seektable/"
-
-    const seektable_response: SeektableResponse = JSON.parse(
-        local_http.GET(
-            `${seektable_url_prefix}${maybe_file_id}.json`,
-            {},
-            false
-        ).body
-    )
-
-    log(seektable_response)
-
-    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 get_license_response: GetLicenseResponse = JSON.parse(
-        local_http.GET(
-            get_license_url_url,
-            { Authorization: `Bearer ${local_state.bearer_token}` },
-            false
-        ).body
-    )
-
-    log(get_license_response)
-
-    const license_url = `https://gue1-spclient.spotify.com/${get_license_response.uri}`
-
-    log(license_url)
+    return {
+        url: `${file_manifest_url_prefix}${file_id}${file_manifest_params}`,
+        headers: { Authorization: `Bearer ${local_state.bearer_token}` }
+    }
+}
 
-    return new PlatformVideoDetails({
-        id: new PlatformID(PLATFORM, maybe_song_uri_id, plugin.config.id),
-        name: song_metadata_response.name,
-        author: new PlatformAuthorLink(new PlatformID(PLATFORM, first_artist.gid, plugin.config.id), first_artist.name, "https://open.spotify.com/artist/6vWDO969PvNqNYHIOW5v0m"),
-        url: song_url,
-        thumbnails: new Thumbnails(song_metadata_response.album.cover_group.image.map(function (image) {
-            return new Thumbnail(`${IMAGE_URL_PREFIX}${image.file_id}`, image.height)
-        })),
-        duration,
-        viewCount: HARDCODED_ZERO,
-        isLive: false,
-        shareUrl: song_metadata_response.canonical_uri,
-        // readonly uploadDate?: number
-        description: HARDCODED_EMPTY_STRING,
-        video: new UnMuxVideoSourceDescriptor([], audio_sources),
-        // readonly live?: IVideoSource
-        rating: new RatingLikes(HARDCODED_ZERO)
-        // readonly subtitles?: ISubtitleSource[]
+function episode_metadata_args(episode_uri_id: string): { readonly url: string, readonly headers: { Authorization: string } } {
+    const episode_metadata_url_prefix = "https://api-partner.spotify.com/pathfinder/v1/query"
+    const variables = JSON.stringify({
+        uri: `spotify:episode:${episode_uri_id}`
+    })
+    const extensions = JSON.stringify({
+        persistedQuery: {
+            version: 1,
+            sha256Hash: "9697538fe993af785c10725a40bb9265a20b998ccd2383bd6f586e01303824e9"
+        }
     })
+    const url = new URL(episode_metadata_url_prefix)
+    url.searchParams.set("operationName", "getEpisodeOrChapter")
+    url.searchParams.set("variables", variables)
+    url.searchParams.set("extensions", extensions)
+    return { url: url.toString(), headers: { Authorization: `Bearer ${local_state.bearer_token}` } }
 }
 
-function get_song_metadata(song_uri_id: string): SongMetadataResponse {
+function song_metadata_args(song_uri_id: string): {
+    readonly url: string,
+    readonly headers: {
+        Authorization: string,
+        Accept: "application/json"
+    }
+} {
     const song_metadata_url = "https://spclient.wg.spotify.com/metadata/4/track/"
-    const song_metadata_response: SongMetadataResponse = JSON.parse(
-        local_http.GET(
-            `${song_metadata_url}${get_gid(song_uri_id)}`,
-            {
-                Authorization: `Bearer ${local_state.bearer_token}`,
-                Accept: "application/json"
-            },
-            false
-        ).body
-    )
-    return song_metadata_response
+    return {
+        url: `${song_metadata_url}${get_gid(song_uri_id)}`,
+        headers: {
+            Authorization: `Bearer ${local_state.bearer_token}`,
+            Accept: "application/json"
+        }
+    }
+}
+//#endregion
+
+//#region utilities
+/**
+ * Converts seconds to the timestamp format used in WebVTT
+ * @param seconds 
+ * @returns 
+ */
+function milliseconds_to_WebVTT_timestamp(milliseconds: number) {
+    return new Date(milliseconds).toISOString().substring(11, 23)
 }
 
-// function assert_never(value: never) {
-//     log(value)
-// }
+function assert_never(value: never) {
+    log(value)
+}
+
+function log_passthrough<T>(value: T): T {
+    log(value)
+    return value
+}
+function assert_no_fall_through(value: never): void
+function assert_no_fall_through(value: never, exception_message: string): ScriptException
+function assert_no_fall_through(value: never, exception_message?: string): ScriptException | undefined {
+    log(["Spotify log:", value])
+    if (exception_message !== undefined) {
+        return new ScriptException(exception_message)
+    }
+    return
+}
+//#endregion
+
+function is_premium(): boolean {
+    return false
+}
 
-// function log_passthrough<T>(value: T): T {
-//     log(value)
-//     return value
-// }
 
 // https://open.spotifycdn.com/cdn/build/web-player/vendor~web-player.391a2438.js
 const Z = "0123456789abcdef"
@@ -383,6 +562,10 @@ function get_gid(song_uri_id: string) {
     }(song_uri_id) : song_uri_id
 }
 
+// export statements are removed during build step
+// used for unit testing in SpotifyScript.test.ts
 // export {
-    get_gid
+    get_gid,
+    assert_never,
+    log_passthrough
 }
diff --git a/package-lock.json b/package-lock.json
index 65e22bb2bc489dc8a485c3657bf1adfbddddfc5c..84fd98a119a942d5f0cea36d5f0e498487146c82 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,10 +9,10 @@
             "version": "1.0.0",
             "license": "MPL-2.0",
             "devDependencies": {
-                "@grayjay/plugin": "gitlab:kaidelorenzo/grayjay-plugin-types#d898f236620e6b5b4d54053c1b15be24bd68a8c1",
+                "@grayjay/plugin": "gitlab:kaidelorenzo/grayjay-plugin-types#c9fcd5e27dd6f04b19e5ac0e03ee46a71f699dfc",
                 "@types/node": "^20.12.7",
                 "http-server": "^14.1.1",
-                "npm-check-updates": "^16.14.18"
+                "npm-check-updates": "^16.14.20"
             },
             "engines": {
                 "node": ">=20.0.0"
@@ -39,8 +39,8 @@
         },
         "node_modules/@grayjay/plugin": {
             "version": "1.0.0",
-            "resolved": "git+ssh://git@gitlab.com/kaidelorenzo/grayjay-plugin-types.git#d898f236620e6b5b4d54053c1b15be24bd68a8c1",
-            "integrity": "sha512-c7GSfB625zIWnxEjdP7PQr8Jalp9hla5h46I6cx9ircACAlWVM7MdtFcuJS+6/SIPvlDyLQz/3k512bqMYPDtg==",
+            "resolved": "git+ssh://git@gitlab.com/kaidelorenzo/grayjay-plugin-types.git#c9fcd5e27dd6f04b19e5ac0e03ee46a71f699dfc",
+            "integrity": "sha512-wvYWXupDyr4kKlTCJbuAuEvi48gRx/RpX6j31m4KeRukCBdV/QuHeS3L+GRVRXIU/ltBraEiNLo2CiMIxY2qiw==",
             "dev": true,
             "dependencies": {
                 "@types/sync-fetch": "^0.4.3",
@@ -3328,9 +3328,9 @@
             }
         },
         "node_modules/path-scurry/node_modules/lru-cache": {
-            "version": "10.2.1",
-            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.1.tgz",
-            "integrity": "sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==",
+            "version": "10.2.2",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+            "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
             "dev": true,
             "engines": {
                 "node": "14 || >=16.14"
diff --git a/package.json b/package.json
index d41d34c81290e3436a7d590622ae7d2cd58ad2f2..f1e2f1aebba9f24386a20317dab677c496b3971e 100644
--- a/package.json
+++ b/package.json
@@ -34,9 +34,9 @@
     },
     "type": "module",
     "devDependencies": {
-        "@grayjay/plugin": "gitlab:kaidelorenzo/grayjay-plugin-types#d898f236620e6b5b4d54053c1b15be24bd68a8c1",
+        "@grayjay/plugin": "gitlab:kaidelorenzo/grayjay-plugin-types#c9fcd5e27dd6f04b19e5ac0e03ee46a71f699dfc",
         "@types/node": "^20.12.7",
         "http-server": "^14.1.1",
-        "npm-check-updates": "^16.14.18"
+        "npm-check-updates": "^16.14.20"
     }
 }
diff --git a/src/SpotifyScript.ts b/src/SpotifyScript.ts
index b19a7c895380b3581889f96e834fb491df98ab8a..6fb3db001b3dbd6625e39d4ba79a13f2e7b68517 100644
--- a/src/SpotifyScript.ts
+++ b/src/SpotifyScript.ts
@@ -1,14 +1,17 @@
+//#region constants
 import {
-    FileManifestResponse,
-    GetLicenseResponse,
-    SeektableResponse,
-    Settings,
-    SongMetadataResponse,
-    // SpotifySource,
-    State
+    type ContentType,
+    type EpisodeMetadataResponse,
+    type FileManifestResponse,
+    type GetLicenseResponse,
+    type Settings,
+    type SongMetadataResponse,
+    // type SpotifySource,
+    type State,
+    type TranscriptResponse,
 } from "./types.js"
 
-const SONG_REGEX = /^https:\/\/open\.spotify\.com\/track\/([a-zA-Z0-9]*)($|\/)/
+const CONTENT_REGEX = /^https:\/\/open\.spotify\.com\/(track|episode)\/([a-zA-Z0-9]*)($|\/)/
 const SONG_URL_PREFIX = "https://open.spotify.com/track/" as const
 const IMAGE_URL_PREFIX = "https://i.scdn.co/image/" as const
 
@@ -17,6 +20,7 @@ const PLATFORM = "Spotify" as const
 
 const HARDCODED_ZERO = 0 as const
 const HARDCODED_EMPTY_STRING = "" as const
+const EMPTY_AUTHOR = new PlatformAuthorLink(new PlatformID(PLATFORM, "", plugin.config.id), "", "")
 
 const local_http = http
 // const local_utility = utility
@@ -28,6 +32,7 @@ Type.Order.Favorites = "Most favorited"
 
 /** State */
 let local_state: State
+//#endregion
 
 //#region source methods
 source.enable = enable
@@ -119,7 +124,9 @@ if (IS_TESTING) {
     }
 }
 */
+//#endregion
 
+//#region enable
 function enable(conf: SourceConfig, settings: Settings, savedState: string | null) {
     if (IS_TESTING) {
         log("IS_TESTING true")
@@ -134,10 +141,14 @@ function enable(conf: SourceConfig, settings: Settings, savedState: string | nul
         const state: State = JSON.parse(savedState)
         local_state = state
     } else {
-        const song_uri_id = "6XXxKsu3RJeN3ZvbMYrgQW"
-        const song_url = `${SONG_URL_PREFIX}${song_uri_id}`
-        const song_html_regex = /<script id="session" data-testid="session" type="application\/json">({.*?})<\/script><script id="features" type="application\/json">/
-        const match_result = local_http.GET(song_url, {}, false).body.match(song_html_regex)
+        // download bearer token
+        const homepage_url = "https://open.spotify.com"
+        const bearer_token_regex = /<script id="session" data-testid="session" type="application\/json">({.*?})<\/script><script id="features" type="application\/json">/
+
+        // use the authenticated client to get a logged in bearer token
+        const homepage_response = local_http.GET(homepage_url, {}, true)
+
+        const match_result = homepage_response.body.match(bearer_token_regex)
         if (match_result === null) {
             throw new ScriptException("regex error")
         }
@@ -146,23 +157,45 @@ function enable(conf: SourceConfig, settings: Settings, savedState: string | nul
             throw new ScriptException("regex error")
         }
         const token_response: { accessToken: string } = JSON.parse(maybe_json)
-        local_state = { bearer_token: token_response.accessToken }
+        const bearer_token = token_response.accessToken
+
+
+        // download license uri
+        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 get_license_url_response = local_http.GET(
+            get_license_url_url,
+            { Authorization: `Bearer ${bearer_token}` },
+            false
+        )
+        const get_license_response: GetLicenseResponse = JSON.parse(
+            get_license_url_response.body
+        )
+        const license_uri = `https://gue1-spclient.spotify.com/${get_license_response.uri}`
+
+
+        local_state = {
+            bearer_token,
+            license_uri: license_uri
+        }
     }
 }
+//#endregion
 
 function disable() {
-    log("BiliBili log: disabling")
+    log("Spotify log: disabling")
 }
 
 function saveState() {
     return JSON.stringify(local_state)
 }
 
+//#region home
 function getHome() {
     const song_uri_id = "6XXxKsu3RJeN3ZvbMYrgQW"
     const song_url = `${SONG_URL_PREFIX}${song_uri_id}`
 
-    const song_metadata_response: SongMetadataResponse = get_song_metadata(song_uri_id)
+    const { url: metadata_url, headers: metadata_headers } = song_metadata_args(song_uri_id)
+    const song_metadata_response: SongMetadataResponse = JSON.parse(local_http.GET(metadata_url, metadata_headers, false).body)
     const first_artist = song_metadata_response.artist[0]
     if (first_artist === undefined) {
         throw new ScriptException("missing artist")
@@ -184,136 +217,282 @@ function getHome() {
     })]
     return new VideoPager(songs, false)
 }
+//#endregion
 
+//#region content
 // https://open.spotify.com/track/6XXxKsu3RJeN3ZvbMYrgQW
+// https://open.spotify.com/episode/3Z88ZE0i3L7AIrymrBwtqg
 function isContentDetailsUrl(url: string) {
-    return SONG_REGEX.test(url)
+    return CONTENT_REGEX.test(url)
 }
 
 function getContentDetails(url: string) {
-    const match_result = url.match(SONG_REGEX)
+    if (!bridge.isLoggedIn()) {
+        throw new LoginRequiredException("login to listen to songs")
+    }
+    const match_result = url.match(CONTENT_REGEX)
     if (match_result === null) {
         throw new ScriptException("regex error")
     }
-    const maybe_song_uri_id = match_result[1]
-    if (maybe_song_uri_id === undefined) {
+    const maybe_content_type = match_result[1]
+    if (maybe_content_type === undefined) {
         throw new ScriptException("regex error")
     }
-    const song_url = `${SONG_URL_PREFIX}${maybe_song_uri_id}`
-
-    const song_metadata_response: SongMetadataResponse = get_song_metadata(maybe_song_uri_id)
-    const first_artist = song_metadata_response.artist[0]
-    if (first_artist === undefined) {
-        throw new ScriptException("missing artist")
+    const content_type: ContentType = maybe_content_type as ContentType
+    const content_uri_id = match_result[2]
+    if (content_uri_id === undefined) {
+        throw new ScriptException("regex error")
     }
+    switch (content_type) {
+        case "track": {
+            const song_url = `${SONG_URL_PREFIX}${content_uri_id}`
+
+            const { url: metadata_url, headers: metadata_headers } = song_metadata_args(content_uri_id)
+            const song_metadata_response: SongMetadataResponse = JSON.parse(local_http.GET(metadata_url, metadata_headers, false).body)
+            const first_artist = song_metadata_response.artist[0]
+            if (first_artist === undefined) {
+                throw new ScriptException("missing artist")
+            }
+            const artist_url = "https://open.spotify.com/artist/6vWDO969PvNqNYHIOW5v0m"
+
+            const format = is_premium() ? "MP4_256" : "MP4_128"
+
+            const maybe_file_id = song_metadata_response.file.find(function (file) { return file.format === format })?.file_id
+            if (maybe_file_id === undefined) {
+                throw new ScriptException("missing expected format")
+            }
+
+            const { url, headers } = file_manifest_args(maybe_file_id)
+            const file_manifest: FileManifestResponse = JSON.parse(local_http.GET(url, headers, false).body)
+
+            const duration = song_metadata_response.duration / 1000
+
+            const file_url = file_manifest.cdnurl[0]
+            if (file_url === undefined) {
+                throw new ScriptException("unreachable")
+            }
+            const audio_sources = [new AudioUrlWidevineSource({
+                //audio/mp4; codecs="mp4a.40.2
+                name: format,
+                bitrate: HARDCODED_ZERO,
+                container: "audio/mp4",
+                codecs: "mp4a.40.2",
+                duration,
+                url: file_url,
+                language: Language.UNKNOWN,
+                bearerToken: local_state.bearer_token,
+                licenseUri: local_state.license_uri
+            })]
+
+            return new PlatformVideoDetails({
+                id: new PlatformID(PLATFORM, content_uri_id, plugin.config.id),
+                name: song_metadata_response.name,
+                author: new PlatformAuthorLink(new PlatformID(PLATFORM, first_artist.gid, plugin.config.id), first_artist.name, artist_url),
+                url: song_url,
+                thumbnails: new Thumbnails(song_metadata_response.album.cover_group.image.map(function (image) {
+                    return new Thumbnail(`${IMAGE_URL_PREFIX}${image.file_id}`, image.height)
+                })),
+                duration,
+                viewCount: HARDCODED_ZERO,
+                isLive: false,
+                shareUrl: song_metadata_response.canonical_uri,
+                // readonly uploadDate?: number
+                description: HARDCODED_EMPTY_STRING,
+                video: new UnMuxVideoSourceDescriptor([], audio_sources),
+                rating: new RatingLikes(HARDCODED_ZERO)
+                // readonly subtitles?: ISubtitleSource[]
+            })
+        }
+
+        case "episode": {
+            const episode_url = `https://open.spotify.com/episode/${content_uri_id}`
+
+            const { url: transcript_url, headers: transcript_headers } = transcript_args(content_uri_id)
+            const { url, headers } = episode_metadata_args(content_uri_id)
+            const responses = local_http.batch()
+                .GET(transcript_url, transcript_headers, false)
+                .GET(url, headers, false)
+                .execute()
+            if (responses[0] === undefined || responses[1] === undefined) {
+                throw new ScriptException("unreachable")
+            }
+            const transcript_response: TranscriptResponse = JSON.parse(responses[0].body)
+            const episode_metadata_response: EpisodeMetadataResponse = JSON.parse(responses[1].body)
+
+            const format = "MP4_128"
+            const maybe_file_id = episode_metadata_response.data.episodeUnionV2.audio.items.find(function (file) { return file.format === format })?.fileId
+            if (maybe_file_id === undefined) {
+                throw new ScriptException("missing expected format")
+            }
+
+            const { url: manifest_url, headers: manifest_headers } = file_manifest_args(maybe_file_id)
+            const file_manifest: FileManifestResponse = JSON.parse(local_http.GET(manifest_url, manifest_headers, false).body)
+
+            const duration = episode_metadata_response.data.episodeUnionV2.duration.totalMilliseconds / 1000
+
+            const file_url = file_manifest.cdnurl[0]
+            if (file_url === undefined) {
+                throw new ScriptException("unreachable")
+            }
+            const codecs = "mp4a.40.2"
+            const audio_sources = [new AudioUrlWidevineSource({
+                //audio/mp4; codecs="mp4a.40.2
+                name: codecs,
+                bitrate: 128000,
+                container: "audio/mp4",
+                codecs,
+                duration,
+                url: file_url,
+                language: Language.UNKNOWN,
+                bearerToken: local_state.bearer_token,
+                licenseUri: local_state.license_uri
+            })]
+
+            const subtitle_name = function () {
+                switch (transcript_response.language) {
+                    case "en":
+                        return "English"
+                    default:
+                        throw assert_no_fall_through(transcript_response.language, "unreachable")
+                }
+            }()
+
+            let vtt_text = `WEBVTT ${subtitle_name}\n`
+            vtt_text += "\n"
+            transcript_response.section.forEach(function (section, index) {
+                if ("title" in section) {
+                    return
+                }
+                const next = transcript_response.section[index + 1]
+                let end = next?.startMs
+                if (end === undefined) {
+                    end = episode_metadata_response.data.episodeUnionV2.duration.totalMilliseconds
+                }
+                vtt_text += `${milliseconds_to_WebVTT_timestamp(section.startMs)} --> ${milliseconds_to_WebVTT_timestamp(end)}\n`
+                vtt_text += `${section.text.sentence.text}\n`
+                vtt_text += "\n"
+            })
+
+            return new PlatformVideoDetails({
+                id: new PlatformID(PLATFORM, content_uri_id, plugin.config.id),
+                name: episode_metadata_response.data.episodeUnionV2.name,
+                author: EMPTY_AUTHOR,
+                url: episode_url,
+                thumbnails: new Thumbnails(episode_metadata_response.data.episodeUnionV2.coverArt.sources.map(function (image) {
+                    return new Thumbnail(image.url, image.height)
+                })),
+                duration,
+                viewCount: HARDCODED_ZERO,
+                isLive: false,
+                shareUrl: episode_metadata_response.data.episodeUnionV2.uri,
+                uploadDate: new Date(episode_metadata_response.data.episodeUnionV2.releaseDate.isoString).getTime() / 1000,
+                description: episode_metadata_response.data.episodeUnionV2.htmlDescription,
+                video: new UnMuxVideoSourceDescriptor([], audio_sources),
+                rating: new RatingLikes(HARDCODED_ZERO),
+                subtitles: [{
+                    url: episode_url,
+                    name: subtitle_name,
+                    getSubtitles() {
+                        return vtt_text
+                    },
+                    format: "text/vtt",
+                }]
+            })
+        }
 
-    const format = "MP4_128"
+        default:
+            throw assert_no_fall_through(content_type, "unreachable")
+    }
+}
 
-    const maybe_file_id = song_metadata_response.file.find(function (file) { return file.format === format })?.file_id
-    if (maybe_file_id === undefined) {
-        throw new ScriptException("missing expected format")
+function transcript_args(episode_uri_id: string): { readonly url: string, readonly headers: { Authorization: string } } {
+    const transcript_url_prefix = "https://spclient.wg.spotify.com/transcript-read-along/v2/episode/"
+    const url = new URL(`${transcript_url_prefix}${episode_uri_id}`)
+    url.searchParams.set("format", "json")
+    return {
+        url: url.toString(),
+        headers: { Authorization: `Bearer ${local_state.bearer_token}` }
     }
+}
 
+function file_manifest_args(file_id: string): { readonly url: string, readonly headers: { Authorization: string } } {
     const file_manifest_url_prefix = "https://gue1-spclient.spotify.com/storage-resolve/v2/files/audio/interactive/10/"
     const file_manifest_params = "?product=9&alt=json"
-    const file_manifest: FileManifestResponse = JSON.parse(
-        local_http.GET(
-            `${file_manifest_url_prefix}${maybe_file_id}${file_manifest_params}`,
-            { Authorization: `Bearer ${local_state.bearer_token}` },
-            false
-        ).body
-    )
-
-    log(file_manifest)
-
-    const duration = song_metadata_response.duration / 1000
-
-    const audio_sources = file_manifest.cdnurl.map(function (url) {
-        return new AudioUrlSource({
-            //audio/mp4; codecs="mp4a.40.2
-            name: format,
-            bitrate: HARDCODED_ZERO,
-            container: "audio/mp4",
-            codecs: "mp4a.40.2",
-            duration,
-            url,
-            language: Language.UNKNOWN,
-        })
-    })
-
-    //https://seektables.scdn.co/seektable/4c652e57fd36f84d77af2b9d1d1332327a8fd774.json
-    const seektable_url_prefix = "https://seektables.scdn.co/seektable/"
-
-    const seektable_response: SeektableResponse = JSON.parse(
-        local_http.GET(
-            `${seektable_url_prefix}${maybe_file_id}.json`,
-            {},
-            false
-        ).body
-    )
-
-    log(seektable_response)
-
-    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 get_license_response: GetLicenseResponse = JSON.parse(
-        local_http.GET(
-            get_license_url_url,
-            { Authorization: `Bearer ${local_state.bearer_token}` },
-            false
-        ).body
-    )
-
-    log(get_license_response)
-
-    const license_url = `https://gue1-spclient.spotify.com/${get_license_response.uri}`
-
-    log(license_url)
+    return {
+        url: `${file_manifest_url_prefix}${file_id}${file_manifest_params}`,
+        headers: { Authorization: `Bearer ${local_state.bearer_token}` }
+    }
+}
 
-    return new PlatformVideoDetails({
-        id: new PlatformID(PLATFORM, maybe_song_uri_id, plugin.config.id),
-        name: song_metadata_response.name,
-        author: new PlatformAuthorLink(new PlatformID(PLATFORM, first_artist.gid, plugin.config.id), first_artist.name, "https://open.spotify.com/artist/6vWDO969PvNqNYHIOW5v0m"),
-        url: song_url,
-        thumbnails: new Thumbnails(song_metadata_response.album.cover_group.image.map(function (image) {
-            return new Thumbnail(`${IMAGE_URL_PREFIX}${image.file_id}`, image.height)
-        })),
-        duration,
-        viewCount: HARDCODED_ZERO,
-        isLive: false,
-        shareUrl: song_metadata_response.canonical_uri,
-        // readonly uploadDate?: number
-        description: HARDCODED_EMPTY_STRING,
-        video: new UnMuxVideoSourceDescriptor([], audio_sources),
-        // readonly live?: IVideoSource
-        rating: new RatingLikes(HARDCODED_ZERO)
-        // readonly subtitles?: ISubtitleSource[]
+function episode_metadata_args(episode_uri_id: string): { readonly url: string, readonly headers: { Authorization: string } } {
+    const episode_metadata_url_prefix = "https://api-partner.spotify.com/pathfinder/v1/query"
+    const variables = JSON.stringify({
+        uri: `spotify:episode:${episode_uri_id}`
+    })
+    const extensions = JSON.stringify({
+        persistedQuery: {
+            version: 1,
+            sha256Hash: "9697538fe993af785c10725a40bb9265a20b998ccd2383bd6f586e01303824e9"
+        }
     })
+    const url = new URL(episode_metadata_url_prefix)
+    url.searchParams.set("operationName", "getEpisodeOrChapter")
+    url.searchParams.set("variables", variables)
+    url.searchParams.set("extensions", extensions)
+    return { url: url.toString(), headers: { Authorization: `Bearer ${local_state.bearer_token}` } }
 }
 
-function get_song_metadata(song_uri_id: string): SongMetadataResponse {
+function song_metadata_args(song_uri_id: string): {
+    readonly url: string,
+    readonly headers: {
+        Authorization: string,
+        Accept: "application/json"
+    }
+} {
     const song_metadata_url = "https://spclient.wg.spotify.com/metadata/4/track/"
-    const song_metadata_response: SongMetadataResponse = JSON.parse(
-        local_http.GET(
-            `${song_metadata_url}${get_gid(song_uri_id)}`,
-            {
-                Authorization: `Bearer ${local_state.bearer_token}`,
-                Accept: "application/json"
-            },
-            false
-        ).body
-    )
-    return song_metadata_response
+    return {
+        url: `${song_metadata_url}${get_gid(song_uri_id)}`,
+        headers: {
+            Authorization: `Bearer ${local_state.bearer_token}`,
+            Accept: "application/json"
+        }
+    }
+}
+//#endregion
+
+//#region utilities
+/**
+ * Converts seconds to the timestamp format used in WebVTT
+ * @param seconds 
+ * @returns 
+ */
+function milliseconds_to_WebVTT_timestamp(milliseconds: number) {
+    return new Date(milliseconds).toISOString().substring(11, 23)
 }
 
-// function assert_never(value: never) {
-//     log(value)
-// }
+function assert_never(value: never) {
+    log(value)
+}
+
+function log_passthrough<T>(value: T): T {
+    log(value)
+    return value
+}
+function assert_no_fall_through(value: never): void
+function assert_no_fall_through(value: never, exception_message: string): ScriptException
+function assert_no_fall_through(value: never, exception_message?: string): ScriptException | undefined {
+    log(["Spotify log:", value])
+    if (exception_message !== undefined) {
+        return new ScriptException(exception_message)
+    }
+    return
+}
+//#endregion
+
+function is_premium(): boolean {
+    return false
+}
 
-// function log_passthrough<T>(value: T): T {
-//     log(value)
-//     return value
-// }
 
 // https://open.spotifycdn.com/cdn/build/web-player/vendor~web-player.391a2438.js
 const Z = "0123456789abcdef"
@@ -383,6 +562,10 @@ function get_gid(song_uri_id: string) {
     }(song_uri_id) : song_uri_id
 }
 
+// export statements are removed during build step
+// used for unit testing in SpotifyScript.test.ts
 export {
-    get_gid
+    get_gid,
+    assert_never,
+    log_passthrough
 }
diff --git a/src/types.ts b/src/types.ts
index aa051b39b72bc351a85bfa5043d2537993d4e831..4213704d8d707629dbe3f8c057acc6166ca2b7be 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,3 +1,4 @@
+//#region custom types
 export type Settings = unknown
 
 export type SpotifySource = Required<Source<
@@ -10,6 +11,55 @@ export type SpotifySource = Required<Source<
 
 export type State = {
     readonly bearer_token: string
+    readonly license_uri: string
+}
+//#endregion
+
+//#region JSON types
+export type ContentType = "track" | "episode"
+
+export type TranscriptResponse = {
+    readonly section: ({
+        readonly startMs: number
+        readonly text: {
+            readonly sentence: {
+                readonly text: string
+            }
+        }
+    } | {
+        readonly startMs: number
+        readonly title: unknown
+    })[]
+    readonly language: "en"
+}
+
+export type EpisodeMetadataResponse = {
+    readonly data: {
+        readonly episodeUnionV2: {
+            readonly name: string
+            readonly duration: {
+                readonly totalMilliseconds: number
+            }
+            readonly coverArt: {
+                readonly sources: {
+                    readonly url: string
+                    readonly height: number
+                }[]
+            }
+            readonly releaseDate: {
+                readonly isoString: string
+            }
+            readonly uri: string
+            readonly audio: {
+                readonly items: {
+                    readonly fileId: string
+                    // only MP4_128 and MP4_256 are available on the web and therefore what we support
+                    readonly format: "MP4_128" | "AAC_24"
+                }[]
+            }
+            readonly htmlDescription: string
+        }
+    }
 }
 
 export type SongMetadataResponse = {
@@ -36,7 +86,8 @@ export type SongMetadataResponse = {
     readonly canonical_uri: string
     readonly file: {
         readonly file_id: string
-        readonly format: "MP4_128" | "AAC_24"
+        // only MP4_128 and MP4_256 are available on the web and therefore what we support
+        readonly format: "MP4_128" | "AAC_24" | "MP4_256" | "MP4_256_DUAL" | "OGG_VORBIS_320"
     }[]
 }
 
@@ -61,3 +112,4 @@ export type GetLicenseResponse = {
     readonly expires: number
     readonly uri: string
 }
+//#endregion