diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000000000000000000000000000000000000..9a2e55f99e75e776ca5f8f9dfecda4fb9ec17898
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,12 @@
+trailingComma: "all"
+arrowParens: "always"
+printWidth: 80
+tabWidth: 2
+useTabs: false
+singleQuote: true
+semi: true
+rangeStart: 0
+parser: "typescript"
+htmlWhitespaceSensitivity: "css"
+embeddedLanguageFormatting: "auto"
+endOfLine: "lf"
diff --git a/DailymotionConfig.json b/DailymotionConfig.json
index 9a06c0989b89d3f94e405ecab0d4645ae0c790b1..f6d1609fe1c9dbf7bb0f64dd94a82eea99d6c36e 100644
--- a/DailymotionConfig.json
+++ b/DailymotionConfig.json
@@ -7,13 +7,14 @@
   "sourceUrl": "https://stefancruz.github.io/GrayjayDailymotion/DailymotionConfig.json",
   "scriptUrl": "./DailymotionScript.js",
   "repositoryUrl": "https://github.com/stefancruz/GrayjayDailymotion",
-  "version": 18,
+  "version": 19,
   "iconUrl": "./DailymotionIcon.png",
   "id": "9c87e8db-e75d-48f4-afe5-2d203d4b95c5",
   "scriptSignature": "",
   "scriptPublicKey": "",
   "packages": [
-    "Http"
+    "Http",
+    "DOMParser"
   ],
   "allowEval": false,
   "allowAllHttpHeaderAccess": false,
diff --git a/Readme.md b/Readme.md
index 1af6669b16bed4cc30cf30e4c21a840acad5fc23..572060dc68185885adb8d865870eef81b8464429 100644
--- a/Readme.md
+++ b/Readme.md
@@ -27,24 +27,14 @@ Click [here](https://stefancruz.github.io/GrayjayDailymotion/index.html) to inst
 
 - [ ] - Harbor account verification
 
-## Internals
-
-- [x] - Save state between clients;
-- [x] - Batch http requests when possible;
-- [ ] - Optimize GraphQL queries;
-- [ ] - Clean up request Headers;
-- [ ] - Stop using configuration option allowAllHttpHeaderAccess;
-- [ ] - Unit tests - (WIP);
-- [ ] - Dev submit;
-
-
-## Todo 
-- [ ] - Search creator's content;
 
 ## Grayjay
 - [ ] - Live filter in Subscriptions tab
 - [ ] - Thumbnails for vertical videos
 
+## Dailymotion
+- [ ] - API fields deprecated with replacement not available yet (createdAt - createDate requires authentication, video?.viewCount, video?.stats?.views?.total);
+- [ ] - Platform comments
 
 ## Install
 npm install
@@ -61,5 +51,4 @@ npm start - will build and watch for changes
 
 ## Notes
 - Content of the 'build' folder should not be manually changed since it's recreated for each build. 
-- Used [rollup](https://rollupjs.org/) to bundle all the scripts into one.
-- Used npm shrinkwrap to lock the dependency versions of the project. This should avoid new bugs, vulnerabilities and breaking changes (in minor and patches), introduced when the dependencies are automatically updated by npm.
\ No newline at end of file
+- Used [rollup](https://rollupjs.org/) to bundle the scripts.
\ No newline at end of file
diff --git a/build/DailymotionConfig.json b/build/DailymotionConfig.json
index 9a06c0989b89d3f94e405ecab0d4645ae0c790b1..f6d1609fe1c9dbf7bb0f64dd94a82eea99d6c36e 100644
--- a/build/DailymotionConfig.json
+++ b/build/DailymotionConfig.json
@@ -7,13 +7,14 @@
   "sourceUrl": "https://stefancruz.github.io/GrayjayDailymotion/DailymotionConfig.json",
   "scriptUrl": "./DailymotionScript.js",
   "repositoryUrl": "https://github.com/stefancruz/GrayjayDailymotion",
-  "version": 18,
+  "version": 19,
   "iconUrl": "./DailymotionIcon.png",
   "id": "9c87e8db-e75d-48f4-afe5-2d203d4b95c5",
   "scriptSignature": "",
   "scriptPublicKey": "",
   "packages": [
-    "Http"
+    "Http",
+    "DOMParser"
   ],
   "allowEval": false,
   "allowAllHttpHeaderAccess": false,
diff --git a/build/DailymotionScript.js b/build/DailymotionScript.js
index c868ee701e4bc3fa5b3cca8961b0161b7822fa81..8a7a5715af86c6bdfa59cb8794488c80868f2f7d 100644
--- a/build/DailymotionScript.js
+++ b/build/DailymotionScript.js
@@ -1,1132 +1,980 @@
 'use strict';
 
-const BASE_URL = "https://www.dailymotion.com";
-const BASE_URL_API = "https://graphql.api.dailymotion.com";
-const BASE_URL_COMMENTS = "https://api-2-0.spot.im/v1.0.0/conversation/read";
-const BASE_URL_COMMENTS_AUTH = "https://api-2-0.spot.im/v1.0.0/authenticate";
-const BASE_URL_COMMENTS_THUMBNAILS = "https://images.spot.im/image/upload";
+const BASE_URL = 'https://www.dailymotion.com';
+const BASE_URL_API = 'https://graphql.api.dailymotion.com';
+const BASE_URL_COMMENTS = 'https://api-2-0.spot.im/v1.0.0/conversation/read';
+const BASE_URL_COMMENTS_AUTH = 'https://api-2-0.spot.im/v1.0.0/authenticate';
+const BASE_URL_COMMENTS_THUMBNAILS = 'https://images.spot.im/image/upload';
 const BASE_URL_API_AUTH = `${BASE_URL_API}/oauth/token`;
 const BASE_URL_VIDEO = `${BASE_URL}/video`;
 const BASE_URL_PLAYLIST = `${BASE_URL}/playlist`;
 const BASE_URL_METADATA = `${BASE_URL}/player/metadata/video`;
+const REGEX_VIDEO_URL = /^https:\/\/(?:www\.)?dailymotion\.com\/video\/[a-zA-Z0-9]+$/i;
+const REGEX_VIDEO_URL_1 = /^https:\/\/dai\.ly\/[a-zA-Z0-9]+$/i;
+const REGEX_VIDEO_URL_EMBED = /^https:\/\/(?:www\.)?dailymotion\.com\/embed\/video\/[a-zA-Z0-9]+(\?.*)?$/i;
+const REGEX_VIDEO_CHANNEL_URL = /^https:\/\/(?:www\.)?dailymotion\.com\/[a-zA-Z0-9-]+$/i;
+const REGEX_VIDEO_PLAYLIST_URL = /^https:\/\/(?:www\.)?dailymotion\.com\/playlist\/[a-zA-Z0-9]+$/i;
+const REGEX_INITIAL_DATA_API_AUTH_1 = /(?<=window\.__LOADABLE_LOADED_CHUNKS__=.*)\b[a-f0-9]{20}\b|\b[a-f0-9]{40}\b/g;
+const createAuthRegexByTextLength = (length) => new RegExp(`\\b\\w+\\s*=\\s*"([a-zA-Z0-9]{${length}})"`);
 const USER_AGENT = 'Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.230 Mobile Safari/537.36';
 // Those are used even for not logged users to make requests on the graphql api.
-//TODO: check how to get them dynamically
-const CLIENT_ID = 'f1a362d288c1b98099c7';
-const CLIENT_SECRET = 'eea605b96e01c796ff369935357eca920c5da4c5';
-const X_DM_AppInfo_Id = "com.dailymotion.neon";
-const X_DM_AppInfo_Type = "website";
-const X_DM_AppInfo_Version = "v2024-07-02T13:55:47.186Z"; //TODO check how to get this dynamically
-const X_DM_Neon_SSR = "0";
-const PLATFORM = "Dailymotion";
+const FALLBACK_CLIENT_ID = 'f1a362d288c1b98099c7';
+const FALLBACK_CLIENT_SECRET = 'eea605b96e01c796ff369935357eca920c5da4c5';
+const X_DM_AppInfo_Id = 'com.dailymotion.neon';
+const X_DM_AppInfo_Type = 'website';
+const X_DM_AppInfo_Version = 'v2024-07-02T13:55:47.186Z'; //TODO check how to get this dynamically
+const X_DM_Neon_SSR = '0';
+const PLATFORM = 'Dailymotion';
 const PLATFORM_CLAIMTYPE = 27;
 // search capabilities - upload date
-const LESS_THAN_MINUTE = "LESS_THAN_MINUTE";
-const ONE_TO_FIVE_MINUTES = "ONE_TO_FIVE_MINUTES";
-const FIVE_TO_THIRTY_MINUTES = "FIVE_TO_THIRTY_MINUTES";
-const THIRTY_TO_ONE_HOUR = "THIRTY_TO_ONE_HOUR";
-const MORE_THAN_ONE_HOUR = "MORE_THAN_ONE_HOUR";
+const LESS_THAN_MINUTE = 'LESS_THAN_MINUTE';
+const ONE_TO_FIVE_MINUTES = 'ONE_TO_FIVE_MINUTES';
+const FIVE_TO_THIRTY_MINUTES = 'FIVE_TO_THIRTY_MINUTES';
+const THIRTY_TO_ONE_HOUR = 'THIRTY_TO_ONE_HOUR';
+const MORE_THAN_ONE_HOUR = 'MORE_THAN_ONE_HOUR';
 const DURATION_THRESHOLDS = {};
 DURATION_THRESHOLDS[LESS_THAN_MINUTE] = { min: 0, max: 60 };
 DURATION_THRESHOLDS[ONE_TO_FIVE_MINUTES] = { min: 60, max: 300 };
 DURATION_THRESHOLDS[FIVE_TO_THIRTY_MINUTES] = { min: 300, max: 1800 };
 DURATION_THRESHOLDS[THIRTY_TO_ONE_HOUR] = { min: 1800, max: 3600 };
 DURATION_THRESHOLDS[MORE_THAN_ONE_HOUR] = { min: 3600, max: null };
+const LIKE_PLAYLIST_ID = 'LIKE_PLAYLIST';
+const FAVORITES_PLAYLIST_ID = 'FAVORITES_PLAYLIST';
+const RECENTLY_WATCHED_PLAYLIST_ID = 'RECENTLY_WATCHED_PLAYLIST';
 /** The possible values which liked media connections can be sorted by. */
 const LikedMediaSort = {
     /** Sort liked medias by most recent. */
     Recent: 'recent',
     /** Sort liked medias by most viewed. */
-    Visited: 'visited'
+    Visited: 'visited',
 };
 // This platform uses a scale system for rating the videos.
 // Ratings are grouped into positive and negative to calculate likes and dislikes.
 const POSITIVE_RATINGS_LABELS = [
-    "STAR_STRUCK", // amazing
-    "SMILING_FACE_WITH_SUNGLASSES", // cool
-    "WINKING_FACE" // interesting
+    'STAR_STRUCK', // amazing
+    'SMILING_FACE_WITH_SUNGLASSES', // cool
+    'WINKING_FACE', // interesting
 ];
 const NEGATIVE_RATINGS_LABELS = [
-    "SLEEPING_FACE", // boring
-    "FISHING_POLE" // waste of time
+    'SLEEPING_FACE', // boring
+    'FISHING_POLE', // waste of time
 ];
 const ERROR_TYPES = {
-    "DM001": "No video has been specified, you need to specify one.",
-    "DM002": "Content has been deleted.",
-    "DM003": "Live content is not available, i.e. it may not have started yet.",
-    "DM004": "Copyrighted content, access forbidden.",
-    "DM005": "Content rejected (this video may have been removed due to a breach of the terms of use, a copyright claim or an infringement upon third party rights).",
-    "DM006": "Publishing in progress…",
-    "DM007": "Video geo-restricted by its owner.",
-    "DM008": "Explicit content. Explicit content can be enabled using the plugin settings",
-    "DM009": "Explicit content (offsite embed)",
-    "DM010": "Private content",
-    "DM011": "An encoding error occurred",
-    "DM012": "Encoding in progress",
-    "DM013": "This video has no preset (no video stream)",
-    "DM014": "This video has not been made available on your device by its owner",
-    "DM015": "Kids host error",
-    "DM016": "Content not available on this website, it can only be watched on Dailymotion",
-    "DM019": "This content has been uploaded by an inactive channel and its access is limited"
+    DM001: 'No video has been specified, you need to specify one.',
+    DM002: 'Content has been deleted.',
+    DM003: 'Live content is not available, i.e. it may not have started yet.',
+    DM004: 'Copyrighted content, access forbidden.',
+    DM005: 'Content rejected (this video may have been removed due to a breach of the terms of use, a copyright claim or an infringement upon third party rights).',
+    DM006: 'Publishing in progress…',
+    DM007: 'Video geo-restricted by its owner.',
+    DM008: 'Explicit content. Explicit content can be enabled using the plugin settings',
+    DM009: 'Explicit content (offsite embed)',
+    DM010: 'Private content',
+    DM011: 'An encoding error occurred',
+    DM012: 'Encoding in progress',
+    DM013: 'This video has no preset (no video stream)',
+    DM014: 'This video has not been made available on your device by its owner',
+    DM015: 'Kids host error',
+    DM016: 'Content not available on this website, it can only be watched on Dailymotion',
+    DM019: 'This content has been uploaded by an inactive channel and its access is limited',
 };
 const SEARCH_CAPABILITIES = {
-    types: [
-        Type.Feed.Mixed
-    ],
-    sorts: [
-        "Most Recent",
-        "Most Viewed",
-        "Most Relevant"
-    ],
+    types: [Type.Feed.Mixed],
+    sorts: ['Most Recent', 'Most Viewed', 'Most Relevant'],
     filters: [
         {
-            id: "uploaddate",
-            name: "Upload Date",
+            id: 'uploaddate',
+            name: 'Upload Date',
             isMultiSelect: false,
             filters: [
-                { name: "Today", value: "today" },
-                { name: "Past week", value: "thisweek" },
-                { name: "Past month", value: "thismonth" },
-                { name: "Past year", value: "thisyear" }
-            ]
+                { name: 'Today', value: 'today' },
+                { name: 'Past week', value: 'thisweek' },
+                { name: 'Past month', value: 'thismonth' },
+                { name: 'Past year', value: 'thisyear' },
+            ],
         },
         {
-            id: "duration",
-            name: "Duration",
+            id: 'duration',
+            name: 'Duration',
             isMultiSelect: false,
             filters: [
-                { name: "< 1 min", value: LESS_THAN_MINUTE },
-                { name: "1 - 5 min", value: ONE_TO_FIVE_MINUTES },
-                { name: "5 - 30 min", value: FIVE_TO_THIRTY_MINUTES },
-                { name: "30 min - 1 hour", value: THIRTY_TO_ONE_HOUR },
-                { name: "> 1 hour", value: MORE_THAN_ONE_HOUR }
-            ]
-        }
-    ]
+                { name: '< 1 min', value: LESS_THAN_MINUTE },
+                { name: '1 - 5 min', value: ONE_TO_FIVE_MINUTES },
+                { name: '5 - 30 min', value: FIVE_TO_THIRTY_MINUTES },
+                { name: '30 min - 1 hour', value: THIRTY_TO_ONE_HOUR },
+                { name: '> 1 hour', value: MORE_THAN_ONE_HOUR },
+            ],
+        },
+    ],
 };
 
-const AUTOCOMPLETE_QUERY = `
-query AUTOCOMPLETE_QUERY($query: String!) {
-	search {
-		id
-		suggestedVideos: autosuggestions(
-		query: {eq: $query}
-		filter: {story: {eq: VIDEO}}
-		) {
-		edges {
-			node {
-			name
-			}
-		}
-		}
-	}
-}
-`;
-const CHANNEL_QUERY_DESKTOP = `
-query CHANNEL_QUERY_DESKTOP(
-	$channel_name: String!
-	$avatar_size: AvatarHeight!
-) {
-	channel(name: $channel_name) {
-		id
-		xid
-		name
-		displayName
-		description
-		avatar(height:$avatar_size) {
-			url
-		}
-		banner(width:LANDSCAPE_1920) {
-			url
-		}
-		tagline
-		metrics {
-			engagement {
-				followers {
-					edges {
-						node {
-							total
-						}
-					}
-				}
-				followings {
-					edges {
-						node {
-							total
-						}
-					}
-				}
-			}
-		}
-		stats {
-			id
-			views {
-				id
-				total
-			}
-			videos {
-				id
-				total
-			}
-		}
-		externalLinks {
-			facebookURL
-			twitterURL
-			websiteURL
-			instagramURL
-			pinterestURL
-		}
-	}
-}
-`;
-const SEACH_DISCOVERY_QUERY = `	
-fragment SEARCH_DISCOVERY_VIDEO_FRAGMENT on Video {
-	id
-	xid
-	title
-	isPublished
-	thumbnail(height:$thumbnail_resolution) {
-		url
-	}
-	createdAt
-	creator {
-		id
-		xid
-		name
-		displayName
-		avatar(height:$avatar_size) {
-			url
-		}
-	}
-	duration
-	viewCount
-	stats {
-		views {
-			total
-		}
-	}
-}
-
-query SEACH_DISCOVERY_QUERY($avatar_size: AvatarHeight!, $thumbnail_resolution: ThumbnailHeight!) {
-	home: views {
-		id
-		neon {
-			id
-			sections(space: "home") {
-				edges {
-					node {
-						id
-						name
-						title
-						description
-						components {
-							pageInfo {
-								hasNextPage
-							}
-							edges {
-								node {
-									... on Media {
-										...SEARCH_DISCOVERY_VIDEO_FRAGMENT
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
+const AUTOCOMPLETE_QUERY = `
+query AUTOCOMPLETE_QUERY($query: String!) {
+  search {
+    suggestedVideos: autosuggestions(
+      query: { eq: $query }
+      filter: { story: { eq: VIDEO } }
+    ) {
+      edges {
+        node {
+          name
+        }
+      }
+    }
+  }
 }`;
-const CHANNEL_VIDEOS_QUERY = `
-query CHANNEL_VIDEOS_QUERY(
-  $channel_name: String!
-  $first: Int!
-  $sort: String
-  $page: Int!
-  $allowExplicit: Boolean
-  $avatar_size: AvatarHeight!
-  $thumbnail_resolution: ThumbnailHeight!
-  $shouldLoadLives: Boolean!
-  $shouldLoadVideos: Boolean!
-) {
-  channel(name: $channel_name) {
-    id
-    xid
-    lives(
-      page: $page
-      first: $first
-      allowExplicit: $allowExplicit
-    ) @include(if: $shouldLoadLives) {
-      pageInfo {
-        hasNextPage
-        nextPage
-      }
-      totalCount
-      edges {
-        node {
-          id
-          xid
-          title
-          thumbnail(height: $thumbnail_resolution) {
-            url
-          }
-          description
-          metrics {
-            engagement {
-              audience {
-                totalCount
-              }
-            }
-          }
-          audienceCount
-          isOnAir
-          stats {
-            views {
-              total
-            }
-          }
-          creator {
-            id
-            xid
-            name
-            displayName
-            avatar(height: $avatar_size) {
-              url
-            }
-          }
-        }
-      }
-    }
-    videos(
-      page: $page
-      first: $first
-      allowExplicit: $allowExplicit
-      sort: $sort
-    ) @include(if: $shouldLoadVideos) {
-      pageInfo {
-        hasNextPage
-        nextPage
-      }
-      edges {
-        node {
-          id
-          xid
-          title
-          thumbnail(height: $thumbnail_resolution) {
-            url
-          }
-          bestAvailableQuality
-          duration
-          createdAt
-          creator {
-            id
-            name
-            displayName
-            avatar(height: $avatar_size) {
-              url
-            }
-          }
-          metrics {
-            engagement {
-              likes {
-                totalCount
-              }
-            }
-          }
-          viewCount
-          stats {
-            views {
-              total
-            }
-          }
-        }
-      }
-    }
-  }
+const CHANNEL_QUERY_DESKTOP = `
+query CHANNEL_QUERY_DESKTOP(
+	$channel_name: String!
+	$avatar_size: AvatarHeight!
+) {
+	channel(name: $channel_name) {
+		id
+		xid
+		name
+		displayName
+		description
+		avatar(height:$avatar_size) {
+			url
+		}
+		banner(width:LANDSCAPE_1920) {
+			url
+		}
+		tagline
+		metrics {
+			engagement {
+				followers {
+					edges {
+						node {
+							total
+						}
+					}
+				}
+				followings {
+					edges {
+						node {
+							total
+						}
+					}
+				}
+			}
+		}
+		stats {
+			views {
+				total
+			}
+			videos {
+				total
+			}
+		}
+		externalLinks {
+			facebookURL
+			twitterURL
+			websiteURL
+			instagramURL
+			pinterestURL
+		}
+	}
+}`;
+const SEACH_DISCOVERY_QUERY = `	
+fragment SEARCH_DISCOVERY_VIDEO_FRAGMENT on Video {
+	id
+	xid
+	title
+	thumbnail(height:$thumbnail_resolution) {
+		url
+	}
+	createdAt
+	creator {
+		id
+		xid
+		name
+		displayName
+		avatar(height:$avatar_size) {
+			url
+		}
+	}
+	duration
+	viewCount
+	stats {
+		views {
+			total
+		}
+	}
+}
+
+query SEACH_DISCOVERY_QUERY($avatar_size: AvatarHeight!, $thumbnail_resolution: ThumbnailHeight!) {
+	home: views {
+		neon {
+			sections(space: "home") {
+				edges {
+					node {
+						id
+						name
+						title
+						description
+						components {
+							pageInfo {
+								hasNextPage
+							}
+							edges {
+								node {
+									... on Media {
+										...SEARCH_DISCOVERY_VIDEO_FRAGMENT
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}`;
+const CHANNEL_VIDEOS_QUERY = `
+query CHANNEL_VIDEOS_QUERY(
+  $channel_name: String!
+  $first: Int!
+  $sort: String
+  $page: Int!
+  $allowExplicit: Boolean
+  $avatar_size: AvatarHeight!
+  $thumbnail_resolution: ThumbnailHeight!
+  $shouldLoadLives: Boolean!
+  $shouldLoadVideos: Boolean!
+) {
+  channel(name: $channel_name) {
+    id
+    xid
+    lives(
+      page: $page
+      first: $first
+      allowExplicit: $allowExplicit
+    ) @include(if: $shouldLoadLives) {
+      pageInfo {
+        hasNextPage
+        nextPage
+      }
+      totalCount
+      edges {
+        node {
+          id
+          xid
+          title
+          thumbnail(height: $thumbnail_resolution) {
+            url
+          }
+          description
+          metrics {
+            engagement {
+              audience {
+                totalCount
+              }
+            }
+          }
+          audienceCount
+          isOnAir
+          stats {
+            views {
+              total
+            }
+          }
+          creator {
+            id
+            xid
+            name
+            displayName
+            avatar(height: $avatar_size) {
+              url
+            }
+          }
+        }
+      }
+    }
+    videos(
+      page: $page
+      first: $first
+      allowExplicit: $allowExplicit
+      sort: $sort
+    ) @include(if: $shouldLoadVideos) {
+      pageInfo {
+        hasNextPage
+        nextPage
+      }
+      edges {
+        node {
+          id
+          xid
+          title
+          thumbnail(height: $thumbnail_resolution) {
+            url
+          }
+          duration
+          createdAt
+          creator {
+            id
+            name
+            displayName
+            avatar(height: $avatar_size) {
+              url
+            }
+          }
+          metrics {
+            engagement {
+              likes {
+                totalCount
+              }
+            }
+          }
+          viewCount
+          stats {
+            views {
+              total
+            }
+          }
+        }
+      }
+    }
+  }
 }`;
-const SEARCH_QUERY = ` 
-fragment VIDEO_BASE_FRAGMENT on Video {
-	id
-	xid
-	title
-	createdAt
-	metrics {
-		engagement {
-			likes {
-				edges {
-					node {
-						rating
-						total
-					}
-				}
-			}
-		}
-	}
-	stats {
-		id
-		views {
-			id
-			total
-		}
-	}
-	creator {
-		id
-		xid
-		name
-		displayName
-		description
-		avatar(height:$avatar_size) {
-			url
-		}
-	}
-	duration
-	thumbnail(height:$thumbnail_resolution) {
-		url
-	}
-	
-}
-
-fragment VIDEO_FAVORITES_FRAGMENT on Media {
-	... on Video {
-		id
-		viewerEngagement {
-			id
-			favorited
-		}
-	}
-	... on Live {
-		id
-		viewerEngagement {
-			id
-			favorited
-		}
-	}
-}
-
-fragment CHANNEL_BASE_FRAG on Channel {
-	id
-	xid
-	name
-	displayName
-	description
-	avatar(height:$avatar_size) {
-		url
-	}
-}
-
-fragment PLAYLIST_BASE_FRAG on Collection {
-	id
-	xid
-	name
-	description
-	thumbnail(height:$thumbnail_resolution) {
-		url
-	}
-	creator {
-		id
-		xid
-		name
-		displayName
-		avatar(height:$avatar_size) {
-			url
-		}
-	}
-	description
-	stats {
-		id
-		videos {
-			id
-			total
-		}
-	}
-	metrics {
-		engagement {
-			videos {
-				edges {
-					node {
-						total
-					}
-				}
-			}
-		}
-	}
-}
-
-query SEARCH_QUERY(
-	$query: String!
-	$shouldIncludeVideos: Boolean!
-	$shouldIncludeChannels: Boolean!
-	$shouldIncludePlaylists: Boolean!
-	$shouldIncludeLives: Boolean!
-	$page: Int
-	$limit: Int
-	$sortByVideos: SearchVideoSort
-	$durationMinVideos: Int
-	$durationMaxVideos: Int
-	$createdAfterVideos: DateTime
-	$avatar_size: AvatarHeight!
-	$thumbnail_resolution: ThumbnailHeight!
-) {
-	search {
-		id
-		videos(
-			query: $query
-			first: $limit
-			page: $page
-			sort: $sortByVideos
-			durationMin: $durationMinVideos
-			durationMax: $durationMaxVideos
-			createdAfter: $createdAfterVideos
-		) @include(if: $shouldIncludeVideos) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					...VIDEO_BASE_FRAGMENT
-					...VIDEO_FAVORITES_FRAGMENT
-				}
-			}
-		}
-		lives(query: $query, first: $limit, page: $page)
-			@include(if: $shouldIncludeLives) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					xid
-					title
-					thumbnail(height:$thumbnail_resolution) {
-						url
-					}
-					description
-					metrics {
-						engagement {
-							audience {
-								totalCount
-							}
-						}
-					}
-					audienceCount
-					isOnAir
-					creator {
-						id
-						xid
-						name
-						displayName
-						avatar(height:$avatar_size){
-							url
-						}
-					}
-				}
-			}
-		}
-		channels(query: $query, first: $limit, page: $page)
-			@include(if: $shouldIncludeChannels) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					...CHANNEL_BASE_FRAG
-				}
-			}
-		}
-		playlists: collections(query: $query, first: $limit, page: $page)
-			@include(if: $shouldIncludePlaylists) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					...PLAYLIST_BASE_FRAG
-				}
-			}
-		}
-	}
+const SEARCH_QUERY = ` 
+fragment VIDEO_BASE_FRAGMENT on Video {
+	id
+	xid
+	title
+	createdAt
+	metrics {
+		engagement {
+			likes {
+				edges {
+					node {
+						rating
+						total
+					}
+				}
+			}
+		}
+	}
+	stats {
+		views {
+			total
+		}
+	}
+	creator {
+		id
+		xid
+		name
+		displayName
+		description
+		avatar(height:$avatar_size) {
+			url
+		}
+	}
+	duration
+	thumbnail(height:$thumbnail_resolution) {
+		url
+	}
+}
+
+fragment PLAYLIST_BASE_FRAG on Collection {
+	id
+	xid
+	name
+	description
+	thumbnail(height:$thumbnail_resolution) {
+		url
+	}
+	creator {
+		id
+		xid
+		name
+		displayName
+		avatar(height:$avatar_size) {
+			url
+		}
+	}
+	description
+	stats {
+		videos {
+			total
+		}
+	}
+	metrics {
+		engagement {
+			videos {
+				edges {
+					node {
+						total
+					}
+				}
+			}
+		}
+	}
+}
+
+query SEARCH_QUERY(
+	$query: String!
+	$shouldIncludeVideos: Boolean!
+	$shouldIncludePlaylists: Boolean!
+	$shouldIncludeLives: Boolean!
+	$page: Int
+	$limit: Int
+	$sortByVideos: SearchVideoSort
+	$durationMinVideos: Int
+	$durationMaxVideos: Int
+	$createdAfterVideos: DateTime
+	$avatar_size: AvatarHeight!
+	$thumbnail_resolution: ThumbnailHeight!
+) {
+	search {
+		videos(
+			query: $query
+			first: $limit
+			page: $page
+			sort: $sortByVideos
+			durationMin: $durationMinVideos
+			durationMax: $durationMaxVideos
+			createdAfter: $createdAfterVideos
+		) @include(if: $shouldIncludeVideos) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			totalCount
+			edges {
+				node {
+					id
+					...VIDEO_BASE_FRAGMENT
+				}
+			}
+		}
+		lives(query: $query, first: $limit, page: $page)
+			@include(if: $shouldIncludeLives) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			totalCount
+			edges {
+				node {
+					id
+					xid
+					title
+					thumbnail(height:$thumbnail_resolution) {
+						url
+					}
+					description
+					metrics {
+						engagement {
+							audience {
+								totalCount
+							}
+						}
+					}
+					audienceCount
+					isOnAir
+					creator {
+						id
+						xid
+						name
+						displayName
+						avatar(height:$avatar_size){
+							url
+						}
+					}
+				}
+			}
+		}
+		playlists: collections(query: $query, first: $limit, page: $page)
+			@include(if: $shouldIncludePlaylists) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			totalCount
+			edges {
+				node {
+					id
+					...PLAYLIST_BASE_FRAG
+				}
+			}
+		}
+	}
 }`;
-const WATCHING_VIDEO = `
-fragment VIDEO_FRAGMENT on Video {
-	id
-	xid
-	isPublished
-	duration
-	title
-	description
-	thumbnail(height:$thumbnail_resolution) {
-		url
-	}
-	bestAvailableQuality
-	createdAt
-	isPrivate
-	isCreatedForKids
-	isExplicit
-	videoWidth: width
-	videoHeight: height
-	status
-	metrics {
-		engagement {
-			likes {
-				totalCount
-				edges {
-					node {
-						rating
-						total
-					}
-				}
-			}
-		}
-	}
-	stats {
-		id
-		views {
-			id
-			total
-		}
-	}
-	creator {
-		id
-		xid
-		name
-		displayName
-		avatar(height:$avatar_size) {
-			url
-			height
-			width
-		}
-		metrics {
-			engagement {
-				followers {
-					totalCount
-					edges {
-						node {
-							total
-						}
-					}
-				}
-			}
-		}
-		stats {
-			id
-			views {
-				id
-				total
-			}
-			followers {
-				id
-				total
-			}
-			videos {
-				id
-				total
-			}
-		}
-	}
-}
-
-fragment LIVE_FRAGMENT on Live {
-	id
-	xid
-	startAt
-	endAt
-	isPublished
-	title
-	description
-	audienceCount
-	isOnAir
-	thumbnail(height:$thumbnail_resolution){
-		url
-		height
-		width
-	}
-	category
-	createdAt
-	isPrivate
-	isExplicit
-	isCreatedForKids
-	bestAvailableQuality
-	canDisplayAds
-	videoWidth: width
-	videoHeight: height
-	metrics {
-		engagement {
-			likes {
-				edges {
-					node {
-						rating
-						total
-					}
-				}
-			}
-		}
-	}
-	stats {
-		id
-		views {
-			id
-			total
-		}
-	}
-	creator {
-		id
-		xid
-		name
-		displayName
-		avatar(height:$avatar_size) {
-			url
-			height
-			width
-		}
-		coverURLx375: coverURL(size: "x375")
-		stats {
-			id
-			views {
-				id
-				total
-			}
-			followers {
-				id
-				total
-			}
-			videos {
-				id
-				total
-			}
-		}
-		country {
-			id
-			codeAlpha2
-		}
-	}
-	language {
-		id
-		codeAlpha2
-	}
-	tags {
-		edges {
-			node {
-				id
-				label
-			}
-		}
-	}
-	geoblockedCountries {
-		id
-		allowed
-		denied
-	}
-}
-
-query WATCHING_VIDEO(
-	$xid: String!
-	$avatar_size: AvatarHeight!
-	$thumbnail_resolution: ThumbnailHeight!
-) {
-	video: media(xid: $xid) {
-		... on Video {
-			id
-			...VIDEO_FRAGMENT
-		}
-		... on Live {
-			id
-			...LIVE_FRAGMENT
-		}
-	}
+const WATCHING_VIDEO = `
+fragment VIDEO_FRAGMENT on Video {
+	id
+	xid
+	duration
+	title
+	description
+	thumbnail(height:$thumbnail_resolution) {
+		url
+	}
+	createdAt
+	metrics {
+		engagement {
+			likes {
+				totalCount
+				edges {
+					node {
+						rating
+						total
+					}
+				}
+			}
+		}
+	}
+	stats {
+		views {
+			total
+		}
+	}
+	creator {
+		id
+		xid
+		name
+		displayName
+		avatar(height:$avatar_size) {
+			url
+			height
+			width
+		}
+		metrics {
+			engagement {
+				followers {
+					totalCount
+					edges {
+						node {
+							total
+						}
+					}
+				}
+			}
+		}
+		stats {
+			views {
+				total
+			}
+			followers {
+				total
+			}
+			videos {
+				total
+			}
+		}
+	}
+}
+
+fragment LIVE_FRAGMENT on Live {
+	id
+	xid
+	startAt
+	endAt
+	title
+	description
+	audienceCount
+	isOnAir
+	thumbnail(height:$thumbnail_resolution){
+		url
+	}
+	createdAt
+	videoWidth: width
+	videoHeight: height
+	metrics {
+		engagement {
+			likes {
+				edges {
+					node {
+						rating
+						total
+					}
+				}
+			}
+		}
+	}
+	stats {
+		views {
+			total
+		}
+	}
+	creator {
+		id
+		xid
+		name
+		displayName
+		avatar(height:$avatar_size) {
+			url
+			height
+			width
+		}
+		stats {
+			views {
+				total
+			}
+			followers {
+				total
+			}
+			videos {
+				total
+			}
+		}
+	}
+}
+
+query WATCHING_VIDEO(
+	$xid: String!
+	$avatar_size: AvatarHeight!
+	$thumbnail_resolution: ThumbnailHeight!
+) {
+	video: media(xid: $xid) {
+		... on Video {
+			id
+			...VIDEO_FRAGMENT
+		}
+		... on Live {
+			id
+			...LIVE_FRAGMENT
+		}
+	}
 }`;
-const SEARCH_CHANNEL = `		
-query SEARCH_QUERY($query: String!, $page: Int, $limit: Int, $avatar_size: AvatarHeight!) {
-	search {
-		id
-		channels(query: $query, first: $limit, page: $page) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					id
-					xid
-					name
-					displayName
-					description
-					avatar(height:$avatar_size) {
-						url
-						height
-						width
-					}
-					metrics {
-						engagement {
-							followers {
-								edges {
-									node {
-										total
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
+const SEARCH_CHANNEL = `		
+query SEARCH_QUERY($query: String!, $page: Int, $limit: Int, $avatar_size: AvatarHeight!) {
+	search {
+		channels(query: $query, first: $limit, page: $page) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			totalCount
+			edges {
+				node {
+					id
+					xid
+					name
+					displayName
+					description
+					avatar(height:$avatar_size) {
+						url
+					}
+					metrics {
+						engagement {
+							followers {
+								edges {
+									node {
+										total
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
 }`;
-const PLAYLIST_DETAILS_QUERY = `
-query PLAYLIST_VIDEO_QUERY($xid: String!, $numberOfVideos: Int = 100, $avatar_size: AvatarHeight!, $thumbnail_resolution: ThumbnailHeight!) {
-	collection(xid: $xid) {
-		id
-		id
-		xid
-		updatedAt
-		name
-		thumbnail(height:$thumbnail_resolution) {
-			url
-		}
-		creator {
-			id
-			name
-			displayName
-			xid
-			avatar(height:$avatar_size) {
-				url
-			}
-			metrics {
-				engagement {
-					followers {
-						edges {
-							node {
-								total
-							}
-						}
-					}
-				}
-			}
-		}
-		metrics {
-			engagement {
-				videos {
-					edges {
-						node {
-							total
-						}
-					}
-				}
-			}
-		}
-		videos(first: $numberOfVideos) {
-			edges {
-				node {
-					id
-					xid
-					duration
-					title
-					description
-					url
-					createdAt
-					thumbnail(height:$thumbnail_resolution) {
-						url
-					}
-					creator {
-						id
-						name
-						displayName
-						xid
-						avatar(height:$avatar_size) {
-							url
-						}
-						metrics {
-							engagement {
-								followers {
-									edges {
-										node {
-											total
-										}
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
+const PLAYLIST_DETAILS_QUERY = `
+query PLAYLIST_VIDEO_QUERY($xid: String!, $numberOfVideos: Int = 100, $avatar_size: AvatarHeight!, $thumbnail_resolution: ThumbnailHeight!) {
+	collection(xid: $xid) {
+		id
+		xid
+		name
+		thumbnail(height:$thumbnail_resolution) {
+			url
+		}
+		creator {
+			id
+			name
+			displayName
+			xid
+			avatar(height:$avatar_size) {
+				url
+			}
+			metrics {
+				engagement {
+					followers {
+						edges {
+							node {
+								total
+							}
+						}
+					}
+				}
+			}
+		}
+		metrics {
+			engagement {
+				videos {
+					edges {
+						node {
+							total
+						}
+					}
+				}
+			}
+		}
+		videos(first: $numberOfVideos) {
+			edges {
+				node {
+					id
+					xid
+					duration
+					title
+					description
+					url
+					createdAt
+					thumbnail(height:$thumbnail_resolution) {
+						url
+					}
+					creator {
+						id
+						name
+						displayName
+						xid
+						avatar(height:$avatar_size) {
+							url
+						}
+						metrics {
+							engagement {
+								followers {
+									edges {
+										node {
+											total
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
 }`;
-const GET_USER_SUBSCRIPTIONS = `
-query SUBSCRIPTIONS_QUERY($first: Int, $page: Int) {
-	me {
-		channel {
-			followings(first: $first, page: $page) {
-				totalCount
-				edges {
-					node {
-						creator {
-							name
-						}
-					}
-				}
-			}
-		}
-	}
+const GET_USER_SUBSCRIPTIONS = `
+query SUBSCRIPTIONS_QUERY($first: Int, $page: Int) {
+	me {
+		channel {
+			followings(first: $first, page: $page) {
+				totalCount
+				edges {
+					node {
+						creator {
+							name
+						}
+					}
+				}
+			}
+		}
+	}
 }`;
-const GET_CHANNEL_PLAYLISTS_XID = `
-query CHANNEL_PLAYLISTS_QUERY(
-	$channel_name: String!
-	$sort: String
-	$page: Int!
-	$first: Int!
-) {
-	channel(name: $channel_name) {
-		collections(
-			sort: $sort
-			page: $page
-			first: $first
-		) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			edges {
-				node {
-					xid
-						}
-					}
-				}
-			}
+const GET_CHANNEL_PLAYLISTS_XID = `
+query CHANNEL_PLAYLISTS_QUERY(
+	$channel_name: String!
+	$sort: String
+	$page: Int!
+	$first: Int!
+) {
+	channel(name: $channel_name) {
+		collections(
+			sort: $sort
+			page: $page
+			first: $first
+		) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			edges {
+				node {
+					xid
+						}
+					}
+				}
+			}
 }`;
-const SUBSCRIPTIONS_QUERY = `
-query SUBSCRIPTIONS_QUERY {
-	me {
-		xid
-		channel {
-			name
-		}
-	}
-}
+const SUBSCRIPTIONS_QUERY = `
+query SUBSCRIPTIONS_QUERY {
+	me {
+		xid
+		channel {
+			name
+		}
+	}
+}
 `;
-const CHANNEL_PLAYLISTS_QUERY = `
-query CHANNEL_PLAYLISTS_QUERY(
-	$channel_name: String!
-	$sort: String
-	$page: Int!
-	$first: Int!
-	$avatar_size: AvatarHeight!, 
-	$thumbnail_resolution: ThumbnailHeight!
-) {
-	channel(name: $channel_name) {
-		id
-		xid
-		collections(sort: $sort, page: $page, first: $first) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			edges {
-				node {
-					id
-					xid
-					updatedAt
-					createdAt
-					name
-					description
-					metrics {
-						engagement {
-							videos {
-								edges {
-									node {
-										total
-									}
-								}
-								totalCount
-							}
-						}
-					}
-					thumbnail(height:$thumbnail_resolution) {
-						url
-					}
-					stats {
-						id
-						videos {
-							id
-							total
-						}
-					}
-					videos {
-						edges {
-							node {
-								createdAt
-								creator {
-									id
-									name
-									xid
-									avatar(height:$avatar_size) {
-										url
-									}
-									displayName
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-}
-
+const CHANNEL_PLAYLISTS_QUERY = `
+query CHANNEL_PLAYLISTS_QUERY(
+	$channel_name: String!
+	$sort: String
+	$page: Int!
+	$first: Int!
+	$avatar_size: AvatarHeight!, 
+	$thumbnail_resolution: ThumbnailHeight!
+) {
+	channel(name: $channel_name) {
+		id
+		xid
+		collections(sort: $sort, page: $page, first: $first) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			edges {
+				node {
+					id
+					xid
+					createdAt
+					name
+					description
+					metrics {
+						engagement {
+							videos {
+								edges {
+									node {
+										total
+									}
+								}
+								totalCount
+							}
+						}
+					}
+					thumbnail(height:$thumbnail_resolution) {
+						url
+					}
+					stats {
+						videos {
+							total
+						}
+					}
+					videos {
+						edges {
+							node {
+								createdAt
+								creator {
+									id
+									name
+									xid
+									avatar(height:$avatar_size) {
+										url
+									}
+									displayName
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
 `;
-const USER_LIKED_VIDEOS_QUERY = `
-query USER_LIKED_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
-	me {
-		id
-		likedMedias(first: 100, page: $page) {
-			edges {
-				node {
-					... on Video {
-						id
-						xid
-						title
-						duration
-						thumbnail(height:$thumbnail_resolution) {
-							url
-						}
-						aspectRatio
-						viewerEngagement {
-							id
-							liked
-						}
-						channel {
-							id
-							logoURLx25: logoURL(size: "x25")
-							displayName
-							accountType	
-						}
-					}
-					... on Live {
-						
-						id
-						xid
-						title
-						isOnAir
-						thumbnail(height:$thumbnail_resolution) {
-							url
-						}
-						viewerEngagement {
-							id
-							liked
-						}
-						channel {
-							id
-							logoURLx25: logoURL(size: "x25")
-							displayName
-							accountType
-						}
-					}
-				}
-			}
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-		}
-	}
+const USER_LIKED_VIDEOS_QUERY = `
+query USER_LIKED_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
+	me {
+		likedMedias(first: 100, page: $page) {
+			edges {
+				node {
+					... on Video {
+						id
+						xid
+						title
+						duration
+						thumbnail(height:$thumbnail_resolution) {
+							url
+						}
+						channel {
+							displayName
+						}
+					}
+					... on Live {
+						
+						id
+						xid
+						title
+						isOnAir
+						thumbnail(height:$thumbnail_resolution) {
+							url
+						}
+						channel {
+							displayName
+						}
+					}
+				}
+			}
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+		}
+	}
 }`;
-const USER_WATCH_LATER_VIDEOS_QUERY = `
-	query USER_WATCH_LATER_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
-	me {
-		id
-		watchLaterMedias(first: 100, page: $page) {
-			edges {
-				node {
-					... on Video {
-						id
-						xid
-						title
-						duration
-						thumbnail(height:$thumbnail_resolution) {
-							url
-						}
-						aspectRatio
-						channel {
-							id
-							logoURLx25: logoURL(size: "x25")
-							displayName
-							accountType
-						}
-						viewerEngagement {
-							id
-							favorited
-						}
-					}
-					... on Live {
-						id
-						xid
-						title
-						isOnAir
-						thumbnail(height:$thumbnail_resolution) {
-							url
-						}
-						channel {
-							id
-							logoURLx25: logoURL(size: "x25")
-							displayName
-							accountType
-						}
-						viewerEngagement {
-							id
-							favorited
-						}
-					}
-				}
-			}
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-		}
-	}
+const USER_WATCH_LATER_VIDEOS_QUERY = `
+	query USER_WATCH_LATER_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
+	me {
+		id
+		watchLaterMedias(first: 100, page: $page) {
+			edges {
+				node {
+					... on Video {
+						id
+						xid
+						title
+						duration
+						thumbnail(height:$thumbnail_resolution) {
+							url
+						}
+						channel {
+							displayName
+						}
+					}
+					... on Live {
+						id
+						xid
+						title
+						isOnAir
+						thumbnail(height:$thumbnail_resolution) {
+							url
+						}
+						channel {
+							displayName
+						}
+					}
+				}
+			}
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+		}
+	}
 }`;
-const USER_WATCHED_VIDEOS_QUERY = `
-	query USER_WATCHED_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
-	me {
-		id
-		watchedVideos(first: 100, page: $page) {
-			edges {
-				node {
-					id
-					xid
-					title
-					duration
-					thumbnail(height:$thumbnail_resolution) {
-						url
-					}
-					aspectRatio
-					channel {
-						id
-						logoURLx25: logoURL(size: "x25")
-						displayName
-						accountType
-					}
-				}
-			}
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-		}
-	}
+const USER_WATCHED_VIDEOS_QUERY = `
+	query USER_WATCHED_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
+	me {
+		id
+		watchedVideos(first: 100, page: $page) {
+			edges {
+				node {
+					id
+					xid
+					title
+					duration
+					thumbnail(height:$thumbnail_resolution) {
+						url
+					}
+					channel {
+						displayName
+					}
+				}
+			}
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+		}
+	}
 }`;
 
 const objectToUrlEncodedString = (obj) => {
@@ -1144,34 +992,33 @@ function getChannelNameFromUrl(url) {
     const channel_name = url.split('/').pop();
     return channel_name;
 }
-function isUsernameUrl(url) {
-    const regex = new RegExp('^' + BASE_URL.replace(/\./g, '\\.') + '/[^/]+$');
-    return regex.test(url);
-}
 const parseUploadDateFilter = (filter) => {
-    let createdAfterVideos;
+    let createdAfterVideos = null;
     const now = new Date();
     switch (filter) {
-        case "today":
+        case 'today': {
             // Last 24 hours from now
             const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
             createdAfterVideos = yesterday.toISOString();
             break;
-        case "thisweek":
+        }
+        case 'thisweek': {
             // Adjusts to the start of the current week (assuming week starts on Sunday)
-            const startOfWeek = new Date(now.setDate(now.getDate() - now.getDay()));
+            const startOfWeek = new Date(now.getTime());
+            startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay());
             createdAfterVideos = new Date(startOfWeek.getFullYear(), startOfWeek.getMonth(), startOfWeek.getDate()).toISOString();
             break;
-        case "thismonth":
+        }
+        case 'thismonth': {
             // Adjusts to the start of the month
             createdAfterVideos = new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
             break;
-        case "thisyear":
+        }
+        case 'thisyear': {
             // Adjusts to the start of the year
             createdAfterVideos = new Date(now.getFullYear(), 0, 1).toISOString();
             break;
-        default:
-            createdAfterVideos = null;
+        }
     }
     return createdAfterVideos;
 };
@@ -1179,14 +1026,14 @@ const parseSort = (order) => {
     let sort;
     switch (order) {
         //TODO: refact this to use constants
-        case "Most Recent":
-            sort = "RECENT";
+        case 'Most Recent':
+            sort = 'RECENT';
             break;
-        case "Most Viewed":
-            sort = "VIEW_COUNT";
+        case 'Most Viewed':
+            sort = 'VIEW_COUNT';
             break;
-        case "Most Relevant":
-            sort = "RELEVANCE";
+        case 'Most Relevant':
+            sort = 'RELEVANCE';
             break;
         default:
             sort = order; // Default to the original order if no match
@@ -1202,8 +1049,10 @@ const getQuery = (context) => {
         context.page = 1;
     }
     if (context?.filters.duration) {
-        context.filters.durationMinVideos = DURATION_THRESHOLDS[context.filters.duration].min;
-        context.filters.durationMaxVideos = DURATION_THRESHOLDS[context.filters.duration].max;
+        context.filters.durationMinVideos =
+            DURATION_THRESHOLDS[context.filters.duration].min;
+        context.filters.durationMaxVideos =
+            DURATION_THRESHOLDS[context.filters.duration].max;
     }
     else {
         context.filters.durationMinVideos = null;
@@ -1216,8 +1065,8 @@ const getQuery = (context) => {
 };
 function generateUUIDv4() {
     return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
-        const r = Math.random() * 16 | 0;
-        const v = c === 'x' ? r : (r & 0x3 | 0x8);
+        const r = (Math.random() * 16) | 0;
+        const v = c === 'x' ? r : (r & 0x3) | 0x8;
         return v.toString(16);
     });
 }
@@ -1234,7 +1083,7 @@ class SearchPagerAll extends VideoPager {
             q: this.context.params.query,
             sort: this.context.params.sort,
             page: this.context.page,
-            filters: this.context.params.filters
+            filters: this.context.params.filters,
         };
         return this.cb(opts);
     }
@@ -1246,7 +1095,11 @@ class SearchChannelPager extends ChannelPager {
         this.cb = cb;
     }
     nextPage() {
-        const opts = { q: this.context.params.query, page: this.context.page += 1 };
+        const page = (this.context.page += 1);
+        const opts = {
+            q: this.context.params.query,
+            page,
+        };
         return this.cb(opts);
     }
 }
@@ -1284,7 +1137,7 @@ class SearchPlaylistPager extends PlaylistPager {
             q: this.context.params.query,
             sort: this.context.params.sort,
             page: this.context.page,
-            filters: this.context.params.filters
+            filters: this.context.params.filters,
         };
         return this.cb(opts);
     }
@@ -1298,29 +1151,38 @@ const SourceChannelToGrayjayChannel = (pluginId, sourceChannel) => {
         }
         return acc;
     }, {});
+    let description = '';
+    if (sourceChannel?.tagline &&
+        sourceChannel?.tagline != sourceChannel?.description) {
+        description = `${sourceChannel?.tagline}\n\n`;
+    }
+    description += `${sourceChannel?.description ?? ''}`;
     return new PlatformChannel({
-        id: new PlatformID(PLATFORM, sourceChannel?.id ?? "", pluginId, PLATFORM_CLAIMTYPE),
-        name: sourceChannel?.displayName ?? "",
-        thumbnail: sourceChannel?.avatar?.url ?? "",
-        banner: sourceChannel.banner?.url ?? "",
-        subscribers: sourceChannel?.metrics?.engagement?.followers?.edges[0]?.node?.total ?? 0,
-        description: sourceChannel?.description ?? "",
+        id: new PlatformID(PLATFORM, sourceChannel?.id ?? '', pluginId, PLATFORM_CLAIMTYPE),
+        name: sourceChannel?.displayName ?? '',
+        thumbnail: sourceChannel?.avatar?.url ?? '',
+        banner: sourceChannel.banner?.url ?? '',
+        subscribers: sourceChannel?.metrics?.engagement?.followers?.edges?.[0]?.node?.total ??
+            0,
+        description,
         url: `${BASE_URL}/${sourceChannel.name}`,
-        links
+        links,
     });
 };
 const SourceAuthorToGrayjayPlatformAuthorLink = (pluginId, creator) => {
-    return new PlatformAuthorLink(new PlatformID(PLATFORM, creator?.id ?? "", pluginId, PLATFORM_CLAIMTYPE), creator?.displayName ?? "", creator?.name ? `${BASE_URL}/${creator?.name}` : "", creator?.avatar?.url ?? "", creator?.followers?.totalCount ?? creator?.stats?.followers?.total ?? creator?.metrics?.engagement?.followers?.edges[0]?.node?.total ?? 0);
+    return new PlatformAuthorLink(new PlatformID(PLATFORM, creator?.id ?? '', pluginId, PLATFORM_CLAIMTYPE), creator?.displayName ?? '', creator?.name ? `${BASE_URL}/${creator?.name}` : '', creator?.avatar?.url ?? '', creator?.followers?.totalCount ??
+        creator?.metrics?.engagement?.followers?.edges?.[0]?.node?.total ??
+        0);
 };
 const SourceVideoToGrayjayVideo = (pluginId, sourceVideo) => {
     const isLive = getIsLive(sourceVideo);
     const viewCount = getViewCount(sourceVideo);
     const video = {
-        id: new PlatformID(PLATFORM, sourceVideo?.id ?? "", pluginId, PLATFORM_CLAIMTYPE),
+        id: new PlatformID(PLATFORM, sourceVideo?.id ?? '', pluginId, PLATFORM_CLAIMTYPE),
         description: sourceVideo?.description ?? '',
-        name: sourceVideo?.title ?? "",
+        name: sourceVideo?.title ?? '',
         thumbnails: new Thumbnails([
-            new Thumbnail(sourceVideo?.thumbnail?.url ?? "", 0)
+            new Thumbnail(sourceVideo?.thumbnail?.url ?? '', 0),
         ]),
         author: SourceAuthorToGrayjayPlatformAuthorLink(pluginId, sourceVideo?.creator),
         uploadDate: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
@@ -1328,41 +1190,57 @@ const SourceVideoToGrayjayVideo = (pluginId, sourceVideo) => {
         url: `${BASE_URL_VIDEO}/${sourceVideo?.xid}`,
         duration: sourceVideo?.duration ?? 0,
         viewCount,
-        isLive
+        isLive,
     };
     return new PlatformVideo(video);
 };
 const SourceCollectionToGrayjayPlaylistDetails = (pluginId, sourceCollection, videos = []) => {
     return new PlatformPlaylistDetails({
-        url: sourceCollection?.xid ? `${BASE_URL_PLAYLIST}/${sourceCollection?.xid}` : "",
-        id: new PlatformID(PLATFORM, sourceCollection?.xid ?? "", pluginId, PLATFORM_CLAIMTYPE),
-        author: sourceCollection?.creator ? SourceAuthorToGrayjayPlatformAuthorLink(pluginId, sourceCollection?.creator) : {},
+        url: sourceCollection?.xid
+            ? `${BASE_URL_PLAYLIST}/${sourceCollection?.xid}`
+            : '',
+        id: new PlatformID(PLATFORM, sourceCollection?.xid ?? '', pluginId, PLATFORM_CLAIMTYPE),
+        author: sourceCollection?.creator
+            ? SourceAuthorToGrayjayPlatformAuthorLink(pluginId, sourceCollection?.creator)
+            : {},
         name: sourceCollection.name,
         thumbnail: sourceCollection?.thumbnail?.url,
         videoCount: videos.length ?? 0,
-        contents: new VideoPager(videos)
+        contents: new VideoPager(videos),
     });
 };
 const SourceCollectionToGrayjayPlaylist = (pluginId, sourceCollection) => {
     return new PlatformPlaylist({
         url: `${BASE_URL_PLAYLIST}/${sourceCollection?.xid}`,
-        id: new PlatformID(PLATFORM, sourceCollection?.xid ?? "", pluginId, PLATFORM_CLAIMTYPE),
+        id: new PlatformID(PLATFORM, sourceCollection?.xid ?? '', pluginId, PLATFORM_CLAIMTYPE),
         author: SourceAuthorToGrayjayPlatformAuthorLink(pluginId, sourceCollection?.creator),
         name: sourceCollection?.name,
         thumbnail: sourceCollection?.thumbnail?.url,
-        videoCount: sourceCollection?.metrics?.engagement?.videos?.edges[0]?.node?.total,
+        videoCount: sourceCollection?.metrics?.engagement?.videos?.edges?.[0]?.node?.total,
     });
 };
 const getIsLive = (sourceVideo) => {
-    return sourceVideo?.isOnAir === true || sourceVideo?.duration == undefined;
+    return (sourceVideo?.isOnAir === true ||
+        sourceVideo?.duration == undefined);
 };
 const getViewCount = (sourceVideo) => {
     let viewCount = 0;
     if (getIsLive(sourceVideo)) {
-        viewCount = sourceVideo?.audienceCount ?? sourceVideo?.viewCount ?? sourceVideo?.stats?.views?.total ?? 0;
+        const live = sourceVideo;
+        //TODO: live?.audienceCount and live.stats.views.total are deprecated
+        //live?.metrics?.engagement?.audience?.edges?.[0]?.node?.total is still empty
+        viewCount =
+            live?.metrics?.engagement?.audience?.edges?.[0]?.node?.total ??
+                live?.audienceCount ??
+                live?.stats?.views?.total ??
+                0;
     }
     else {
-        viewCount = sourceVideo?.viewCount ?? sourceVideo?.stats?.views?.total ?? 0;
+        const video = sourceVideo;
+        // TODO: both fields are deprecated.
+        // video?.stats?.views?.total replaced video?.viewCount
+        // now video?.viewCount is deprecated too but there replacement is not accessible yet
+        viewCount = video?.viewCount ?? video?.stats?.views?.total ?? 0;
     }
     return viewCount;
 };
@@ -1382,44 +1260,45 @@ const SourceVideoToPlatformVideoDetailsDef = (pluginId, sourceVideo, player_meta
     }
     const isLive = getIsLive(sourceVideo);
     const viewCount = getViewCount(sourceVideo);
-    const duration = isLive ? 0 : sourceVideo?.duration ?? 0;
+    const duration = isLive ? 0 : (sourceVideo?.duration ?? 0);
     const source = new HLSSource({
-        name: isLive ? 'live' : 'source',
+        name: 'HLS',
         duration,
         url: player_metadata?.qualities?.auto[0]?.url,
     });
-    const sources = [
-        source
-    ];
+    const sources = [source];
     const platformVideoDetails = {
-        id: new PlatformID(PLATFORM, sourceVideo?.id ?? "", pluginId, PLATFORM_CLAIMTYPE),
-        name: sourceVideo?.title ?? "",
-        thumbnails: new Thumbnails([new Thumbnail(sourceVideo?.thumbnail?.url ?? "", 0)]),
+        id: new PlatformID(PLATFORM, sourceVideo?.id ?? '', pluginId, PLATFORM_CLAIMTYPE),
+        name: sourceVideo?.title ?? '',
+        thumbnails: new Thumbnails([
+            new Thumbnail(sourceVideo?.thumbnail?.url ?? '', 0),
+        ]),
         author: SourceAuthorToGrayjayPlatformAuthorLink(pluginId, sourceVideo?.creator),
+        //TODO: sourceVideo?.createdAt is deprecated but sourceVideo?.createDate requires authentication
         uploadDate: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
         datetime: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
         duration,
         viewCount,
-        url: sourceVideo?.xid ? `${BASE_URL_VIDEO}/${sourceVideo.xid}` : "",
+        url: sourceVideo?.xid ? `${BASE_URL_VIDEO}/${sourceVideo.xid}` : '',
         isLive,
-        description: sourceVideo?.description ?? "",
+        description: sourceVideo?.description ?? '',
         video: new VideoSourceDescriptor(sources),
         rating: new RatingLikesDislikes(positiveRatingCount, negativeRatingCount),
         dash: null,
         live: null,
         hls: null,
-        subtitles: []
+        subtitles: [],
     };
     const sourceSubtitle = player_metadata?.subtitles;
     if (sourceSubtitle?.enable && sourceSubtitle?.data) {
-        Object.keys(sourceSubtitle.data).forEach(key => {
+        Object.keys(sourceSubtitle.data).forEach((key) => {
             const subtitleData = sourceSubtitle.data[key];
             if (subtitleData) {
                 const subtitleUrl = subtitleData.urls[0];
                 platformVideoDetails.subtitles.push({
                     name: subtitleData.label,
                     url: subtitleUrl,
-                    format: "text/vtt",
+                    format: 'text/vtt',
                     getSubtitles() {
                         try {
                             const subResp = http.GET(subtitleUrl, {});
@@ -1427,7 +1306,7 @@ const SourceVideoToPlatformVideoDetailsDef = (pluginId, sourceVideo, player_meta
                                 if (IS_TESTING) {
                                     bridge.log(`Failed to fetch subtitles from ${subtitleUrl}`);
                                 }
-                                return "";
+                                return '';
                             }
                             return convertSRTtoVTT(subResp.body);
                         }
@@ -1435,9 +1314,9 @@ const SourceVideoToPlatformVideoDetailsDef = (pluginId, sourceVideo, player_meta
                             if (IS_TESTING) {
                                 bridge.log(`Error fetching subtitles: ${error?.message}`);
                             }
-                            return "";
+                            return '';
                         }
-                    }
+                    },
                 });
             }
         });
@@ -1472,16 +1351,127 @@ const convertSRTtoVTT = (srt) => {
     return vtt.join('');
 };
 
+function oauthClientCredentialsRequest(httpClient, url, clientId, secret, throwOnInvalid = false) {
+    if (!httpClient || !url || !clientId || !secret) {
+        throw new ScriptException('Invalid parameters provided to oauthClientCredentialsRequest');
+    }
+    const body = objectToUrlEncodedString({
+        client_id: clientId,
+        client_secret: secret,
+        grant_type: 'client_credentials',
+    });
+    try {
+        return httpClient.POST(url, body, {
+            'User-Agent': USER_AGENT,
+            'Content-Type': 'application/x-www-form-urlencoded',
+            Origin: BASE_URL,
+            DNT: '1',
+            'Sec-GPC': '1',
+            Connection: 'keep-alive',
+            'Sec-Fetch-Dest': 'empty',
+            'Sec-Fetch-Mode': 'cors',
+            'Sec-Fetch-Site': 'same-site',
+            Priority: 'u=4',
+            Pragma: 'no-cache',
+            'Cache-Control': 'no-cache',
+        }, false);
+    }
+    catch (error) {
+        console.error('Error making OAuth client credentials request:', error);
+        if (throwOnInvalid) {
+            throw new ScriptException('Failed to obtain OAuth client credentials');
+        }
+        return null;
+    }
+}
+function extractClientCredentials(httpClient) {
+    const detailsRequestHtml = httpClient.GET(BASE_URL, {}, false);
+    if (!detailsRequestHtml.isOk) {
+        throw new ScriptException('Failed to fetch page to extract auth details');
+    }
+    const result = [
+        {
+            clientId: FALLBACK_CLIENT_ID,
+            secret: FALLBACK_CLIENT_SECRET,
+        },
+    ];
+    const match = detailsRequestHtml.body.match(REGEX_INITIAL_DATA_API_AUTH_1);
+    if (match?.length === 2 && match[0] && match[1]) {
+        result.unshift({
+            clientId: match[0],
+            secret: match[1],
+        });
+        console.log('Successfully extracted API credentials from page:', match[1]);
+    }
+    else {
+        console.log('Failed to extract API credentials from page using regex. Using DOM parsing.');
+        const htmlElement = domParser.parseFromString(detailsRequestHtml.body, 'text/html');
+        const extractedId = getScriptVariableByTextLength(htmlElement, 20);
+        const extractedSecret = getScriptVariableByTextLength(htmlElement, 40);
+        if (extractedId && extractedSecret) {
+            result.unshift({
+                clientId: extractedId,
+                secret: extractedSecret,
+            });
+            console.log('Successfully extracted API credentials from page using DOM parsing:', extractedId);
+        }
+        else {
+            console.log('Failed to extract API credentials using DOM parsing with exact text length.');
+        }
+    }
+    return result;
+}
+function getScriptVariableByTextLength(htmlElement, length) {
+    const scriptTags = htmlElement.querySelectorAll('script[type="text/javascript"]');
+    if (!scriptTags.length) {
+        console.error('No script tags found.');
+        return null; // or throw an error, depending on your use case
+    }
+    let pageContent = '';
+    scriptTags.forEach((tag) => {
+        pageContent += tag.outerHTML;
+    });
+    let matches = createAuthRegexByTextLength(length).exec(pageContent);
+    if (matches?.length == 2) {
+        return matches[1];
+    }
+}
+function getTokenFromClientCredentials(httpClient, credentials, throwOnInvalid = false) {
+    let result = {
+        isValid: false,
+    };
+    for (const credential of credentials) {
+        const res = oauthClientCredentialsRequest(httpClient, BASE_URL_API_AUTH, credential.clientId, credential.secret);
+        if (res?.isOk) {
+            const anonymousTokenResponse = JSON.parse(res.body);
+            if (!anonymousTokenResponse.token_type ||
+                !anonymousTokenResponse.access_token) {
+                console.error('Invalid token response', res);
+                if (throwOnInvalid) {
+                    throw new ScriptException('', 'Invalid token response: ' + res.body);
+                }
+            }
+            result = {
+                anonymousUserAuthorizationToken: `${anonymousTokenResponse.token_type} ${anonymousTokenResponse.access_token}`,
+                anonymousUserAuthorizationTokenExpirationDate: Date.now() + anonymousTokenResponse.expires_in * 1000,
+                isValid: true,
+            };
+            break;
+        }
+        else {
+            console.error('Failed to get token', res);
+        }
+    }
+    return result;
+}
+
 let config;
 let _settings;
 const state = {
-    anonymousUserAuthorizationToken: "",
+    anonymousUserAuthorizationToken: '',
     anonymousUserAuthorizationTokenExpirationDate: 0,
-    messageServiceToken: ""
+    messageServiceToken: '',
 };
-const LIKE_PLAYLIST_ID = "LIKE_PLAYLIST";
-const FAVORITES_PLAYLIST_ID = "FAVORITES_PLAYLIST";
-const RECENTLY_WATCHED_PLAYLIST_ID = "RECENTLY_WATCHED_PLAYLIST";
 // Will be used to store private playlists that require authentication
 const authenticatedPlaylistCollection = [];
 source.setSettings = function (settings) {
@@ -1495,121 +1485,111 @@ let THUMBNAIL_HEIGHT = [];
 //Source Methods
 source.enable = function (conf, settings, saveStateStr) {
     config = conf ?? {};
-    COUNTRY_NAMES_TO_CODE = config?.settings?.find(s => s.variable == "preferredCountryOptionIndex")?.options ?? [];
-    VIDEOS_PER_PAGE_OPTIONS = config?.settings?.find(s => s.variable == "videosPerPageOptionIndex")?.options?.map(s => parseInt(s)) ?? [];
-    PLAYLISTS_PER_PAGE_OPTIONS = config?.settings?.find(s => s.variable == "playlistsPerPageOptionIndex")?.options?.map(s => parseInt(s)) ?? [];
-    CREATOR_AVATAR_HEIGHT = config?.settings?.find(s => s.variable == "avatarSizeOptionIndex")?.options?.map(s => `SQUARE_${s.replace("px", "")}`) ?? [];
-    THUMBNAIL_HEIGHT = config?.settings?.find(s => s.variable == "thumbnailResolutionOptionIndex")?.options?.map(s => `PORTRAIT_${s.replace("px", "")}`) ?? [];
+    COUNTRY_NAMES_TO_CODE =
+        config?.settings?.find((s) => s.variable == 'preferredCountryOptionIndex')
+            ?.options ?? [];
+    VIDEOS_PER_PAGE_OPTIONS =
+        config?.settings
+            ?.find((s) => s.variable == 'videosPerPageOptionIndex')
+            ?.options?.map((s) => parseInt(s)) ?? [];
+    PLAYLISTS_PER_PAGE_OPTIONS =
+        config?.settings
+            ?.find((s) => s.variable == 'playlistsPerPageOptionIndex')
+            ?.options?.map((s) => parseInt(s)) ?? [];
+    CREATOR_AVATAR_HEIGHT =
+        config?.settings
+            ?.find((s) => s.variable == 'avatarSizeOptionIndex')
+            ?.options?.map((s) => `SQUARE_${s.replace('px', '')}`) ?? [];
+    THUMBNAIL_HEIGHT =
+        config?.settings
+            ?.find((s) => s.variable == 'thumbnailResolutionOptionIndex')
+            ?.options?.map((s) => `PORTRAIT_${s.replace('px', '')}`) ?? [];
     const DEFAULT_SETTINGS = {
         hideSensitiveContent: true,
         avatarSizeOptionIndex: 8, // 720px
         thumbnailResolutionOptionIndex: 7, // 1080px
         preferredCountryOptionIndex: 0, // empty
         videosPerPageOptionIndex: 3, // 20
-        playlistsPerPageOptionIndex: 0 // 5
+        playlistsPerPageOptionIndex: 0, // 5
     };
     _settings = { ...DEFAULT_SETTINGS, ...settings };
     if (IS_TESTING) {
-        config.id = "9c87e8db-e75d-48f4-afe5-2d203d4b95c5";
+        config.id = '9c87e8db-e75d-48f4-afe5-2d203d4b95c5';
     }
     let didSaveState = false;
     try {
         if (saveStateStr) {
             const saveState = JSON.parse(saveStateStr);
             if (saveState) {
-                state.anonymousUserAuthorizationToken = saveState.anonymousUserAuthorizationToken;
-                state.anonymousUserAuthorizationTokenExpirationDate = saveState.anonymousUserAuthorizationTokenExpirationDate;
+                state.anonymousUserAuthorizationToken =
+                    saveState.anonymousUserAuthorizationToken;
+                state.anonymousUserAuthorizationTokenExpirationDate =
+                    saveState.anonymousUserAuthorizationTokenExpirationDate;
                 state.messageServiceToken = saveState.messageServiceToken;
                 if (!isTokenValid()) {
-                    log("Token expired. Fetching a new one.");
+                    log('Token expired. Fetching a new one.');
                 }
                 else {
                     didSaveState = true;
-                    log("Using save state");
+                    log('Using save state');
                 }
             }
         }
     }
     catch (ex) {
-        log("Failed to parse saveState:" + ex);
+        log('Failed to parse saveState:' + ex);
         didSaveState = false;
     }
     if (!didSaveState) {
-        log("Getting a new tokens");
-        const body = objectToUrlEncodedString({
-            client_id: CLIENT_ID,
-            client_secret: CLIENT_SECRET,
-            grant_type: 'client_credentials'
-        });
-        let batchRequests = http.batch()
-            .POST(BASE_URL_API_AUTH, body, {
-            'User-Agent': USER_AGENT,
-            'Content-Type': 'application/x-www-form-urlencoded',
-            'Origin': BASE_URL,
-            'DNT': '1',
-            'Sec-GPC': '1',
-            'Connection': 'keep-alive',
-            'Sec-Fetch-Dest': 'empty',
-            'Sec-Fetch-Mode': 'cors',
-            'Sec-Fetch-Site': 'same-site',
-            'Priority': 'u=4',
-            'Pragma': 'no-cache',
-            'Cache-Control': 'no-cache'
-        }, false);
+        log('Getting a new tokens');
+        const clientCredentials = extractClientCredentials(http);
+        const { anonymousUserAuthorizationToken, anonymousUserAuthorizationTokenExpirationDate, isValid, } = getTokenFromClientCredentials(http, clientCredentials);
+        if (!isValid) {
+            console.error('Failed to get token');
+            throw new ScriptException('Failed to get authentication token');
+        }
+        state.anonymousUserAuthorizationToken =
+            anonymousUserAuthorizationToken ?? '';
+        state.anonymousUserAuthorizationTokenExpirationDate =
+            anonymousUserAuthorizationTokenExpirationDate ?? 0;
         if (config.allowAllHttpHeaderAccess) {
-            batchRequests = batchRequests.POST(BASE_URL_COMMENTS_AUTH, "", {
+            // get token for message service api-2-0.spot.im
+            const authenticateIm = http.POST(BASE_URL_COMMENTS_AUTH, '', {
                 'User-Agent': USER_AGENT,
                 Accept: '*/*',
                 'Accept-Language': 'en-US,en;q=0.5',
                 'x-spot-id': 'sp_vWPN1lBu',
                 'x-post-id': 'no$post',
                 'Content-Type': 'application/json',
-                'Origin': BASE_URL,
+                Origin: BASE_URL,
                 Connection: 'keep-alive',
                 Referer: BASE_URL,
                 'Sec-Fetch-Dest': 'empty',
                 'Sec-Fetch-Mode': 'cors',
                 'Sec-Fetch-Site': 'cross-site',
                 Priority: 'u=6',
-                'Content-Length': '0'
+                'Content-Length': '0',
             }, false);
-        }
-        const responses = batchRequests.execute();
-        const res = responses[0];
-        if (res.code !== 200) {
-            console.error('Failed to get token', res);
-            throw new ScriptException("", "Failed to get token: " + res.code + " - " + res.body);
-        }
-        const json = JSON.parse(res.body);
-        if (!json.token_type || !json.access_token) {
-            console.error('Invalid token response', res);
-            throw new ScriptException("", 'Invalid token response: ' + res.body);
-        }
-        state.anonymousUserAuthorizationToken = `${json.token_type} ${json.access_token}`;
-        state.anonymousUserAuthorizationTokenExpirationDate = Date.now() + (json.expires_in * 1000);
-        if (config.allowAllHttpHeaderAccess) {
-            const authenticateIm = responses[1];
             if (!authenticateIm.isOk) {
-                // throw new UnavailableException('Failed to authenticate to comments service');
                 log('Failed to authenticate to comments service');
             }
-            state.messageServiceToken = authenticateIm.headers["x-access-token"][0];
+            state.messageServiceToken = authenticateIm.headers['x-access-token'][0];
         }
     }
 };
 source.getHome = function () {
-    return getVideoPager({}, 0);
+    return getHomePager({}, 0);
 };
 source.searchSuggestions = function (query) {
     try {
-        const jsonResponse = executeGqlQuery(http, {
+        const gqlResponse = executeGqlQuery(http, {
             operationName: 'AUTOCOMPLETE_QUERY',
             variables: {
-                query
+                query,
             },
-            query: AUTOCOMPLETE_QUERY
+            query: AUTOCOMPLETE_QUERY,
         });
-        return jsonResponse?.data?.search?.suggestedVideos?.edges?.map(edge => edge?.node?.name ?? "") ?? [];
+        return (gqlResponse?.data?.search?.suggestedVideos?.edges?.map((edge) => edge?.node?.name ?? '') ?? []);
     }
     catch (error) {
         log('Failed to get search suggestions:' + error?.message);
@@ -1625,7 +1605,7 @@ source.searchChannels = function (query) {
 };
 //Channel
 source.isChannelUrl = function (url) {
-    return isUsernameUrl(url);
+    return REGEX_VIDEO_CHANNEL_URL.test(url);
 };
 source.getChannel = function (url) {
     const channel_name = getChannelNameFromUrl(url);
@@ -1633,9 +1613,9 @@ source.getChannel = function (url) {
         operationName: 'CHANNEL_QUERY_DESKTOP',
         variables: {
             channel_name,
-            avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex]
+            avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
         },
-        query: CHANNEL_QUERY_DESKTOP
+        query: CHANNEL_QUERY_DESKTOP,
     });
     return SourceChannelToGrayjayChannel(config.id, channelDetails.data.channel);
 };
@@ -1648,20 +1628,20 @@ source.getChannelPlaylists = (url) => {
         return getChannelPlaylists(url, 1);
     }
     catch (error) {
-        log('Failed to get channel playlists:' + error?.message);
+        log('Failed to get channel playlists:' + error);
         return new ChannelPlaylistPager([]);
     }
 };
 source.getChannelCapabilities = () => {
     return {
         types: [Type.Feed.Mixed],
-        sorts: [Type.Order.Chronological, "Popular"],
-        filters: []
+        sorts: [Type.Order.Chronological, 'Popular'],
+        filters: [],
     };
 };
 //Video
 source.isContentDetailsUrl = function (url) {
-    return url.startsWith(BASE_URL_VIDEO);
+    return [REGEX_VIDEO_URL, REGEX_VIDEO_URL_1, REGEX_VIDEO_URL_EMBED].some((r) => r.test(url));
 };
 source.getContentDetails = function (url) {
     return getSavedVideo(url, false);
@@ -1670,14 +1650,27 @@ source.saveState = () => {
     return JSON.stringify(state);
 };
 source.getSubComments = (comment) => {
-    const params = { "count": 5, "offset": 0, "parent_id": comment.context.id, "sort_by": "best", "child_count": comment.replyCount };
+    const params = {
+        count: 5,
+        offset: 0,
+        parent_id: comment.context.id,
+        sort_by: 'best',
+        child_count: comment.replyCount,
+    };
     return getCommentPager(comment.contextUrl, params, 0);
 };
 source.getComments = (url) => {
     if (!config.allowAllHttpHeaderAccess) {
         return new PlatformCommentPager([], false, url, {}, 0);
     }
-    const params = { "sort_by": "best", "offset": 0, "count": 10, "message_id": null, "depth": 2, "child_count": 2 };
+    const params = {
+        sort_by: 'best',
+        offset: 0,
+        count: 10,
+        message_id: null,
+        depth: 2,
+        child_count: 2,
+    };
     return getCommentPager(url, params, 0);
 };
 function getCommentPager(url, params, page) {
@@ -1691,14 +1684,14 @@ function getCommentPager(url, params, page) {
             'Content-Type': 'application/json',
             'x-spot-id': 'sp_vWPN1lBu',
             'x-post-id': xid,
-            'Origin': BASE_URL,
+            Origin: BASE_URL,
             Connection: 'keep-alive',
             Referer: BASE_URL,
             'Sec-Fetch-Dest': 'empty',
             'Sec-Fetch-Mode': 'cors',
             'Sec-Fetch-Site': 'cross-site',
             Priority: 'u=6',
-            TE: 'trailers'
+            TE: 'trailers',
         };
         const commentRequest = http.POST(BASE_URL_COMMENTS, JSON.stringify(params), commentsHeaders, false);
         if (!commentRequest.isOk) {
@@ -1706,22 +1699,22 @@ function getCommentPager(url, params, page) {
         }
         const comments = JSON.parse(commentRequest.body);
         const users = comments.conversation.users;
-        const results = comments.conversation.comments.map(v => {
+        const results = comments.conversation.comments.map((v) => {
             const user = users[v.user_id];
             return new Comment({
                 contextUrl: url,
-                author: new PlatformAuthorLink(new PlatformID(PLATFORM, user.id ?? "", config.id), user.display_name ?? "", "", `${BASE_URL_COMMENTS_THUMBNAILS}/${user.image_id}`),
+                author: new PlatformAuthorLink(new PlatformID(PLATFORM, user.id ?? '', config.id), user.display_name ?? '', '', `${BASE_URL_COMMENTS_THUMBNAILS}/${user.image_id}`),
                 message: v.content[0].text,
                 rating: new RatingLikes(v.stars),
                 date: v.written_at,
                 replyCount: v.total_replies_count ?? 0,
-                context: { id: v.id }
+                context: { id: v.id },
             });
         });
         return new PlatformCommentPager(results, comments.conversation.has_next, url, params, ++page);
     }
     catch (error) {
-        bridge.log('Failed to get comments:' + error?.message);
+        bridge.log('Failed to get comments:' + error);
         return new PlatformCommentPager([], false, url, params, 0);
     }
 }
@@ -1735,10 +1728,10 @@ class PlatformCommentPager extends CommentPager {
 }
 //Playlist
 source.isPlaylistUrl = (url) => {
-    return url.startsWith(BASE_URL_PLAYLIST) ||
+    return (REGEX_VIDEO_PLAYLIST_URL.test(url) ||
         url === LIKE_PLAYLIST_ID ||
         url === FAVORITES_PLAYLIST_ID ||
-        url === RECENTLY_WATCHED_PLAYLIST_ID;
+        url === RECENTLY_WATCHED_PLAYLIST_ID);
 };
 source.searchPlaylists = (query, type, order, filters) => {
     return searchPlaylists({ q: query, type, order, filters });
@@ -1761,21 +1754,21 @@ source.getPlaylist = (url) => {
         avatar_size: CREATOR_AVATAR_HEIGHT[_settings.avatarSizeOptionIndex],
         thumbnail_resolution: THUMBNAIL_HEIGHT[thumbnailResolutionIndex],
     };
-    let jsonResponse = executeGqlQuery(http, {
+    const gqlResponse = executeGqlQuery(http, {
         operationName: 'PLAYLIST_VIDEO_QUERY',
         variables,
         query: PLAYLIST_DETAILS_QUERY,
-        usePlatformAuth
+        usePlatformAuth,
     });
-    const videos = jsonResponse?.data?.collection?.videos?.edges.map(edge => {
+    const videos = gqlResponse?.data?.collection?.videos?.edges.map((edge) => {
         return SourceVideoToGrayjayVideo(config.id, edge.node);
     });
-    return SourceCollectionToGrayjayPlaylistDetails(config.id, jsonResponse?.data?.collection, videos);
+    return SourceCollectionToGrayjayPlaylistDetails(config.id, gqlResponse?.data?.collection, videos);
 };
 source.getUserSubscriptions = () => {
     if (!bridge.isLoggedIn()) {
-        log("Failed to retrieve subscriptions page because not logged in.");
-        throw new ScriptException("Not logged in");
+        log('Failed to retrieve subscriptions page because not logged in.');
+        throw new ScriptException('Not logged in');
     }
     const headers = {
         'Content-Type': 'application/json',
@@ -1796,29 +1789,28 @@ source.getUserSubscriptions = () => {
     };
     const usePlatformAuth = true;
     const fetchSubscriptions = (page, first) => {
-        const jsonResponse = executeGqlQuery(http, {
+        const gqlResponse = executeGqlQuery(http, {
             operationName: 'SUBSCRIPTIONS_QUERY',
             variables: {
                 first: first,
                 page: page,
-                avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
             },
             headers,
             query: GET_USER_SUBSCRIPTIONS,
-            usePlatformAuth
+            usePlatformAuth,
         });
-        return jsonResponse?.data?.me?.channel?.followings?.edges?.map(edge => edge?.node?.creator?.name ?? "") ?? [];
+        return (gqlResponse?.data?.me?.channel?.followings?.edges?.map((edge) => edge?.node?.creator?.name ?? '') ?? []);
     };
     const first = 100; // Number of records to fetch per page
     let page = 1;
-    let subscriptions = [];
-    // There is a totalCount ($.data.me.channel.followings.totalCount) property but it's not reliable. 
+    const subscriptions = [];
+    // There is a totalCount ($.data.me.channel.followings.totalCount) property but it's not reliable.
     // For example, it may return 0 even if there are subscriptions, or it may return a number that is not the actual number of subscriptions.
     // For now, it's better to fetch until no more results are returned
     let items = [];
     do {
         const response = fetchSubscriptions(page, first);
-        items = response.map(creatorName => `${BASE_URL}/${creatorName}`);
+        items = response.map((creatorName) => `${BASE_URL}/${creatorName}`);
         subscriptions.push(...items);
         page++;
     } while (items.length);
@@ -1826,8 +1818,8 @@ source.getUserSubscriptions = () => {
 };
 source.getUserPlaylists = () => {
     if (!bridge.isLoggedIn()) {
-        log("Failed to retrieve subscriptions page because not logged in.");
-        throw new ScriptException("Not logged in");
+        log('Failed to retrieve subscriptions page because not logged in.');
+        throw new ScriptException('Not logged in');
     }
     const headers = {
         'Content-Type': 'application/json',
@@ -1846,19 +1838,19 @@ source.getUserPlaylists = () => {
         Pragma: 'no-cache',
         'Cache-Control': 'no-cache',
     };
-    const jsonResponse = executeGqlQuery(http, {
+    const gqlResponse = executeGqlQuery(http, {
         operationName: 'SUBSCRIPTIONS_QUERY',
         headers,
         query: SUBSCRIPTIONS_QUERY,
-        usePlatformAuth: true
+        usePlatformAuth: true,
     });
-    const userName = jsonResponse?.data?.me?.channel?.name;
+    const userName = gqlResponse?.data?.me?.channel?.name;
     const playlists = getPlaylistsByUsername(userName, headers, true);
     [
         LIKE_PLAYLIST_ID,
         FAVORITES_PLAYLIST_ID,
-        RECENTLY_WATCHED_PLAYLIST_ID
-    ].forEach(playlistId => {
+        RECENTLY_WATCHED_PLAYLIST_ID,
+    ].forEach((playlistId) => {
         if (!authenticatedPlaylistCollection.includes(playlistId)) {
             authenticatedPlaylistCollection.push(playlistId);
         }
@@ -1872,8 +1864,8 @@ source.getChannelTemplateByClaimMap = () => {
     return {
         //Dailymotion claim type
         27: {
-            0: BASE_URL + "/{{CLAIMVALUE}}",
-        }
+            0: BASE_URL + '/{{CLAIMVALUE}}',
+        },
     };
 };
 function getPlaylistsByUsername(userName, headers, usePlatformAuth = false) {
@@ -1881,7 +1873,7 @@ function getPlaylistsByUsername(userName, headers, usePlatformAuth = false) {
         operationName: 'CHANNEL_PLAYLISTS_QUERY',
         variables: {
             channel_name: userName,
-            sort: "recent",
+            sort: 'recent',
             page: 1,
             first: 99,
             avatar_size: CREATOR_AVATAR_HEIGHT[_settings.avatarSizeOptionIndex],
@@ -1889,9 +1881,9 @@ function getPlaylistsByUsername(userName, headers, usePlatformAuth = false) {
         },
         headers,
         query: GET_CHANNEL_PLAYLISTS_XID,
-        usePlatformAuth
+        usePlatformAuth,
     });
-    const playlists = collections.data.channel.collections.edges.map(edge => {
+    const playlists = collections.data.channel.collections.edges.map((edge) => {
         const playlistUrl = `${BASE_URL_PLAYLIST}/${edge.node.xid}`;
         if (!authenticatedPlaylistCollection.includes(playlistUrl)) {
             authenticatedPlaylistCollection.push(playlistUrl);
@@ -1903,28 +1895,28 @@ function getPlaylistsByUsername(userName, headers, usePlatformAuth = false) {
 function searchPlaylists(contextQuery) {
     const context = getQuery(contextQuery);
     const variables = {
-        "query": context.q,
-        "sortByVideos": context.sort,
-        "durationMaxVideos": context.filters?.durationMaxVideos,
-        "durationMinVideos": context.filters?.durationMinVideos,
-        "createdAfterVideos": context.filters?.createdAfterVideos, //Represents a DateTime value as specified by iso8601
-        "shouldIncludeChannels": false,
-        "shouldIncludePlaylists": true,
-        "shouldIncludeVideos": false,
-        "shouldIncludeLives": false,
-        "page": context.page,
-        "limit": VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
-        "thumbnail_resolution": THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
-        "avatar_size": CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+        query: context.q,
+        sortByVideos: context.sort,
+        durationMaxVideos: context.filters?.durationMaxVideos,
+        durationMinVideos: context.filters?.durationMinVideos,
+        createdAfterVideos: context.filters?.createdAfterVideos, //Represents a DateTime value as specified by iso8601
+        shouldIncludePlaylists: true,
+        shouldIncludeVideos: false,
+        shouldIncludeLives: false,
+        page: context.page,
+        limit: VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
+        thumbnail_resolution: THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
+        avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
     };
-    const jsonResponse = executeGqlQuery(http, {
+    const gqlResponse = executeGqlQuery(http, {
         operationName: 'SEARCH_QUERY',
         variables: variables,
         query: SEARCH_QUERY,
-        headers: undefined
+        headers: undefined,
     });
-    const playlistConnection = jsonResponse?.data?.search?.playlists;
-    const searchResults = playlistConnection?.edges?.map(edge => {
+    const playlistConnection = gqlResponse?.data?.search
+        ?.playlists;
+    const searchResults = playlistConnection?.edges?.map((edge) => {
         return SourceCollectionToGrayjayPlaylist(config.id, edge?.node);
     });
     const hasMore = playlistConnection?.pageInfo?.hasNextPage;
@@ -1939,27 +1931,27 @@ function searchPlaylists(contextQuery) {
     return new SearchPlaylistPager(searchResults, hasMore, params, context.page, searchPlaylists);
 }
 //Internals
-function getVideoPager(params, page) {
+function getHomePager(params, page) {
     const count = VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex];
     if (!params) {
         params = {};
     }
     params = { ...params, count };
     const headersToAdd = {
-        "User-Agent": USER_AGENT,
-        "Referer": BASE_URL,
-        "Content-Type": "application/json",
-        "X-DM-AppInfo-Id": X_DM_AppInfo_Id,
-        "X-DM-AppInfo-Type": X_DM_AppInfo_Type,
-        "X-DM-AppInfo-Version": X_DM_AppInfo_Version,
-        "X-DM-Neon-SSR": X_DM_Neon_SSR,
-        "X-DM-Preferred-Country": getPreferredCountry(_settings?.preferredCountryOptionIndex),
-        "Origin": BASE_URL,
-        "DNT": "1",
-        "Sec-Fetch-Site": "same-site",
-        "Priority": "u=4",
-        "Pragma": "no-cache",
-        "Cache-Control": "no-cache"
+        'User-Agent': USER_AGENT,
+        Referer: BASE_URL,
+        'Content-Type': 'application/json',
+        'X-DM-AppInfo-Id': X_DM_AppInfo_Id,
+        'X-DM-AppInfo-Type': X_DM_AppInfo_Type,
+        'X-DM-AppInfo-Version': X_DM_AppInfo_Version,
+        'X-DM-Neon-SSR': X_DM_Neon_SSR,
+        'X-DM-Preferred-Country': getPreferredCountry(_settings?.preferredCountryOptionIndex),
+        Origin: BASE_URL,
+        DNT: '1',
+        'Sec-Fetch-Site': 'same-site',
+        Priority: 'u=4',
+        Pragma: 'no-cache',
+        'Cache-Control': 'no-cache',
     };
     let obj;
     try {
@@ -1976,36 +1968,39 @@ function getVideoPager(params, page) {
     catch (error) {
         return new VideoPager([], false, { params });
     }
-    const results = obj?.data?.home?.neon?.sections?.edges[0]?.node?.components?.edges
-        ?.filter(edge => edge?.node?.id)
-        ?.map(edge => {
+    const results = obj?.data?.home?.neon?.sections?.edges?.[0]?.node?.components?.edges
+        ?.filter((edge) => edge?.node?.id)
+        ?.map((edge) => {
         return SourceVideoToGrayjayVideo(config.id, edge.node);
     });
-    const hasMore = obj?.data?.home?.neon?.sections?.edges[0]?.node?.components?.pageInfo?.hasNextPage ?? false;
-    return new SearchPagerAll(results, hasMore, params, page, getVideoPager);
+    const hasMore = obj?.data?.home?.neon?.sections?.edges?.[0]?.node?.components?.pageInfo
+        ?.hasNextPage ?? false;
+    return new SearchPagerAll(results, hasMore, params, page, getHomePager);
 }
 function getChannelContentsPager(url, page, type, order, filters) {
     const channel_name = getChannelNameFromUrl(url);
     const shouldLoadVideos = type === Type.Feed.Mixed || type === Type.Feed.Videos;
-    const shouldLoadLives = type === Type.Feed.Mixed || type === Type.Feed.Streams || type === Type.Feed.Live;
+    const shouldLoadLives = type === Type.Feed.Mixed ||
+        type === Type.Feed.Streams ||
+        type === Type.Feed.Live;
     if (IS_TESTING) {
         log(`Getting channel contents for ${url}, page: ${page}, type: ${type}, order: ${order}, shouldLoadVideos: ${shouldLoadVideos}, shouldLoadLives: ${shouldLoadLives}, filters: ${JSON.stringify(filters)}`);
     }
     /**
-        Recent = Sort liked medias by most recent.
-        Visited - Sort liked medias by most viewed
-    */
+          Recent = Sort liked medias by most recent.
+          Visited - Sort liked medias by most viewed
+      */
     let sort;
     if (order == Type.Order.Chronological) {
         sort = LikedMediaSort.Recent;
     }
-    else if (order == "Popular") {
+    else if (order == 'Popular') {
         sort = LikedMediaSort.Visited;
     }
     else {
         sort = LikedMediaSort.Recent;
     }
-    const jsonResponse = executeGqlQuery(http, {
+    const gqlResponse = executeGqlQuery(http, {
         operationName: 'CHANNEL_VIDEOS_QUERY',
         variables: {
             channel_name,
@@ -2016,17 +2011,18 @@ function getChannelContentsPager(url, page, type, order, filters) {
             avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
             thumbnail_resolution: THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
             shouldLoadLives,
-            shouldLoadVideos
+            shouldLoadVideos,
         },
-        query: CHANNEL_VIDEOS_QUERY
+        query: CHANNEL_VIDEOS_QUERY,
     });
-    const channel = jsonResponse?.data?.channel;
+    const channel = gqlResponse?.data?.channel;
     const all = [
-        ...(channel?.lives?.edges?.filter(e => e?.node?.isOnAir)?.map(e => e?.node) ?? []),
-        ...(channel?.videos?.edges?.map(e => e?.node) ?? [])
+        ...(channel?.lives?.edges
+            ?.filter((e) => e?.node?.isOnAir)
+            ?.map((e) => e?.node) ?? []),
+        ...(channel?.videos?.edges?.map((e) => e?.node) ?? []),
     ];
-    let videos = all
-        .map((node => SourceVideoToGrayjayVideo(config.id, node)));
+    const videos = all.map((node) => SourceVideoToGrayjayVideo(config.id, node));
     const videosHasNext = channel?.videos?.pageInfo?.hasNextPage;
     const livesHasNext = channel?.lives?.pageInfo?.hasNextPage;
     const hasNext = videosHasNext || livesHasNext || false;
@@ -2035,40 +2031,39 @@ function getChannelContentsPager(url, page, type, order, filters) {
         type,
         order,
         page,
-        filters
+        filters,
     };
     return new ChannelVideoPager(videos, hasNext, params, getChannelContentsPager);
 }
 function getSearchPagerAll(contextQuery) {
     const context = getQuery(contextQuery);
     const variables = {
-        "query": context.q,
-        "sortByVideos": context.sort,
-        "durationMaxVideos": context.filters?.durationMaxVideos,
-        "durationMinVideos": context.filters?.durationMinVideos,
-        "createdAfterVideos": context.filters?.createdAfterVideos, //Represents a DateTime value as specified by iso8601
-        "shouldIncludeChannels": false,
-        "shouldIncludePlaylists": false,
-        "shouldIncludeVideos": true,
-        "shouldIncludeLives": true,
-        "page": context.page ?? 1,
-        "limit": VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
-        "avatar_size": CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
-        "thumbnail_resolution": THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex]
+        query: context.q,
+        sortByVideos: context.sort,
+        durationMaxVideos: context.filters?.durationMaxVideos,
+        durationMinVideos: context.filters?.durationMinVideos,
+        createdAfterVideos: context.filters?.createdAfterVideos, //Represents a DateTime value as specified by iso8601
+        shouldIncludePlaylists: false,
+        shouldIncludeVideos: true,
+        shouldIncludeLives: true,
+        page: context.page ?? 1,
+        limit: VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
+        avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+        thumbnail_resolution: THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
     };
-    const jsonResponse = executeGqlQuery(http, {
+    const gqlResponse = executeGqlQuery(http, {
         operationName: 'SEARCH_QUERY',
         variables: variables,
         query: SEARCH_QUERY,
-        headers: undefined
+        headers: undefined,
     });
-    const videoConnection = jsonResponse?.data?.search?.videos;
-    const liveConnection = jsonResponse?.data?.search?.lives;
+    const videoConnection = gqlResponse?.data?.search?.videos;
+    const liveConnection = gqlResponse?.data?.search?.lives;
     const all = [
         ...(videoConnection?.edges ?? []),
-        ...(liveConnection?.edges ?? [])
+        ...(liveConnection?.edges ?? []),
     ];
-    const results = all.map(edge => SourceVideoToGrayjayVideo(config.id, edge?.node));
+    const results = all.map((edge) => SourceVideoToGrayjayVideo(config.id, edge?.node));
     const params = {
         query: context.q,
         sort: context.sort,
@@ -2080,57 +2075,59 @@ function getSavedVideo(url, usePlatformAuth = false) {
     const id = url.split('/').pop();
     const player_metadata_url = `${BASE_URL_METADATA}/${id}?embedder=https%3A%2F%2Fwww.dailymotion.com%2Fvideo%2Fx8yb2e8&geo=1&player-id=xjnde&locale=en-GB&dmV1st=ce2035cd-bdca-4d7b-baa4-127a17490ca5&dmTs=747022&is_native_app=0&app=com.dailymotion.neon&client_type=webapp&section_type=player&component_style=_`;
     const headers1 = {
-        "User-Agent": USER_AGENT,
-        "Accept": "*/*",
-        "Referer": "https://geo.dailymotion.com/",
-        "Origin": "https://geo.dailymotion.com",
-        "DNT": "1",
-        "Connection": "keep-alive",
-        "Sec-Fetch-Dest": "empty",
-        "Sec-Fetch-Mode": "cors",
-        "Sec-Fetch-Site": "same-site",
-        "Pragma": "no-cache",
-        "Cache-Control": "no-cache"
+        'User-Agent': USER_AGENT,
+        Accept: '*/*',
+        Referer: 'https://geo.dailymotion.com/',
+        Origin: 'https://geo.dailymotion.com',
+        DNT: '1',
+        Connection: 'keep-alive',
+        'Sec-Fetch-Dest': 'empty',
+        'Sec-Fetch-Mode': 'cors',
+        'Sec-Fetch-Site': 'same-site',
+        Pragma: 'no-cache',
+        'Cache-Control': 'no-cache',
     };
     if (_settings.hideSensitiveContent) {
-        headers1["Cookie"] = "ff=on";
+        headers1['Cookie'] = 'ff=on';
     }
     else {
-        headers1["Cookie"] = "ff=off";
+        headers1['Cookie'] = 'ff=off';
     }
     const videoDetailsRequestBody = JSON.stringify({
-        operationName: "WATCHING_VIDEO",
+        operationName: 'WATCHING_VIDEO',
         variables: {
-            "xid": id,
-            "avatar_size": CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
-            "thumbnail_resolution": THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex]
+            xid: id,
+            avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+            thumbnail_resolution: THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
         },
-        query: WATCHING_VIDEO
+        query: WATCHING_VIDEO,
     });
     const videoDetailsRequestHeaders = {
-        "Content-Type": "application/json",
-        "User-Agent": USER_AGENT,
-        "Accept": "*/*, */*",
-        "Referer": `${BASE_URL_VIDEO}/${id}`,
-        "X-DM-AppInfo-Id": X_DM_AppInfo_Id,
-        "X-DM-AppInfo-Type": X_DM_AppInfo_Type,
-        "X-DM-AppInfo-Version": X_DM_AppInfo_Version,
-        "X-DM-Neon-SSR": X_DM_Neon_SSR,
-        "X-DM-Preferred-Country": getPreferredCountry(_settings?.preferredCountryOptionIndex),
-        "Origin": BASE_URL,
-        "DNT": "1",
-        "Connection": "keep-alive",
-        "Sec-Fetch-Dest": "empty",
-        "Sec-Fetch-Mode": "cors",
-        "Sec-Fetch-Site": "same-site",
-        "Priority": "u=4",
-        "Pragma": "no-cache",
-        "Cache-Control": "no-cache"
+        'Content-Type': 'application/json',
+        'User-Agent': USER_AGENT,
+        Accept: '*/*, */*',
+        Referer: `${BASE_URL_VIDEO}/${id}`,
+        'X-DM-AppInfo-Id': X_DM_AppInfo_Id,
+        'X-DM-AppInfo-Type': X_DM_AppInfo_Type,
+        'X-DM-AppInfo-Version': X_DM_AppInfo_Version,
+        'X-DM-Neon-SSR': X_DM_Neon_SSR,
+        'X-DM-Preferred-Country': getPreferredCountry(_settings?.preferredCountryOptionIndex),
+        Origin: BASE_URL,
+        DNT: '1',
+        Connection: 'keep-alive',
+        'Sec-Fetch-Dest': 'empty',
+        'Sec-Fetch-Mode': 'cors',
+        'Sec-Fetch-Site': 'same-site',
+        Priority: 'u=4',
+        Pragma: 'no-cache',
+        'Cache-Control': 'no-cache',
     };
     if (!usePlatformAuth) {
-        videoDetailsRequestHeaders.Authorization = state.anonymousUserAuthorizationToken;
+        videoDetailsRequestHeaders.Authorization =
+            state.anonymousUserAuthorizationToken;
     }
-    const responses = http.batch()
+    const responses = http
+        .batch()
         .GET(player_metadata_url, headers1, usePlatformAuth)
         .POST(BASE_URL_API, videoDetailsRequestBody, videoDetailsRequestHeaders, usePlatformAuth)
         .execute();
@@ -2141,7 +2138,8 @@ function getSavedVideo(url, usePlatformAuth = false) {
     }
     const player_metadata = JSON.parse(player_metadataResponse.body);
     if (player_metadata.error) {
-        if (player_metadata.error.code && ERROR_TYPES[player_metadata.error.code] !== undefined) {
+        if (player_metadata.error.code &&
+            ERROR_TYPES[player_metadata.error.code] !== undefined) {
             throw new UnavailableException(ERROR_TYPES[player_metadata.error.code]);
         }
         throw new UnavailableException('This content is not available');
@@ -2156,16 +2154,16 @@ function getSavedVideo(url, usePlatformAuth = false) {
 }
 function getSearchChannelPager(context) {
     const searchResponse = executeGqlQuery(http, {
-        operationName: "SEARCH_QUERY",
+        operationName: 'SEARCH_QUERY',
         variables: {
             query: context.q,
             page: context.page ?? 1,
             limit: VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
-            avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex]
+            avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
         },
-        query: SEARCH_CHANNEL
+        query: SEARCH_CHANNEL,
     });
-    const results = searchResponse?.data?.search?.channels?.edges.map(edge => {
+    const results = searchResponse?.data?.search?.channels?.edges.map((edge) => {
         const channel = edge.node;
         return SourceChannelToGrayjayChannel(config.id, channel);
     });
@@ -2197,11 +2195,11 @@ function getChannelPlaylists(url, page = 1) {
     };
     const usePlatformAuth = false;
     const channel_name = getChannelNameFromUrl(url);
-    const jsonResponse1 = executeGqlQuery(http, {
+    const gqlResponse = executeGqlQuery(http, {
         operationName: 'CHANNEL_PLAYLISTS_QUERY',
         variables: {
             channel_name,
-            sort: "recent",
+            sort: 'recent',
             page,
             first: PLAYLISTS_PER_PAGE_OPTIONS[_settings.playlistsPerPageOptionIndex],
             avatar_size: CREATOR_AVATAR_HEIGHT[_settings.avatarSizeOptionIndex],
@@ -2209,17 +2207,19 @@ function getChannelPlaylists(url, page = 1) {
         },
         headers,
         query: CHANNEL_PLAYLISTS_QUERY,
-        usePlatformAuth
+        usePlatformAuth,
     });
-    const channel = jsonResponse1.data.channel;
-    const content = (channel?.collections?.edges ?? []).map(edge => {
+    const channel = gqlResponse.data.channel;
+    const content = (channel?.collections?.edges ?? [])
+        .filter((e) => e?.node?.metrics?.engagement?.videos?.edges?.[0]?.node?.total) //exclude empty playlists. could be empty doe to geographic restrictions
+        .map((edge) => {
         return SourceCollectionToGrayjayPlaylist(config.id, edge?.node);
     });
     if (content?.length === 0) {
         return new ChannelPlaylistPager([]);
     }
     const params = {
-        url
+        url,
     };
     const hasMore = channel?.collections?.pageInfo?.hasNextPage ?? false;
     return new ChannelPlaylistPager(content, hasMore, params, page, getChannelPlaylists);
@@ -2230,40 +2230,44 @@ function isTokenValid() {
 }
 function executeGqlQuery(httpClient, requestOptions) {
     const headersToAdd = requestOptions.headers || {
-        "User-Agent": USER_AGENT,
-        "Accept": "*/*",
+        'User-Agent': USER_AGENT,
+        Accept: '*/*',
         // "Accept-Language": Accept_Language,
-        "Referer": BASE_URL,
-        "Origin": BASE_URL,
-        "DNT": "1",
-        "Connection": "keep-alive",
-        "Sec-Fetch-Dest": "empty",
-        "Sec-Fetch-Mode": "cors",
-        "Sec-Fetch-Site": "same-site",
-        "Pragma": "no-cache",
-        "Cache-Control": "no-cache"
+        Referer: BASE_URL,
+        Origin: BASE_URL,
+        DNT: '1',
+        Connection: 'keep-alive',
+        'Sec-Fetch-Dest': 'empty',
+        'Sec-Fetch-Mode': 'cors',
+        'Sec-Fetch-Site': 'same-site',
+        Pragma: 'no-cache',
+        'Cache-Control': 'no-cache',
     };
     const gql = JSON.stringify({
         operationName: requestOptions.operationName,
         variables: requestOptions.variables,
         query: requestOptions.query,
     });
-    const usePlatformAuth = requestOptions.usePlatformAuth == undefined ? false : requestOptions.usePlatformAuth;
-    const throwOnError = requestOptions.throwOnError == undefined ? true : requestOptions.throwOnError;
+    const usePlatformAuth = requestOptions.usePlatformAuth == undefined
+        ? false
+        : requestOptions.usePlatformAuth;
+    const throwOnError = requestOptions.throwOnError == undefined
+        ? true
+        : requestOptions.throwOnError;
     if (!usePlatformAuth) {
         headersToAdd.Authorization = state.anonymousUserAuthorizationToken;
     }
     const res = httpClient.POST(BASE_URL_API, gql, headersToAdd, usePlatformAuth);
     if (!res.isOk) {
-        console.error('Failed to get token', res);
+        console.error('Failed to execute request', res);
         if (throwOnError) {
-            throw new ScriptException("Failed to get token", res);
+            throw new ScriptException('Failed to execute request', res);
         }
     }
     const body = JSON.parse(res.body);
     // some errors may be returned in the body with a status code 200
     if (body.errors) {
-        const message = body.errors.map(e => e.message).join(', ');
+        const message = body.errors.map((e) => e.message).join(', ');
         if (throwOnError) {
             throw new UnavailableException(message);
         }
@@ -2279,13 +2283,13 @@ function getPages(httpClient, query, operationName, variables, usePlatformAuth,
     let nextPage = 1;
     do {
         variables = { ...variables, page: nextPage };
-        const jsonResponse = executeGqlQuery(httpClient, {
+        const gqlResponse = executeGqlQuery(httpClient, {
             operationName,
             variables,
             query,
-            usePlatformAuth
+            usePlatformAuth,
         });
-        const root = setRoot(jsonResponse);
+        const root = setRoot(gqlResponse);
         nextPage = getNextPage(root, nextPage);
         const items = map(root);
         hasNext = hasNextCallback(root);
@@ -2302,7 +2306,7 @@ function getLikePlaylist(pluginId, httpClient, usePlatformAuth = false, thumbnai
         rootObject: 'likedMedias',
         playlistName: 'Liked Videos',
         usePlatformAuth,
-        thumbnailResolutionIndex
+        thumbnailResolutionIndex,
     });
 }
 function getFavoritesPlaylist(pluginId, httpClient, usePlatformAuth = false, thumbnailResolutionIndex = 0) {
@@ -2314,7 +2318,7 @@ function getFavoritesPlaylist(pluginId, httpClient, usePlatformAuth = false, thu
         rootObject: 'watchLaterMedias',
         playlistName: 'Favorites',
         usePlatformAuth,
-        thumbnailResolutionIndex
+        thumbnailResolutionIndex,
     });
 }
 function getRecentlyWatchedPlaylist(pluginId, httpClient, usePlatformAuth = false, thumbnailResolutionIndex = 0) {
@@ -2326,30 +2330,30 @@ function getRecentlyWatchedPlaylist(pluginId, httpClient, usePlatformAuth = fals
         rootObject: 'watchedVideos',
         playlistName: 'Recently Watched',
         usePlatformAuth,
-        thumbnailResolutionIndex
+        thumbnailResolutionIndex,
     });
 }
 function getPlatformSystemPlaylist(opts) {
     const videos = getPages(opts.httpClient, opts.query, opts.operationName, {
         page: 1,
-        thumbnail_resolution: THUMBNAIL_HEIGHT[opts.thumbnailResolutionIndex]
-    }, opts.usePlatformAuth, (jsonResponse) => jsonResponse?.data?.me, //set root
-    (me) => (me?.[opts.rootObject]?.edges.length ?? 0) > 0 ?? false, //hasNextCallback
+        thumbnail_resolution: THUMBNAIL_HEIGHT[opts.thumbnailResolutionIndex],
+    }, opts.usePlatformAuth, (gqlResponse) => gqlResponse?.data?.me, //set root
+    (me) => (me?.[opts.rootObject]?.edges?.length ?? 0) > 0, //hasNextCallback
     (me, currentPage) => ++currentPage, //getNextPage
-    (me) => me?.[opts.rootObject]?.edges.map(edge => {
+    (me) => me?.[opts.rootObject]?.edges.map((edge) => {
         return SourceVideoToGrayjayVideo(opts.pluginId, edge.node);
     }));
     const collection = {
-        "id": generateUUIDv4(),
-        "name": opts.playlistName,
-        "creator": {}
+        id: generateUUIDv4(),
+        name: opts.playlistName,
+        creator: {},
     };
     return SourceCollectionToGrayjayPlaylistDetails(opts.pluginId, collection, videos);
 }
 function getPreferredCountry(preferredCountryIndex) {
     const country = COUNTRY_NAMES_TO_CODE[preferredCountryIndex];
     const parts = country.split('-');
-    const code = parts[0] ?? "";
+    const code = parts[0] ?? '';
     return (code || '').toLowerCase();
 }
-log("LOADED");
+log('LOADED');
diff --git a/package.json b/package.json
index c2909ea3b8e0ca082d99968cfe9b99bfdc324486..da7613305884d51b13720e97d0f3a9134b84c137 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
   "scripts": {
     "build": "rollup -c",
     "start": "rollup -c -w",
-    "codegen": "graphql-codegen --config ./scripts/codegen.ts"
+    "codegen": "graphql-codegen --config ./scripts/codegen.ts",
+    "prettier": "npx prettier --write ./src/**/*.ts"
   },
   "engines": {
     "node": ">=14",
diff --git a/scripts/codegen.ts b/scripts/codegen.ts
index 8113db3a6e6444b0223cd6a61a4487336bd6f491..340b290a8c593cbe523163b55ba79600fdd60669 100644
--- a/scripts/codegen.ts
+++ b/scripts/codegen.ts
@@ -1,4 +1,4 @@
-const axios = require("axios").default;
+const axios = require('axios').default;
 
 const client_id = 'f1a362d288c1b98099c7';
 const client_secret = 'eea605b96e01c796ff369935357eca920c5da4c5';
@@ -11,7 +11,8 @@ async function fetchAndSaveToken() {
     url: 'https://graphql.api.dailymotion.com/oauth/token',
     headers: {
       'Content-Type': 'application/x-www-form-urlencoded',
-      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0',
+      'User-Agent':
+        'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0',
       Accept: '*/*',
       'Accept-Language': 'en-GB,en;q=0.5',
       'Accept-Encoding': 'gzip, deflate, br, zstd',
@@ -24,13 +25,13 @@ async function fetchAndSaveToken() {
       'Sec-Fetch-Site': 'same-site',
       Priority: 'u=4',
       Pragma: 'no-cache',
-      'Cache-Control': 'no-cache'
+      'Cache-Control': 'no-cache',
     },
     data: {
       client_id,
       client_secret,
-      grant_type
-    }
+      grant_type,
+    },
   };
 
   try {
@@ -46,45 +47,45 @@ async function fetchAndSaveToken() {
 
 // Main function to setup GraphQL Codegen config
 async function setupCodegenConfig() {
-  
   const token = await fetchAndSaveToken();
 
   const config = {
     overwrite: true,
     schema: {
       // URL of the GraphQL endpoint
-      "https://graphql.api.dailymotion.com": {
+      'https://graphql.api.dailymotion.com': {
         // Headers to be sent with the request
         headers: {
           // Authorization header with the token
-          'Authorization': `Bearer ${token}`,
+          Authorization: `Bearer ${token}`,
           'Content-Type': 'application/json',
-          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0',
-          'Accept': '*/*',
+          'User-Agent':
+            'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0',
+          Accept: '*/*',
           'Accept-Language': 'en-GB,en;q=0.5',
           'Accept-Encoding': 'gzip, deflate, br, zstd',
-          'Origin': 'https://www.dailymotion.com',
-          'DNT': '1',
+          Origin: 'https://www.dailymotion.com',
+          DNT: '1',
           'Sec-GPC': '1',
-          'Connection': 'keep-alive',
+          Connection: 'keep-alive',
           'Sec-Fetch-Dest': 'empty',
           'Sec-Fetch-Mode': 'cors',
           'Sec-Fetch-Site': 'same-site',
-          'Priority': 'u=4',
-          'Cache-Control': 'no-cache'
+          Priority: 'u=4',
+          'Cache-Control': 'no-cache',
         },
       },
     },
     generates: {
-      "./types/CodeGenDailymotion.d.ts": {
+      './types/CodeGenDailymotion.d.ts': {
         plugins: [
-          "typescript",
-          "typescript-operations",
-          "typescript-resolvers",
+          'typescript',
+          'typescript-operations',
+          'typescript-resolvers',
         ],
       },
-      "./types/CodeGenDailymotion.schema.json": {
-        plugins: ["introspection"],
+      './types/CodeGenDailymotion.schema.json': {
+        plugins: ['introspection'],
       },
     },
   };
@@ -94,11 +95,11 @@ async function setupCodegenConfig() {
 
 export default new Promise((resolve, reject) => {
   setupCodegenConfig()
-    .then(config => {
+    .then((config) => {
       resolve(config);
-    }).catch(error => {
+    })
+    .catch((error) => {
       console.error('Failed to setup GraphQL Codegen config:', error);
       reject(error);
     });
-
-})
+});
diff --git a/src/DailymotionScript.ts b/src/DailymotionScript.ts
index 1633fdd581dca5e89d9ed8c93312ddac12417320..48a4e6f84484df1427d6573c97d6d66445e3e5b1 100644
--- a/src/DailymotionScript.ts
+++ b/src/DailymotionScript.ts
@@ -1,1290 +1,1347 @@
-let config: Config;
-let _settings: IDailymotionPluginSettings;
-
-const state = {
-	anonymousUserAuthorizationToken: "",
-	anonymousUserAuthorizationTokenExpirationDate: 0,
-	messageServiceToken: ""
-};
-
-const LIKE_PLAYLIST_ID = "LIKE_PLAYLIST";
-const FAVORITES_PLAYLIST_ID = "FAVORITES_PLAYLIST";
-const RECENTLY_WATCHED_PLAYLIST_ID = "RECENTLY_WATCHED_PLAYLIST";
-
-
-import {
-	BASE_URL,
-	SEARCH_CAPABILITIES,
-	BASE_URL_VIDEO,
-	BASE_URL_PLAYLIST,
-	USER_AGENT,
-	X_DM_AppInfo_Id,
-	X_DM_AppInfo_Type,
-	X_DM_AppInfo_Version,
-	X_DM_Neon_SSR,
-	BASE_URL_API,
-	BASE_URL_METADATA,
-	ERROR_TYPES,
-	LikedMediaSort,
-	CLIENT_ID,
-	CLIENT_SECRET,
-	BASE_URL_API_AUTH,
-	PLATFORM,
-	BASE_URL_COMMENTS,
-	BASE_URL_COMMENTS_AUTH,
-	BASE_URL_COMMENTS_THUMBNAILS
-} from './constants';
-
-import {
-	AUTOCOMPLETE_QUERY,
-	CHANNEL_QUERY_DESKTOP,
-	PLAYLIST_DETAILS_QUERY,
-	GET_USER_SUBSCRIPTIONS,
-	SEARCH_QUERY,
-	SEACH_DISCOVERY_QUERY,
-	CHANNEL_VIDEOS_QUERY,
-	WATCHING_VIDEO,
-	SEARCH_CHANNEL,
-	CHANNEL_PLAYLISTS_QUERY,
-	SUBSCRIPTIONS_QUERY,
-	GET_CHANNEL_PLAYLISTS_XID,
-	USER_LIKED_VIDEOS_QUERY,
-	USER_WATCHED_VIDEOS_QUERY,
-	USER_WATCH_LATER_VIDEOS_QUERY
-} from './gqlQueries';
-
-import {
-	getChannelNameFromUrl,
-	isUsernameUrl,
-	getQuery,
-	objectToUrlEncodedString,
-	generateUUIDv4
-} from './util';
-
-import {
-	Channel,
-	Collection,
-	CollectionConnection,
-	Live,
-	LiveConnection,
-	LiveEdge,
-	Maybe,
-	SuggestionConnection,
-	User,
-	Video,
-	VideoConnection,
-	VideoEdge
-} from '../types/CodeGenDailymotion';
-
-import {
-	SearchPagerAll,
-	SearchChannelPager,
-	ChannelVideoPager,
-	SearchPlaylistPager,
-	ChannelPlaylistPager
-} from './Pagers';
-
-
-import {
-	SourceChannelToGrayjayChannel,
-	SourceCollectionToGrayjayPlaylist,
-	SourceCollectionToGrayjayPlaylistDetails,
-	SourceVideoToGrayjayVideo,
-	SourceVideoToPlatformVideoDetailsDef
-} from './Mappers';
-
-
-// Will be used to store private playlists that require authentication
-const authenticatedPlaylistCollection: string[] = [];
-
-source.setSettings = function (settings) {
-	_settings = settings;
-}
-
-let COUNTRY_NAMES_TO_CODE: string[] = [];
-let VIDEOS_PER_PAGE_OPTIONS: number[]= [];
-let PLAYLISTS_PER_PAGE_OPTIONS: number[] = [];
-let CREATOR_AVATAR_HEIGHT: string[] = [];
-let THUMBNAIL_HEIGHT: string[] = [];
-
-//Source Methods
-source.enable = function (conf, settings, saveStateStr) {
-
-	config = conf ?? {};
-
-	COUNTRY_NAMES_TO_CODE = config?.settings?.find(s => s.variable == "preferredCountryOptionIndex")?.options ?? [];
-	VIDEOS_PER_PAGE_OPTIONS = config?.settings?.find(s => s.variable == "videosPerPageOptionIndex")?.options?.map(s => parseInt(s)) ?? [];
-	PLAYLISTS_PER_PAGE_OPTIONS = config?.settings?.find(s => s.variable == "playlistsPerPageOptionIndex")?.options?.map(s => parseInt(s)) ?? [];
-	CREATOR_AVATAR_HEIGHT = config?.settings?.find(s => s.variable == "avatarSizeOptionIndex")?.options?.map(s => `SQUARE_${s.replace("px","")}`) ?? [];
-	THUMBNAIL_HEIGHT = config?.settings?.find(s => s.variable == "thumbnailResolutionOptionIndex")?.options?.map(s => `PORTRAIT_${s.replace("px","")}`) ?? [];
-
-    const DEFAULT_SETTINGS = {
-        hideSensitiveContent: true,
-        avatarSizeOptionIndex: 8, // 720px
-        thumbnailResolutionOptionIndex: 7, // 1080px
-        preferredCountryOptionIndex: 0, // empty
-        videosPerPageOptionIndex: 3, // 20
-        playlistsPerPageOptionIndex: 0 // 5
-	};
-
-    _settings = { ...DEFAULT_SETTINGS, ...settings };
-
-	if (IS_TESTING) {
-
-		config.id = "9c87e8db-e75d-48f4-afe5-2d203d4b95c5";
-	}
-	
-	let didSaveState = false;
-	
-	try {
-		if (saveStateStr) {
-			const saveState = JSON.parse(saveStateStr);
-			if (saveState) {
-				state.anonymousUserAuthorizationToken = saveState.anonymousUserAuthorizationToken;
-				state.anonymousUserAuthorizationTokenExpirationDate = saveState.anonymousUserAuthorizationTokenExpirationDate;
-				state.messageServiceToken = saveState.messageServiceToken;
-				
-				if (!isTokenValid()) {
-					log("Token expired. Fetching a new one.");
-				} else {
-					didSaveState = true;
-					log("Using save state");
-				}
-			}
-		}
-	} catch (ex) {
-		log("Failed to parse saveState:" + ex);
-		didSaveState = false;
-	}
-
-	if (!didSaveState) {
-
-		log("Getting a new tokens");
-
-		const body = objectToUrlEncodedString({
-			client_id: CLIENT_ID,
-			client_secret: CLIENT_SECRET,
-			grant_type: 'client_credentials'
-		});
-
-		let batchRequests = http.batch()
-		.POST(BASE_URL_API_AUTH, body, {
-			'User-Agent': USER_AGENT,
-			'Content-Type': 'application/x-www-form-urlencoded',
-			'Origin': BASE_URL,
-			'DNT': '1',
-			'Sec-GPC': '1',
-			'Connection': 'keep-alive',
-			'Sec-Fetch-Dest': 'empty',
-			'Sec-Fetch-Mode': 'cors',
-			'Sec-Fetch-Site': 'same-site',
-			'Priority': 'u=4',
-			'Pragma': 'no-cache',
-			'Cache-Control': 'no-cache'
-		}, false);
-
-		if(config.allowAllHttpHeaderAccess){
-
-			batchRequests = batchRequests.POST(BASE_URL_COMMENTS_AUTH, "", {//// get token for message service api-2-0.spot.im
-				'User-Agent': USER_AGENT,
-				Accept: '*/*',
-				'Accept-Language': 'en-US,en;q=0.5',
-				'x-spot-id': 'sp_vWPN1lBu',
-				'x-post-id': 'no$post',
-				'Content-Type': 'application/json',
-				'Origin': BASE_URL,
-				Connection: 'keep-alive',
-				Referer: BASE_URL,
-				'Sec-Fetch-Dest': 'empty',
-				'Sec-Fetch-Mode': 'cors',
-				'Sec-Fetch-Site': 'cross-site',
-				Priority: 'u=6',
-				'Content-Length': '0'
-			}, false)
-
-		}
-
-		const responses = batchRequests.execute();
-		
-		const res = responses[0];
-
-		if (res.code !== 200) {
-			console.error('Failed to get token', res);
-			throw new ScriptException("", "Failed to get token: " + res.code + " - " + res.body);
-		}
-
-		const json = JSON.parse(res.body);
-
-		if (!json.token_type || !json.access_token) {
-			console.error('Invalid token response', res);
-			throw new ScriptException("", 'Invalid token response: ' + res.body);
-		}
-
-		state.anonymousUserAuthorizationToken = `${json.token_type} ${json.access_token}`;
-		state.anonymousUserAuthorizationTokenExpirationDate = Date.now() + (json.expires_in * 1000);
-		
-		if(config.allowAllHttpHeaderAccess)
-		{
-			const authenticateIm = responses[1];
-
-			if (!authenticateIm.isOk) {
-				// throw new UnavailableException('Failed to authenticate to comments service');
-				log('Failed to authenticate to comments service');
-			}
-
-			state.messageServiceToken = authenticateIm.headers["x-access-token"][0];
-		}
-	}
-
-}
-
-
-source.getHome = function () {
-	return getVideoPager({}, 0);
-};
-
-source.searchSuggestions = function (query): string[] {
-
-	try {
-
-		const jsonResponse = executeGqlQuery(
-			http,
-			{
-				operationName: 'AUTOCOMPLETE_QUERY',
-				variables: {
-					query
-				},
-				query: AUTOCOMPLETE_QUERY
-			});
-
-		return (jsonResponse?.data?.search?.suggestedVideos as SuggestionConnection)?.edges?.map(edge => edge?.node?.name ?? "") ?? [];
-	} catch (error: any) {
-		log('Failed to get search suggestions:' + error?.message);
-		return [];
-	}
-};
-
-
-source.getSearchCapabilities = (): ResultCapabilities => SEARCH_CAPABILITIES;
-
-
-source.search = function (query: string, type: string, order: string, filters) {
-	return getSearchPagerAll({ q: query, page: 1, type, order, filters });
-}
-
-source.searchChannels = function (query) {
-	return getSearchChannelPager({ q: query, page: 1 })
-}
-
-//Channel
-source.isChannelUrl = function (url) {
-	return isUsernameUrl(url);
-};
-
-source.getChannel = function (url) {
-
-	const channel_name = getChannelNameFromUrl(url);
-
-	const channelDetails = executeGqlQuery(
-		http,
-		{
-			operationName: 'CHANNEL_QUERY_DESKTOP',
-			variables: {
-				channel_name,
-				avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex]
-			},
-			query: CHANNEL_QUERY_DESKTOP
-		});
-
-	return SourceChannelToGrayjayChannel(config.id, channelDetails.data.channel as Channel);
-
-};
-
-source.getChannelContents = function (url, type, order, filters) {
-
-	const page = 1;
-	return getChannelContentsPager(
-		url,
-		page,
-		type,
-		order,
-		filters
-	)
-}
-
-source.getChannelPlaylists = (url): SearchPlaylistPager => {
-	try {
-		return getChannelPlaylists(url, 1);
-	} catch (error) {
-		log('Failed to get channel playlists:' + error?.message);
-		return new ChannelPlaylistPager([]);
-	}
-}
-
-source.getChannelCapabilities = (): ResultCapabilities => {
-	return {
-		types: [Type.Feed.Mixed],
-		sorts: [Type.Order.Chronological, "Popular"],
-		filters: []
-	};
-};
-
-//Video
-source.isContentDetailsUrl = function (url) {
-	return url.startsWith(BASE_URL_VIDEO);
-};
-
-source.getContentDetails = function (url) {
-	return getSavedVideo(url, false);
-};
-
-source.saveState = () => {
-	return JSON.stringify(state);
-};
-
-source.getSubComments = (comment) => {
-	const params = { "count": 5, "offset": 0, "parent_id": comment.context.id, "sort_by": "best", "child_count": comment.replyCount };
-	return getCommentPager(comment.contextUrl, params, 0);
-}
-
-
-source.getComments = (url) => {
-
-	if(!config.allowAllHttpHeaderAccess) {
-		return new PlatformCommentPager([], false, url, {}, 0);
-	}
-
-	const params = { "sort_by": "best", "offset": 0, "count": 10, "message_id": null, "depth": 2, "child_count": 2 };
-	return getCommentPager(url, params, 0);
-}
-
-function getCommentPager(url, params, page) {
-
-	try {
-		const xid = url.split('/').pop();
-
-		const commentsHeaders = {
-			'User-Agent': USER_AGENT,
-			Accept: 'application/json',
-			'Accept-Language': 'en-US,en;q=0.5',
-			'x-access-token': state.messageServiceToken,
-			'Content-Type': 'application/json',
-			'x-spot-id': 'sp_vWPN1lBu',
-			'x-post-id': xid,
-			'Origin': BASE_URL,
-			Connection: 'keep-alive',
-			Referer: BASE_URL,
-			'Sec-Fetch-Dest': 'empty',
-			'Sec-Fetch-Mode': 'cors',
-			'Sec-Fetch-Site': 'cross-site',
-			Priority: 'u=6',
-			TE: 'trailers'
-		}
-
-		const commentRequest = http.POST(BASE_URL_COMMENTS, JSON.stringify(params), commentsHeaders, false);
-
-		if (!commentRequest.isOk) {
-			throw new UnavailableException('Failed to authenticate to comments service');
-		}
-
-		const comments = JSON.parse(commentRequest.body);
-
-		const users = comments.conversation.users;
-
-		const results = comments.conversation.comments.map(v => {
-
-			const user = users[v.user_id];
-
-			return new Comment({
-				contextUrl: url,
-				author: new PlatformAuthorLink(
-					new PlatformID(PLATFORM, user.id ?? "", config.id), 
-					user.display_name ?? "",
-					"",
-					`${BASE_URL_COMMENTS_THUMBNAILS}/${user.image_id}`
-				),
-				message: v.content[0].text,
-				rating: new RatingLikes(v.stars),
-				date: v.written_at,
-				replyCount: v.total_replies_count ?? 0,
-				context: { id: v.id }
-			});
-
-		});
-
-		return new PlatformCommentPager(results, comments.conversation.has_next, url, params, ++page);
-	} catch (error) {
-		bridge.log('Failed to get comments:' + error?.message);
-		return new PlatformCommentPager([], false, url, params, 0);
-	}
-
-}
-
-class PlatformCommentPager extends CommentPager {
-	constructor(results, hasMore, path, params, page) {
-		super(results, hasMore, { path, params, page });
-	}
-
-	nextPage() {
-		return getCommentPager(this.context.path, this.context.params, (this.context.page ?? 0) + 1);
-	}
-}
-
-//Playlist
-source.isPlaylistUrl = (url): boolean => {
-	return url.startsWith(BASE_URL_PLAYLIST) ||
-		url === LIKE_PLAYLIST_ID ||
-		url === FAVORITES_PLAYLIST_ID ||
-		url === RECENTLY_WATCHED_PLAYLIST_ID;
-};
-
-source.searchPlaylists = (query, type, order, filters) => {
-	return searchPlaylists({ q: query, type, order, filters });
-};
-
-source.getPlaylist = (url: string): PlatformPlaylistDetails => {
-
-	const usePlatformAuth = authenticatedPlaylistCollection.includes(url);
-
-	const thumbnailResolutionIndex = _settings.thumbnailResolutionOptionIndex;
-
-	if (url === LIKE_PLAYLIST_ID) {
-		return getLikePlaylist(config.id, http, usePlatformAuth, thumbnailResolutionIndex);
-	}
-
-	if (url === FAVORITES_PLAYLIST_ID) {
-		return getFavoritesPlaylist(config.id, http, usePlatformAuth, thumbnailResolutionIndex);
-	}
-
-	if (url === RECENTLY_WATCHED_PLAYLIST_ID) {
-		return getRecentlyWatchedPlaylist(config.id, http, usePlatformAuth, thumbnailResolutionIndex);
-	}
-
-	const xid = url.split('/').pop();
-
-	const variables = {
-		xid,
-		avatar_size: CREATOR_AVATAR_HEIGHT[_settings.avatarSizeOptionIndex],
-		thumbnail_resolution: THUMBNAIL_HEIGHT[thumbnailResolutionIndex],
-	}
-
-	let jsonResponse = executeGqlQuery(
-		http,
-		{
-			operationName: 'PLAYLIST_VIDEO_QUERY',
-			variables,
-			query: PLAYLIST_DETAILS_QUERY,
-			usePlatformAuth
-		});
-
-	const videos: PlatformVideo[] = jsonResponse?.data?.collection?.videos?.edges.map(edge => {
-		return SourceVideoToGrayjayVideo(config.id, edge.node as Video);
-	});
-
-	return SourceCollectionToGrayjayPlaylistDetails(config.id, jsonResponse?.data?.collection as Collection, videos);
-
-}
-
-source.getUserSubscriptions = (): string[] => {
-
-	if (!bridge.isLoggedIn()) {
-		log("Failed to retrieve subscriptions page because not logged in.");
-		throw new ScriptException("Not logged in");
-	}
-
-	const headers = {
-		'Content-Type': 'application/json',
-		'User-Agent': USER_AGENT,
-		// Accept: '*/*, */*',
-		'Accept-Language': 'en-GB',
-		Referer: `${BASE_URL}/library/subscriptions`,
-		'X-DM-Preferred-Country': getPreferredCountry(_settings?.preferredCountryOptionIndex),
-		Origin: BASE_URL,
-		DNT: '1',
-		Connection: 'keep-alive',
-		'Sec-Fetch-Dest': 'empty',
-		'Sec-Fetch-Mode': 'cors',
-		'Sec-Fetch-Site': 'same-site',
-		Priority: 'u=4',
-		Pragma: 'no-cache',
-		'Cache-Control': 'no-cache',
-	}
-
-	const usePlatformAuth = true;
-
-	const fetchSubscriptions = (page, first): string[] => {
-		const jsonResponse = executeGqlQuery(
-			http,
-			{
-				operationName: 'SUBSCRIPTIONS_QUERY',
-				variables: {
-					first: first,
-					page: page,
-					avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
-				},
-				headers,
-				query: GET_USER_SUBSCRIPTIONS,
-				usePlatformAuth
-			});
-
-		return (jsonResponse?.data?.me?.channel as Channel)?.followings?.edges?.map(edge => edge?.node?.creator?.name ?? "") ?? [];
-	};
-
-	const first = 100;  // Number of records to fetch per page
-	let page = 1;
-	let subscriptions: string[] = [];
-
-	// There is a totalCount ($.data.me.channel.followings.totalCount) property but it's not reliable. 
-	// For example, it may return 0 even if there are subscriptions, or it may return a number that is not the actual number of subscriptions.
-	// For now, it's better to fetch until no more results are returned
-
-	let items: string[] = [];
-
-	do {
-		const response = fetchSubscriptions(page, first);
-
-		items = response.map(creatorName => `${BASE_URL}/${creatorName}`);
-
-		subscriptions.push(...items);
-		page++;
-	} while (items.length);
-
-	return subscriptions;
-};
-
-
-source.getUserPlaylists = (): string[] => {
-
-	if (!bridge.isLoggedIn()) {
-		log("Failed to retrieve subscriptions page because not logged in.");
-		throw new ScriptException("Not logged in");
-	}
-
-	const headers = {
-		'Content-Type': 'application/json',
-		'User-Agent': USER_AGENT,
-		'Accept-Language': 'en-GB',
-		Referer: 'https://www.dailymotion.com/',
-		'Sec-GPC': '1',
-		'X-DM-Preferred-Country': getPreferredCountry(_settings?.preferredCountryOptionIndex),
-		Origin: BASE_URL,
-		DNT: '1',
-		Connection: 'keep-alive',
-		'Sec-Fetch-Dest': 'empty',
-		'Sec-Fetch-Mode': 'cors',
-		'Sec-Fetch-Site': 'same-site',
-		Priority: 'u=1',
-		Pragma: 'no-cache',
-		'Cache-Control': 'no-cache',
-	}
-
-	const jsonResponse = executeGqlQuery(
-		http,
-		{
-			operationName: 'SUBSCRIPTIONS_QUERY',
-			headers,
-			query: SUBSCRIPTIONS_QUERY,
-			usePlatformAuth: true
-		});
-
-	const userName = (jsonResponse?.data?.me?.channel as Channel)?.name;
-
-	const playlists = getPlaylistsByUsername(userName, headers, true);
-
-	[
-		LIKE_PLAYLIST_ID,
-		FAVORITES_PLAYLIST_ID,
-		RECENTLY_WATCHED_PLAYLIST_ID
-	].forEach(playlistId => {
-
-		if (!authenticatedPlaylistCollection.includes(playlistId)) {
-			authenticatedPlaylistCollection.push(playlistId);
-		}
-
-		if (!playlists.includes(playlistId)) {
-			playlists.push(playlistId);
-		}
-	});
-
-	return playlists;
-
-}
-
-source.getChannelTemplateByClaimMap = () => {
-	return {
-		//Dailymotion claim type
-		27: {
-			0: BASE_URL + "/{{CLAIMVALUE}}",
-		}
-	};
-};
-
-
-function getPlaylistsByUsername(userName, headers, usePlatformAuth = false): string[] {
-
-
-	const collections = executeGqlQuery(
-		http,
-		{
-			operationName: 'CHANNEL_PLAYLISTS_QUERY',
-			variables: {
-				channel_name: userName,
-				sort: "recent",
-				page: 1,
-				first: 99,
-				avatar_size: CREATOR_AVATAR_HEIGHT[_settings.avatarSizeOptionIndex],
-				thumbnail_resolution: THUMBNAIL_HEIGHT[_settings.thumbnailResolutionOptionIndex],
-			},
-			headers,
-			query: GET_CHANNEL_PLAYLISTS_XID,
-			usePlatformAuth
-		}
-	);
-
-	const playlists: string[] = collections.data.channel.collections.edges.map(edge => {
-		const playlistUrl = `${BASE_URL_PLAYLIST}/${edge.node.xid}`;
-		if (!authenticatedPlaylistCollection.includes(playlistUrl)) {
-			authenticatedPlaylistCollection.push(playlistUrl);
-		}
-		return playlistUrl;
-	});
-
-	return playlists;
-
-}
-
-function searchPlaylists(contextQuery) {
-
-	const context = getQuery(contextQuery);
-
-	const variables = {
-		"query": context.q,
-		"sortByVideos": context.sort,
-		"durationMaxVideos": context.filters?.durationMaxVideos,
-		"durationMinVideos": context.filters?.durationMinVideos,
-		"createdAfterVideos": context.filters?.createdAfterVideos, //Represents a DateTime value as specified by iso8601
-		"shouldIncludeChannels": false,
-		"shouldIncludePlaylists": true,
-		"shouldIncludeVideos": false,
-		"shouldIncludeLives": false,
-		"page": context.page,
-		"limit": VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
-		"thumbnail_resolution": THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
-		"avatar_size": CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
-	}
-
-
-	const jsonResponse = executeGqlQuery(
-		http,
-		{
-			operationName: 'SEARCH_QUERY',
-			variables: variables,
-			query: SEARCH_QUERY,
-			headers: undefined
-		});
-
-	const playlistConnection = jsonResponse?.data?.search?.playlists as CollectionConnection;
-
-	const searchResults = playlistConnection?.edges?.map(edge => {
-		return SourceCollectionToGrayjayPlaylist(config.id, edge?.node);
-	});
-
-	const hasMore = playlistConnection?.pageInfo?.hasNextPage;
-
-	if (!searchResults || searchResults.length === 0) {
-		return new PlaylistPager([]);
-	}
-
-	const params = {
-		query: context.q,
-		sort: context.sort,
-		filters: context.filters,
-	}
-
-	return new SearchPlaylistPager(searchResults, hasMore, params, context.page, searchPlaylists);
-}
-
-
-//Internals
-
-
-function getVideoPager(params, page) {
-
-	const count = VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex];
-	
-	if (!params) {
-		params = {};
-	}
-
-	params = { ...params, count }
-
-
-	const headersToAdd = {
-		"User-Agent": USER_AGENT,
-		"Referer": BASE_URL,
-		"Content-Type": "application/json",
-		"X-DM-AppInfo-Id": X_DM_AppInfo_Id,
-		"X-DM-AppInfo-Type": X_DM_AppInfo_Type,
-		"X-DM-AppInfo-Version": X_DM_AppInfo_Version,
-		"X-DM-Neon-SSR": X_DM_Neon_SSR,
-		"X-DM-Preferred-Country": getPreferredCountry(_settings?.preferredCountryOptionIndex),
-		"Origin": BASE_URL,
-		"DNT": "1",
-		"Sec-Fetch-Site": "same-site",
-		"Priority": "u=4",
-		"Pragma": "no-cache",
-		"Cache-Control": "no-cache"
-	};
-
-
-	let obj;
-
-	try {
-		obj = executeGqlQuery(
-			http,
-			{
-				operationName: 'SEACH_DISCOVERY_QUERY',
-				variables: {
-					avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
-					thumbnail_resolution: THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
-				},
-				query: SEACH_DISCOVERY_QUERY,
-				headers: headersToAdd,
-			});
-
-	} catch (error) {
-		return new VideoPager([], false, { params });
-	}
-
-	const results = obj?.data?.home?.neon?.sections?.edges[0]?.node?.components?.edges
-		?.filter(edge => edge?.node?.id)
-		?.map(edge => {
-
-			return SourceVideoToGrayjayVideo(config.id, edge.node as Video);
-
-		})
-
-	const hasMore = obj?.data?.home?.neon?.sections?.edges[0]?.node?.components?.pageInfo?.hasNextPage ?? false;
-	return new SearchPagerAll(results, hasMore, params, page, getVideoPager);
-}
-
-function getChannelContentsPager(url, page, type, order, filters) {
-
-	const channel_name = getChannelNameFromUrl(url);
-
-	const shouldLoadVideos = type === Type.Feed.Mixed || type === Type.Feed.Videos;
-	const shouldLoadLives = type === Type.Feed.Mixed || type === Type.Feed.Streams || type === Type.Feed.Live;
-
-	if (IS_TESTING) {
-		log(`Getting channel contents for ${url}, page: ${page}, type: ${type}, order: ${order}, shouldLoadVideos: ${shouldLoadVideos}, shouldLoadLives: ${shouldLoadLives}, filters: ${JSON.stringify(filters)}`);
-	}
-
-	/** 
-		Recent = Sort liked medias by most recent.
-		Visited - Sort liked medias by most viewed
-	*/
-	let sort: string;
-
-	if (order == Type.Order.Chronological) {
-		sort = LikedMediaSort.Recent;
-	} else if (order == "Popular") {
-		sort = LikedMediaSort.Visited;
-	} else {
-		sort = LikedMediaSort.Recent;
-	}
-	
-	const jsonResponse = executeGqlQuery(
-		http,
-		{
-			operationName: 'CHANNEL_VIDEOS_QUERY',
-			variables: {
-				channel_name,
-				sort,
-				page: page ?? 1,
-				allowExplicit: !_settings.hideSensitiveContent,
-				first: VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
-				avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
-				thumbnail_resolution: THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
-				shouldLoadLives,
-				shouldLoadVideos
-			},
-			query: CHANNEL_VIDEOS_QUERY
-		});
-		
-	const channel = jsonResponse?.data?.channel as Channel;
-
-	const all: (Live | Video)[] = [
-		...(channel?.lives?.edges?.filter(e => e?.node?.isOnAir)?.map(e => e?.node as Live) ?? []),
-		...(channel?.videos?.edges?.map(e => e?.node as Video) ?? [])
-	  ];
-
-	let videos = all
-		.map((node => SourceVideoToGrayjayVideo(config.id, node)));
-
-		
-	const videosHasNext = channel?.videos?.pageInfo?.hasNextPage;
-	const livesHasNext = channel?.lives?.pageInfo?.hasNextPage;
-	const hasNext = videosHasNext || livesHasNext || false;
-
-	const params = {
-		url,
-		type,
-		order,
-		page,
-		filters
-	}
-
-	return new ChannelVideoPager(
-		videos,
-		hasNext,
-		params,
-		getChannelContentsPager
-	);
-}
-
-function getSearchPagerAll(contextQuery): VideoPager {
-
-	const context = getQuery(contextQuery);
-
-	const variables = {
-		"query": context.q,
-		"sortByVideos": context.sort,
-		"durationMaxVideos": context.filters?.durationMaxVideos,
-		"durationMinVideos": context.filters?.durationMinVideos,
-		"createdAfterVideos": context.filters?.createdAfterVideos, //Represents a DateTime value as specified by iso8601
-		"shouldIncludeChannels": false,
-		"shouldIncludePlaylists": false,
-		"shouldIncludeVideos": true,
-		"shouldIncludeLives": true,
-		"page": context.page ?? 1,
-		"limit": VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
-		"avatar_size": CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
-		"thumbnail_resolution": THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex]
-	}
-
-
-	const jsonResponse = executeGqlQuery(
-		http,
-		{
-			operationName: 'SEARCH_QUERY',
-			variables: variables,
-			query: SEARCH_QUERY,
-			headers: undefined
-		});
-
-
-	const videoConnection = jsonResponse?.data?.search?.videos as VideoConnection;
-	const liveConnection = jsonResponse?.data?.search?.lives as LiveConnection;
-
-	const all: (VideoEdge | LiveEdge | null)[] = [
-		...(videoConnection?.edges ?? []),
-		...(liveConnection?.edges ?? [])
-	]
-
-	const results: PlatformVideo[] = all.map(edge => SourceVideoToGrayjayVideo(config.id, edge?.node));
-
-	const params = {
-		query: context.q,
-		sort: context.sort,
-		filters: context.filters,
-	}
-	return new SearchPagerAll(results, videoConnection?.pageInfo?.hasNextPage, params, context.page, getSearchPagerAll);
-}
-
-
-function getSavedVideo(url, usePlatformAuth = false) {
-
-	const id = url.split('/').pop();
-
-	const player_metadata_url = `${BASE_URL_METADATA}/${id}?embedder=https%3A%2F%2Fwww.dailymotion.com%2Fvideo%2Fx8yb2e8&geo=1&player-id=xjnde&locale=en-GB&dmV1st=ce2035cd-bdca-4d7b-baa4-127a17490ca5&dmTs=747022&is_native_app=0&app=com.dailymotion.neon&client_type=webapp&section_type=player&component_style=_`;
-
-	const headers1 = {
-		"User-Agent": USER_AGENT,
-		"Accept": "*/*",
-		"Referer": "https://geo.dailymotion.com/",
-		"Origin": "https://geo.dailymotion.com",
-		"DNT": "1",
-		"Connection": "keep-alive",
-		"Sec-Fetch-Dest": "empty",
-		"Sec-Fetch-Mode": "cors",
-		"Sec-Fetch-Site": "same-site",
-		"Pragma": "no-cache",
-		"Cache-Control": "no-cache"
-	}
-
-	if (_settings.hideSensitiveContent) {
-		headers1["Cookie"] = "ff=on"
-	} else {
-		headers1["Cookie"] = "ff=off"
-	}
-
-	const videoDetailsRequestBody = JSON.stringify({
-		operationName: "WATCHING_VIDEO",
-		variables:
-		{
-			"xid": id,
-			"avatar_size": CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
-			"thumbnail_resolution": THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex]
-		},
-		query: WATCHING_VIDEO
-	});
-
-	const videoDetailsRequestHeaders: IDictionary<string> = {
-		"Content-Type": "application/json",
-		"User-Agent": USER_AGENT,
-		"Accept": "*/*, */*",
-		"Referer": `${BASE_URL_VIDEO}/${id}`,
-		"X-DM-AppInfo-Id": X_DM_AppInfo_Id,
-		"X-DM-AppInfo-Type": X_DM_AppInfo_Type,
-		"X-DM-AppInfo-Version": X_DM_AppInfo_Version,
-		"X-DM-Neon-SSR": X_DM_Neon_SSR,
-		"X-DM-Preferred-Country": getPreferredCountry(_settings?.preferredCountryOptionIndex),
-		"Origin": BASE_URL,
-		"DNT": "1",
-		"Connection": "keep-alive",
-		"Sec-Fetch-Dest": "empty",
-		"Sec-Fetch-Mode": "cors",
-		"Sec-Fetch-Site": "same-site",
-		"Priority": "u=4",
-		"Pragma": "no-cache",
-		"Cache-Control": "no-cache"
-	};
-	
-	if (!usePlatformAuth) {
-		videoDetailsRequestHeaders.Authorization = state.anonymousUserAuthorizationToken;
-	}
-	
-	const responses = http.batch()
-			.GET(
-				player_metadata_url,
-				headers1,
-				usePlatformAuth
-			)
-			.POST(
-				BASE_URL_API,
-				videoDetailsRequestBody,
-				videoDetailsRequestHeaders,
-				usePlatformAuth
-			  )
-		.execute()
-
-	const player_metadataResponse = responses[0];
-	const video_details_response = responses[1];
-
-	if (!player_metadataResponse.isOk) {
-		throw new UnavailableException('Unable to get player metadata');
-	}
-
-	const player_metadata = JSON.parse(player_metadataResponse.body);
-
-	if (player_metadata.error) {
-
-		if (player_metadata.error.code && ERROR_TYPES[player_metadata.error.code] !== undefined) {
-			throw new UnavailableException(ERROR_TYPES[player_metadata.error.code]);
-		}
-
-		throw new UnavailableException('This content is not available');
-	}	
-
-	if (video_details_response.code != 200) {
-		throw new UnavailableException('Failed to get video details');
-	}
-
-	const video_details = JSON.parse(video_details_response.body);
-
-	const video = video_details?.data?.video as Video;
-
-	const platformVideoDetails: PlatformVideoDetailsDef = SourceVideoToPlatformVideoDetailsDef(
-	  config.id,
-	  video,
-	  player_metadata
-	);
-
-	return new PlatformVideoDetails(platformVideoDetails);
-}
-
-function getSearchChannelPager(context) {
-
-	const searchResponse = executeGqlQuery(
-		http, {
-		operationName: "SEARCH_QUERY",
-		variables: {
-			query: context.q,
-			page: context.page ?? 1,
-			limit: VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
-			avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex]
-		},
-		query: SEARCH_CHANNEL
-	});
-
-	const results = searchResponse?.data?.search?.channels?.edges.map(edge => {
-      
-		const channel = edge.node as Channel;
-      
-      return SourceChannelToGrayjayChannel(
-        config.id,
-        channel
-      );
-	});
-
-	const params = {
-		query: context.q,
-	}
-
-    return new SearchChannelPager(
-      results,
-      searchResponse?.data?.search?.channels?.pageInfo?.hasNextPage,
-      params,
-      context.page,
-      getSearchChannelPager
-    );
-}
-
-function getChannelPlaylists(url: string, page: number = 1): SearchPlaylistPager {
-
-	const headers = {
-		'Content-Type': 'application/json',
-		'User-Agent': USER_AGENT,
-		'Accept-Language': 'en-GB',
-		Referer: `${BASE_URL}/library/subscriptions`,
-		'X-DM-AppInfo-Id': X_DM_AppInfo_Id,
-		'X-DM-AppInfo-Type': X_DM_AppInfo_Type,
-		'X-DM-AppInfo-Version': X_DM_AppInfo_Version,
-		'X-DM-Neon-SSR': '0',
-		'X-DM-Preferred-Country': getPreferredCountry(_settings?.preferredCountryOptionIndex),
-		Origin: BASE_URL,
-		DNT: '1',
-		Connection: 'keep-alive',
-		'Sec-Fetch-Dest': 'empty',
-		'Sec-Fetch-Mode': 'cors',
-		'Sec-Fetch-Site': 'same-site',
-		Priority: 'u=4',
-		Pragma: 'no-cache',
-		'Cache-Control': 'no-cache',
-	};
-
-	const usePlatformAuth = false;
-	const channel_name = getChannelNameFromUrl(url);
-
-	const jsonResponse1 = executeGqlQuery(
-		http,
-		{
-			operationName: 'CHANNEL_PLAYLISTS_QUERY',
-			variables: {
-				channel_name,
-				sort: "recent",
-				page,
-				first: PLAYLISTS_PER_PAGE_OPTIONS[_settings.playlistsPerPageOptionIndex],
-				avatar_size: CREATOR_AVATAR_HEIGHT[_settings.avatarSizeOptionIndex],
-				thumbnail_resolution: THUMBNAIL_HEIGHT[_settings.thumbnailResolutionOptionIndex],
-			},
-			headers,
-			query: CHANNEL_PLAYLISTS_QUERY,
-			usePlatformAuth
-		}
-	)
-
-	const channel = (jsonResponse1.data.channel as Channel);
-
-	const content: PlatformPlaylist[] = (channel?.collections?.edges ?? []).map(edge => {
-		return SourceCollectionToGrayjayPlaylist(config.id, edge?.node);
-	});
-
-	if (content?.length === 0) {
-		return new ChannelPlaylistPager([]);
-	}
-
-	const params = {
-		url
-	}
-
-	const hasMore = channel?.collections?.pageInfo?.hasNextPage ?? false;
-
-	return new ChannelPlaylistPager(content, hasMore, params, page, getChannelPlaylists);
-}
-
-function isTokenValid() {
-    const currentTime = Date.now();
-    return state.anonymousUserAuthorizationTokenExpirationDate > currentTime;
-}
-
-function executeGqlQuery(httpClient, requestOptions) {
-
-	const headersToAdd = requestOptions.headers || {
-		"User-Agent": USER_AGENT,
-		"Accept": "*/*",
-		// "Accept-Language": Accept_Language,
-		"Referer": BASE_URL,
-		"Origin": BASE_URL,
-		"DNT": "1",
-		"Connection": "keep-alive",
-		"Sec-Fetch-Dest": "empty",
-		"Sec-Fetch-Mode": "cors",
-		"Sec-Fetch-Site": "same-site",
-		"Pragma": "no-cache",
-		"Cache-Control": "no-cache"
-	}
-
-	const gql = JSON.stringify({
-		operationName: requestOptions.operationName,
-		variables: requestOptions.variables,
-		query: requestOptions.query,
-	});
-	
-	const usePlatformAuth = requestOptions.usePlatformAuth == undefined ? false : requestOptions.usePlatformAuth;
-	const throwOnError = requestOptions.throwOnError == undefined ? true : requestOptions.throwOnError;
-	
-	if (!usePlatformAuth) {
-		headersToAdd.Authorization = state.anonymousUserAuthorizationToken;
-	}
-
-	const res = httpClient.POST(BASE_URL_API, gql, headersToAdd, usePlatformAuth);
-
-	if (!res.isOk) {
-		console.error('Failed to get token', res);
-		if (throwOnError) {
-			throw new ScriptException("Failed to get token", res);
-		}
-	}
-
-	const body = JSON.parse(res.body);
-
-	// some errors may be returned in the body with a status code 200
-	if (body.errors) {
-		const message = body.errors.map(e => e.message).join(', ');
-
-		if (throwOnError) {
-			throw new UnavailableException(message);
-		}
-	}
-
-	return body;
-}
-
-
-function getPages<TI, TO>(
-	httpClient: IHttp,
-	query: string,
-	operationName: string,
-	variables: any,
-	usePlatformAuth: boolean,
-	setRoot: (jsonResponse: any) => TI,
-	hasNextCallback: (page: TI) => boolean,
-	getNextPage: (page: TI, currentPage) => number,
-	map: (page: any) => TO[]
-
-): TO[] {
-
-	let all: TO[] = [];
-
-	if (!hasNextCallback) {
-		hasNextCallback = () => false;
-	}
-
-	let hasNext = true;
-	let nextPage = 1;
-
-	do {
-
-		variables = { ...variables, page: nextPage };
-
-		const jsonResponse = executeGqlQuery(
-			httpClient,
-			{
-				operationName,
-				variables,
-				query,
-				usePlatformAuth
-			});
-
-		const root = setRoot(jsonResponse);
-
-		nextPage = getNextPage(root, nextPage);
-
-		const items = map(root);
-
-		hasNext = hasNextCallback(root);
-
-		all = all.concat(items);
-
-	} while (hasNext);
-
-	return all;
-}
-
-function getLikePlaylist(pluginId: string, httpClient: IHttp, usePlatformAuth: boolean = false, thumbnailResolutionIndex: number = 0): PlatformPlaylistDetails {
-	return getPlatformSystemPlaylist({
-		pluginId,
-		httpClient,
-		query: USER_LIKED_VIDEOS_QUERY,
-		operationName: 'USER_LIKED_VIDEOS_QUERY',
-		rootObject: 'likedMedias',
-		playlistName: 'Liked Videos',
-		usePlatformAuth,
-		thumbnailResolutionIndex
-	});
-
-}
-
-function getFavoritesPlaylist(pluginId: string, httpClient: IHttp, usePlatformAuth: boolean = false, thumbnailResolutionIndex: number = 0): PlatformPlaylistDetails {
-	return getPlatformSystemPlaylist({
-		pluginId,
-		httpClient,
-		query: USER_WATCH_LATER_VIDEOS_QUERY,
-		operationName: 'USER_WATCH_LATER_VIDEOS_QUERY',
-		rootObject: 'watchLaterMedias',
-		playlistName: 'Favorites',
-		usePlatformAuth,
-		thumbnailResolutionIndex
-	})
-}
-
-function getRecentlyWatchedPlaylist(pluginId: string, httpClient: IHttp, usePlatformAuth: boolean = false, thumbnailResolutionIndex: number = 0): PlatformPlaylistDetails {
-	return getPlatformSystemPlaylist({
-		pluginId,
-		httpClient,
-		query: USER_WATCHED_VIDEOS_QUERY,
-		operationName: 'USER_WATCHED_VIDEOS_QUERY',
-		rootObject: 'watchedVideos',
-		playlistName: 'Recently Watched',
-		usePlatformAuth,
-		thumbnailResolutionIndex
-
-	});
-}
-
-function getPlatformSystemPlaylist(opts: IPlatformSystemPlaylist): PlatformPlaylistDetails {
-
-	const videos: PlatformVideo[] = getPages<Maybe<User>, PlatformVideo>(
-		opts.httpClient,
-		opts.query,
-		opts.operationName,
-		{
-			page: 1,
-			thumbnail_resolution: THUMBNAIL_HEIGHT[opts.thumbnailResolutionIndex]
-		},
-		opts.usePlatformAuth,
-		(jsonResponse) => jsonResponse?.data?.me,//set root
-		(me) => (me?.[opts.rootObject]?.edges.length ?? 0) > 0 ?? false,//hasNextCallback
-		(me, currentPage) => ++currentPage, //getNextPage
-		(me) => me?.[opts.rootObject]?.edges.map(edge => {
-			return SourceVideoToGrayjayVideo(opts.pluginId, edge.node as Video);
-		}));
-
-	const collection = {
-		"id": generateUUIDv4(),
-		"name": opts.playlistName,
-		"creator": {}
-	}
-
-	return SourceCollectionToGrayjayPlaylistDetails(opts.pluginId, collection as Collection, videos);
-}
-
-function getPreferredCountry(preferredCountryIndex) {
-    const country = COUNTRY_NAMES_TO_CODE[preferredCountryIndex];
-	const parts = country.split('-');
-	const code = parts[0] ?? "";
-    return (code  || '').toLowerCase();
-}
-
-log("LOADED");
\ No newline at end of file
+let config: Config;
+let _settings: IDailymotionPluginSettings;
+
+const state = {
+  anonymousUserAuthorizationToken: '',
+  anonymousUserAuthorizationTokenExpirationDate: 0,
+  messageServiceToken: '',
+};
+
+import {
+  BASE_URL,
+  SEARCH_CAPABILITIES,
+  BASE_URL_VIDEO,
+  BASE_URL_PLAYLIST,
+  USER_AGENT,
+  X_DM_AppInfo_Id,
+  X_DM_AppInfo_Type,
+  X_DM_AppInfo_Version,
+  X_DM_Neon_SSR,
+  BASE_URL_API,
+  BASE_URL_METADATA,
+  ERROR_TYPES,
+  LikedMediaSort,
+  PLATFORM,
+  BASE_URL_COMMENTS,
+  BASE_URL_COMMENTS_AUTH,
+  BASE_URL_COMMENTS_THUMBNAILS,
+  FAVORITES_PLAYLIST_ID,
+  LIKE_PLAYLIST_ID,
+  RECENTLY_WATCHED_PLAYLIST_ID,
+  REGEX_VIDEO_CHANNEL_URL,
+  REGEX_VIDEO_PLAYLIST_URL,
+  REGEX_VIDEO_URL,
+  REGEX_VIDEO_URL_1,
+  REGEX_VIDEO_URL_EMBED,
+} from './constants';
+
+import {
+  AUTOCOMPLETE_QUERY,
+  CHANNEL_QUERY_DESKTOP,
+  PLAYLIST_DETAILS_QUERY,
+  GET_USER_SUBSCRIPTIONS,
+  SEARCH_QUERY,
+  SEACH_DISCOVERY_QUERY,
+  CHANNEL_VIDEOS_QUERY,
+  WATCHING_VIDEO,
+  SEARCH_CHANNEL,
+  CHANNEL_PLAYLISTS_QUERY,
+  SUBSCRIPTIONS_QUERY,
+  GET_CHANNEL_PLAYLISTS_XID,
+  USER_LIKED_VIDEOS_QUERY,
+  USER_WATCHED_VIDEOS_QUERY,
+  USER_WATCH_LATER_VIDEOS_QUERY,
+} from './gqlQueries';
+
+import { getChannelNameFromUrl, getQuery, generateUUIDv4 } from './util';
+
+import {
+  Channel,
+  Collection,
+  CollectionConnection,
+  Live,
+  LiveConnection,
+  LiveEdge,
+  Maybe,
+  SuggestionConnection,
+  User,
+  Video,
+  VideoConnection,
+  VideoEdge,
+} from '../types/CodeGenDailymotion';
+
+import {
+  SearchPagerAll,
+  SearchChannelPager,
+  ChannelVideoPager,
+  SearchPlaylistPager,
+  ChannelPlaylistPager,
+} from './Pagers';
+
+import {
+  SourceChannelToGrayjayChannel,
+  SourceCollectionToGrayjayPlaylist,
+  SourceCollectionToGrayjayPlaylistDetails,
+  SourceVideoToGrayjayVideo,
+  SourceVideoToPlatformVideoDetailsDef,
+} from './Mappers';
+
+import {
+  IDailymotionPluginSettings,
+  IDictionary,
+  IPlatformSystemPlaylist,
+} from '../types/types';
+import {
+  extractClientCredentials,
+  getTokenFromClientCredentials,
+} from './extraction';
+
+// Will be used to store private playlists that require authentication
+const authenticatedPlaylistCollection: string[] = [];
+
+source.setSettings = function (settings) {
+  _settings = settings;
+};
+
+let COUNTRY_NAMES_TO_CODE: string[] = [];
+let VIDEOS_PER_PAGE_OPTIONS: number[] = [];
+let PLAYLISTS_PER_PAGE_OPTIONS: number[] = [];
+let CREATOR_AVATAR_HEIGHT: string[] = [];
+let THUMBNAIL_HEIGHT: string[] = [];
+
+//Source Methods
+source.enable = function (conf, settings, saveStateStr) {
+  config = conf ?? {};
+
+  COUNTRY_NAMES_TO_CODE =
+    config?.settings?.find((s) => s.variable == 'preferredCountryOptionIndex')
+      ?.options ?? [];
+
+  VIDEOS_PER_PAGE_OPTIONS =
+    config?.settings
+      ?.find((s) => s.variable == 'videosPerPageOptionIndex')
+      ?.options?.map((s) => parseInt(s)) ?? [];
+
+  PLAYLISTS_PER_PAGE_OPTIONS =
+    config?.settings
+      ?.find((s) => s.variable == 'playlistsPerPageOptionIndex')
+      ?.options?.map((s) => parseInt(s)) ?? [];
+
+  CREATOR_AVATAR_HEIGHT =
+    config?.settings
+      ?.find((s) => s.variable == 'avatarSizeOptionIndex')
+      ?.options?.map((s) => `SQUARE_${s.replace('px', '')}`) ?? [];
+
+  THUMBNAIL_HEIGHT =
+    config?.settings
+      ?.find((s) => s.variable == 'thumbnailResolutionOptionIndex')
+      ?.options?.map((s) => `PORTRAIT_${s.replace('px', '')}`) ?? [];
+
+  const DEFAULT_SETTINGS = {
+    hideSensitiveContent: true,
+    avatarSizeOptionIndex: 8, // 720px
+    thumbnailResolutionOptionIndex: 7, // 1080px
+    preferredCountryOptionIndex: 0, // empty
+    videosPerPageOptionIndex: 3, // 20
+    playlistsPerPageOptionIndex: 0, // 5
+  };
+
+  _settings = { ...DEFAULT_SETTINGS, ...settings };
+
+  if (IS_TESTING) {
+    config.id = '9c87e8db-e75d-48f4-afe5-2d203d4b95c5';
+  }
+
+  let didSaveState = false;
+
+  try {
+    if (saveStateStr) {
+      const saveState = JSON.parse(saveStateStr);
+      if (saveState) {
+        state.anonymousUserAuthorizationToken =
+          saveState.anonymousUserAuthorizationToken;
+
+        state.anonymousUserAuthorizationTokenExpirationDate =
+          saveState.anonymousUserAuthorizationTokenExpirationDate;
+
+        state.messageServiceToken = saveState.messageServiceToken;
+
+        if (!isTokenValid()) {
+          log('Token expired. Fetching a new one.');
+        } else {
+          didSaveState = true;
+          log('Using save state');
+        }
+      }
+    }
+  } catch (ex) {
+    log('Failed to parse saveState:' + ex);
+    didSaveState = false;
+  }
+
+  if (!didSaveState) {
+    log('Getting a new tokens');
+
+    const clientCredentials = extractClientCredentials(http);
+
+    const {
+      anonymousUserAuthorizationToken,
+      anonymousUserAuthorizationTokenExpirationDate,
+      isValid,
+    } = getTokenFromClientCredentials(http, clientCredentials);
+
+    if (!isValid) {
+      console.error('Failed to get token');
+      throw new ScriptException('Failed to get authentication token');
+    }
+
+    state.anonymousUserAuthorizationToken =
+      anonymousUserAuthorizationToken ?? '';
+    state.anonymousUserAuthorizationTokenExpirationDate =
+      anonymousUserAuthorizationTokenExpirationDate ?? 0;
+
+    if (config.allowAllHttpHeaderAccess) {
+      // get token for message service api-2-0.spot.im
+      const authenticateIm = http.POST(
+        BASE_URL_COMMENTS_AUTH,
+        '',
+        {
+          'User-Agent': USER_AGENT,
+          Accept: '*/*',
+          'Accept-Language': 'en-US,en;q=0.5',
+          'x-spot-id': 'sp_vWPN1lBu',
+          'x-post-id': 'no$post',
+          'Content-Type': 'application/json',
+          Origin: BASE_URL,
+          Connection: 'keep-alive',
+          Referer: BASE_URL,
+          'Sec-Fetch-Dest': 'empty',
+          'Sec-Fetch-Mode': 'cors',
+          'Sec-Fetch-Site': 'cross-site',
+          Priority: 'u=6',
+          'Content-Length': '0',
+        },
+        false,
+      );
+
+      if (!authenticateIm.isOk) {
+        log('Failed to authenticate to comments service');
+      }
+
+      state.messageServiceToken = authenticateIm.headers['x-access-token'][0];
+    }
+  }
+};
+
+source.getHome = function () {
+  return getHomePager({}, 0);
+};
+
+source.searchSuggestions = function (query): string[] {
+  try {
+    const gqlResponse = executeGqlQuery(http, {
+      operationName: 'AUTOCOMPLETE_QUERY',
+      variables: {
+        query,
+      },
+      query: AUTOCOMPLETE_QUERY,
+    });
+
+    return (
+      (
+        gqlResponse?.data?.search?.suggestedVideos as SuggestionConnection
+      )?.edges?.map((edge) => edge?.node?.name ?? '') ?? []
+    );
+  } catch (error: any) {
+    log('Failed to get search suggestions:' + error?.message);
+    return [];
+  }
+};
+
+source.getSearchCapabilities = (): ResultCapabilities => SEARCH_CAPABILITIES;
+
+source.search = function (query: string, type: string, order: string, filters) {
+  return getSearchPagerAll({ q: query, page: 1, type, order, filters });
+};
+
+source.searchChannels = function (query) {
+  return getSearchChannelPager({ q: query, page: 1 });
+};
+
+//Channel
+source.isChannelUrl = function (url) {
+  return REGEX_VIDEO_CHANNEL_URL.test(url);
+};
+
+source.getChannel = function (url) {
+  const channel_name = getChannelNameFromUrl(url);
+
+  const channelDetails = executeGqlQuery(http, {
+    operationName: 'CHANNEL_QUERY_DESKTOP',
+    variables: {
+      channel_name,
+      avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+    },
+    query: CHANNEL_QUERY_DESKTOP,
+  });
+
+  return SourceChannelToGrayjayChannel(
+    config.id,
+    channelDetails.data.channel as Channel,
+  );
+};
+
+source.getChannelContents = function (url, type, order, filters) {
+  const page = 1;
+  return getChannelContentsPager(url, page, type, order, filters);
+};
+
+source.getChannelPlaylists = (url): SearchPlaylistPager => {
+  try {
+    return getChannelPlaylists(url, 1);
+  } catch (error) {
+    log('Failed to get channel playlists:' + error);
+    return new ChannelPlaylistPager([]);
+  }
+};
+
+source.getChannelCapabilities = (): ResultCapabilities => {
+  return {
+    types: [Type.Feed.Mixed],
+    sorts: [Type.Order.Chronological, 'Popular'],
+    filters: [],
+  };
+};
+
+//Video
+source.isContentDetailsUrl = function (url) {
+  return [REGEX_VIDEO_URL, REGEX_VIDEO_URL_1, REGEX_VIDEO_URL_EMBED].some((r) =>
+    r.test(url),
+  );
+};
+
+source.getContentDetails = function (url) {
+  return getSavedVideo(url, false);
+};
+
+source.saveState = () => {
+  return JSON.stringify(state);
+};
+
+source.getSubComments = (comment) => {
+  const params = {
+    count: 5,
+    offset: 0,
+    parent_id: comment.context.id,
+    sort_by: 'best',
+    child_count: comment.replyCount,
+  };
+  return getCommentPager(comment.contextUrl, params, 0);
+};
+
+source.getComments = (url) => {
+  if (!config.allowAllHttpHeaderAccess) {
+    return new PlatformCommentPager([], false, url, {}, 0);
+  }
+
+  const params = {
+    sort_by: 'best',
+    offset: 0,
+    count: 10,
+    message_id: null,
+    depth: 2,
+    child_count: 2,
+  };
+  return getCommentPager(url, params, 0);
+};
+
+function getCommentPager(url, params, page) {
+  try {
+    const xid = url.split('/').pop();
+
+    const commentsHeaders = {
+      'User-Agent': USER_AGENT,
+      Accept: 'application/json',
+      'Accept-Language': 'en-US,en;q=0.5',
+      'x-access-token': state.messageServiceToken,
+      'Content-Type': 'application/json',
+      'x-spot-id': 'sp_vWPN1lBu',
+      'x-post-id': xid,
+      Origin: BASE_URL,
+      Connection: 'keep-alive',
+      Referer: BASE_URL,
+      'Sec-Fetch-Dest': 'empty',
+      'Sec-Fetch-Mode': 'cors',
+      'Sec-Fetch-Site': 'cross-site',
+      Priority: 'u=6',
+      TE: 'trailers',
+    };
+
+    const commentRequest = http.POST(
+      BASE_URL_COMMENTS,
+      JSON.stringify(params),
+      commentsHeaders,
+      false,
+    );
+
+    if (!commentRequest.isOk) {
+      throw new UnavailableException(
+        'Failed to authenticate to comments service',
+      );
+    }
+
+    const comments = JSON.parse(commentRequest.body);
+
+    const users = comments.conversation.users;
+
+    const results = comments.conversation.comments.map((v) => {
+      const user = users[v.user_id];
+
+      return new Comment({
+        contextUrl: url,
+        author: new PlatformAuthorLink(
+          new PlatformID(PLATFORM, user.id ?? '', config.id),
+          user.display_name ?? '',
+          '',
+          `${BASE_URL_COMMENTS_THUMBNAILS}/${user.image_id}`,
+        ),
+        message: v.content[0].text,
+        rating: new RatingLikes(v.stars),
+        date: v.written_at,
+        replyCount: v.total_replies_count ?? 0,
+        context: { id: v.id },
+      });
+    });
+
+    return new PlatformCommentPager(
+      results,
+      comments.conversation.has_next,
+      url,
+      params,
+      ++page,
+    );
+  } catch (error) {
+    bridge.log('Failed to get comments:' + error);
+    return new PlatformCommentPager([], false, url, params, 0);
+  }
+}
+
+class PlatformCommentPager extends CommentPager {
+  constructor(results, hasMore, path, params, page) {
+    super(results, hasMore, { path, params, page });
+  }
+
+  nextPage() {
+    return getCommentPager(
+      this.context.path,
+      this.context.params,
+      (this.context.page ?? 0) + 1,
+    );
+  }
+}
+
+//Playlist
+source.isPlaylistUrl = (url): boolean => {
+  return (
+    REGEX_VIDEO_PLAYLIST_URL.test(url) ||
+    url === LIKE_PLAYLIST_ID ||
+    url === FAVORITES_PLAYLIST_ID ||
+    url === RECENTLY_WATCHED_PLAYLIST_ID
+  );
+};
+
+source.searchPlaylists = (query, type, order, filters) => {
+  return searchPlaylists({ q: query, type, order, filters });
+};
+
+source.getPlaylist = (url: string): PlatformPlaylistDetails => {
+  const usePlatformAuth = authenticatedPlaylistCollection.includes(url);
+
+  const thumbnailResolutionIndex = _settings.thumbnailResolutionOptionIndex;
+
+  if (url === LIKE_PLAYLIST_ID) {
+    return getLikePlaylist(
+      config.id,
+      http,
+      usePlatformAuth,
+      thumbnailResolutionIndex,
+    );
+  }
+
+  if (url === FAVORITES_PLAYLIST_ID) {
+    return getFavoritesPlaylist(
+      config.id,
+      http,
+      usePlatformAuth,
+      thumbnailResolutionIndex,
+    );
+  }
+
+  if (url === RECENTLY_WATCHED_PLAYLIST_ID) {
+    return getRecentlyWatchedPlaylist(
+      config.id,
+      http,
+      usePlatformAuth,
+      thumbnailResolutionIndex,
+    );
+  }
+
+  const xid = url.split('/').pop();
+
+  const variables = {
+    xid,
+    avatar_size: CREATOR_AVATAR_HEIGHT[_settings.avatarSizeOptionIndex],
+    thumbnail_resolution: THUMBNAIL_HEIGHT[thumbnailResolutionIndex],
+  };
+
+  const gqlResponse = executeGqlQuery(http, {
+    operationName: 'PLAYLIST_VIDEO_QUERY',
+    variables,
+    query: PLAYLIST_DETAILS_QUERY,
+    usePlatformAuth,
+  });
+
+  const videos: PlatformVideo[] =
+    gqlResponse?.data?.collection?.videos?.edges.map((edge) => {
+      return SourceVideoToGrayjayVideo(config.id, edge.node as Video);
+    });
+
+  return SourceCollectionToGrayjayPlaylistDetails(
+    config.id,
+    gqlResponse?.data?.collection as Collection,
+    videos,
+  );
+};
+
+source.getUserSubscriptions = (): string[] => {
+  if (!bridge.isLoggedIn()) {
+    log('Failed to retrieve subscriptions page because not logged in.');
+    throw new ScriptException('Not logged in');
+  }
+
+  const headers = {
+    'Content-Type': 'application/json',
+    'User-Agent': USER_AGENT,
+    // Accept: '*/*, */*',
+    'Accept-Language': 'en-GB',
+    Referer: `${BASE_URL}/library/subscriptions`,
+    'X-DM-Preferred-Country': getPreferredCountry(
+      _settings?.preferredCountryOptionIndex,
+    ),
+    Origin: BASE_URL,
+    DNT: '1',
+    Connection: 'keep-alive',
+    'Sec-Fetch-Dest': 'empty',
+    'Sec-Fetch-Mode': 'cors',
+    'Sec-Fetch-Site': 'same-site',
+    Priority: 'u=4',
+    Pragma: 'no-cache',
+    'Cache-Control': 'no-cache',
+  };
+
+  const usePlatformAuth = true;
+
+  const fetchSubscriptions = (page, first): string[] => {
+    const gqlResponse = executeGqlQuery(http, {
+      operationName: 'SUBSCRIPTIONS_QUERY',
+      variables: {
+        first: first,
+        page: page,
+      },
+      headers,
+      query: GET_USER_SUBSCRIPTIONS,
+      usePlatformAuth,
+    });
+
+    return (
+      (gqlResponse?.data?.me?.channel as Channel)?.followings?.edges?.map(
+        (edge) => edge?.node?.creator?.name ?? '',
+      ) ?? []
+    );
+  };
+
+  const first = 100; // Number of records to fetch per page
+  let page = 1;
+  const subscriptions: string[] = [];
+
+  // There is a totalCount ($.data.me.channel.followings.totalCount) property but it's not reliable.
+  // For example, it may return 0 even if there are subscriptions, or it may return a number that is not the actual number of subscriptions.
+  // For now, it's better to fetch until no more results are returned
+
+  let items: string[] = [];
+
+  do {
+    const response = fetchSubscriptions(page, first);
+
+    items = response.map((creatorName) => `${BASE_URL}/${creatorName}`);
+
+    subscriptions.push(...items);
+    page++;
+  } while (items.length);
+
+  return subscriptions;
+};
+
+source.getUserPlaylists = (): string[] => {
+  if (!bridge.isLoggedIn()) {
+    log('Failed to retrieve subscriptions page because not logged in.');
+    throw new ScriptException('Not logged in');
+  }
+
+  const headers = {
+    'Content-Type': 'application/json',
+    'User-Agent': USER_AGENT,
+    'Accept-Language': 'en-GB',
+    Referer: 'https://www.dailymotion.com/',
+    'Sec-GPC': '1',
+    'X-DM-Preferred-Country': getPreferredCountry(
+      _settings?.preferredCountryOptionIndex,
+    ),
+    Origin: BASE_URL,
+    DNT: '1',
+    Connection: 'keep-alive',
+    'Sec-Fetch-Dest': 'empty',
+    'Sec-Fetch-Mode': 'cors',
+    'Sec-Fetch-Site': 'same-site',
+    Priority: 'u=1',
+    Pragma: 'no-cache',
+    'Cache-Control': 'no-cache',
+  };
+
+  const gqlResponse = executeGqlQuery(http, {
+    operationName: 'SUBSCRIPTIONS_QUERY',
+    headers,
+    query: SUBSCRIPTIONS_QUERY,
+    usePlatformAuth: true,
+  });
+
+  const userName = (gqlResponse?.data?.me?.channel as Channel)?.name;
+
+  const playlists = getPlaylistsByUsername(userName, headers, true);
+
+  [
+    LIKE_PLAYLIST_ID,
+    FAVORITES_PLAYLIST_ID,
+    RECENTLY_WATCHED_PLAYLIST_ID,
+  ].forEach((playlistId) => {
+    if (!authenticatedPlaylistCollection.includes(playlistId)) {
+      authenticatedPlaylistCollection.push(playlistId);
+    }
+
+    if (!playlists.includes(playlistId)) {
+      playlists.push(playlistId);
+    }
+  });
+
+  return playlists;
+};
+
+source.getChannelTemplateByClaimMap = () => {
+  return {
+    //Dailymotion claim type
+    27: {
+      0: BASE_URL + '/{{CLAIMVALUE}}',
+    },
+  };
+};
+
+function getPlaylistsByUsername(
+  userName,
+  headers,
+  usePlatformAuth = false,
+): string[] {
+  const collections = executeGqlQuery(http, {
+    operationName: 'CHANNEL_PLAYLISTS_QUERY',
+    variables: {
+      channel_name: userName,
+      sort: 'recent',
+      page: 1,
+      first: 99,
+      avatar_size: CREATOR_AVATAR_HEIGHT[_settings.avatarSizeOptionIndex],
+      thumbnail_resolution:
+        THUMBNAIL_HEIGHT[_settings.thumbnailResolutionOptionIndex],
+    },
+    headers,
+    query: GET_CHANNEL_PLAYLISTS_XID,
+    usePlatformAuth,
+  });
+
+  const playlists: string[] = collections.data.channel.collections.edges.map(
+    (edge) => {
+      const playlistUrl = `${BASE_URL_PLAYLIST}/${edge.node.xid}`;
+      if (!authenticatedPlaylistCollection.includes(playlistUrl)) {
+        authenticatedPlaylistCollection.push(playlistUrl);
+      }
+      return playlistUrl;
+    },
+  );
+
+  return playlists;
+}
+
+function searchPlaylists(contextQuery) {
+  const context = getQuery(contextQuery);
+
+  const variables = {
+    query: context.q,
+    sortByVideos: context.sort,
+    durationMaxVideos: context.filters?.durationMaxVideos,
+    durationMinVideos: context.filters?.durationMinVideos,
+    createdAfterVideos: context.filters?.createdAfterVideos, //Represents a DateTime value as specified by iso8601
+    shouldIncludePlaylists: true,
+    shouldIncludeVideos: false,
+    shouldIncludeLives: false,
+    page: context.page,
+    limit: VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
+    thumbnail_resolution:
+      THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
+    avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+  };
+
+  const gqlResponse = executeGqlQuery(http, {
+    operationName: 'SEARCH_QUERY',
+    variables: variables,
+    query: SEARCH_QUERY,
+    headers: undefined,
+  });
+
+  const playlistConnection = gqlResponse?.data?.search
+    ?.playlists as CollectionConnection;
+
+  const searchResults = playlistConnection?.edges?.map((edge) => {
+    return SourceCollectionToGrayjayPlaylist(config.id, edge?.node);
+  });
+
+  const hasMore = playlistConnection?.pageInfo?.hasNextPage;
+
+  if (!searchResults || searchResults.length === 0) {
+    return new PlaylistPager([]);
+  }
+
+  const params = {
+    query: context.q,
+    sort: context.sort,
+    filters: context.filters,
+  };
+
+  return new SearchPlaylistPager(
+    searchResults,
+    hasMore,
+    params,
+    context.page,
+    searchPlaylists,
+  );
+}
+
+//Internals
+
+function getHomePager(params, page) {
+  const count = VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex];
+
+  if (!params) {
+    params = {};
+  }
+
+  params = { ...params, count };
+
+  const headersToAdd = {
+    'User-Agent': USER_AGENT,
+    Referer: BASE_URL,
+    'Content-Type': 'application/json',
+    'X-DM-AppInfo-Id': X_DM_AppInfo_Id,
+    'X-DM-AppInfo-Type': X_DM_AppInfo_Type,
+    'X-DM-AppInfo-Version': X_DM_AppInfo_Version,
+    'X-DM-Neon-SSR': X_DM_Neon_SSR,
+    'X-DM-Preferred-Country': getPreferredCountry(
+      _settings?.preferredCountryOptionIndex,
+    ),
+    Origin: BASE_URL,
+    DNT: '1',
+    'Sec-Fetch-Site': 'same-site',
+    Priority: 'u=4',
+    Pragma: 'no-cache',
+    'Cache-Control': 'no-cache',
+  };
+
+  let obj;
+
+  try {
+    obj = executeGqlQuery(http, {
+      operationName: 'SEACH_DISCOVERY_QUERY',
+      variables: {
+        avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+        thumbnail_resolution:
+          THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
+      },
+      query: SEACH_DISCOVERY_QUERY,
+      headers: headersToAdd,
+    });
+  } catch (error) {
+    return new VideoPager([], false, { params });
+  }
+
+  const results =
+    obj?.data?.home?.neon?.sections?.edges?.[0]?.node?.components?.edges
+      ?.filter((edge) => edge?.node?.id)
+      ?.map((edge) => {
+        return SourceVideoToGrayjayVideo(config.id, edge.node as Video);
+      });
+
+  const hasMore =
+    obj?.data?.home?.neon?.sections?.edges?.[0]?.node?.components?.pageInfo
+      ?.hasNextPage ?? false;
+
+  return new SearchPagerAll(results, hasMore, params, page, getHomePager);
+}
+
+function getChannelContentsPager(url, page, type, order, filters) {
+  const channel_name = getChannelNameFromUrl(url);
+
+  const shouldLoadVideos =
+    type === Type.Feed.Mixed || type === Type.Feed.Videos;
+
+  const shouldLoadLives =
+    type === Type.Feed.Mixed ||
+    type === Type.Feed.Streams ||
+    type === Type.Feed.Live;
+
+  if (IS_TESTING) {
+    log(
+      `Getting channel contents for ${url}, page: ${page}, type: ${type}, order: ${order}, shouldLoadVideos: ${shouldLoadVideos}, shouldLoadLives: ${shouldLoadLives}, filters: ${JSON.stringify(filters)}`,
+    );
+  }
+
+  /** 
+		Recent = Sort liked medias by most recent.
+		Visited - Sort liked medias by most viewed
+	*/
+  let sort: string;
+
+  if (order == Type.Order.Chronological) {
+    sort = LikedMediaSort.Recent;
+  } else if (order == 'Popular') {
+    sort = LikedMediaSort.Visited;
+  } else {
+    sort = LikedMediaSort.Recent;
+  }
+
+  const gqlResponse = executeGqlQuery(http, {
+    operationName: 'CHANNEL_VIDEOS_QUERY',
+    variables: {
+      channel_name,
+      sort,
+      page: page ?? 1,
+      allowExplicit: !_settings.hideSensitiveContent,
+      first: VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
+      avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+      thumbnail_resolution:
+        THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
+      shouldLoadLives,
+      shouldLoadVideos,
+    },
+    query: CHANNEL_VIDEOS_QUERY,
+  });
+
+  const channel = gqlResponse?.data?.channel as Channel;
+
+  const all: (Live | Video)[] = [
+    ...(channel?.lives?.edges
+      ?.filter((e) => e?.node?.isOnAir)
+      ?.map((e) => e?.node as Live) ?? []),
+    ...(channel?.videos?.edges?.map((e) => e?.node as Video) ?? []),
+  ];
+
+  const videos = all.map((node) => SourceVideoToGrayjayVideo(config.id, node));
+
+  const videosHasNext = channel?.videos?.pageInfo?.hasNextPage;
+  const livesHasNext = channel?.lives?.pageInfo?.hasNextPage;
+  const hasNext = videosHasNext || livesHasNext || false;
+
+  const params = {
+    url,
+    type,
+    order,
+    page,
+    filters,
+  };
+
+  return new ChannelVideoPager(
+    videos,
+    hasNext,
+    params,
+    getChannelContentsPager,
+  );
+}
+
+function getSearchPagerAll(contextQuery): VideoPager {
+  const context = getQuery(contextQuery);
+
+  const variables = {
+    query: context.q,
+    sortByVideos: context.sort,
+    durationMaxVideos: context.filters?.durationMaxVideos,
+    durationMinVideos: context.filters?.durationMinVideos,
+    createdAfterVideos: context.filters?.createdAfterVideos, //Represents a DateTime value as specified by iso8601
+    shouldIncludePlaylists: false,
+    shouldIncludeVideos: true,
+    shouldIncludeLives: true,
+    page: context.page ?? 1,
+    limit: VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
+    avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+    thumbnail_resolution:
+      THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
+  };
+
+  const gqlResponse = executeGqlQuery(http, {
+    operationName: 'SEARCH_QUERY',
+    variables: variables,
+    query: SEARCH_QUERY,
+    headers: undefined,
+  });
+
+  const videoConnection = gqlResponse?.data?.search?.videos as VideoConnection;
+  const liveConnection = gqlResponse?.data?.search?.lives as LiveConnection;
+
+  const all: (VideoEdge | LiveEdge | null)[] = [
+    ...(videoConnection?.edges ?? []),
+    ...(liveConnection?.edges ?? []),
+  ];
+
+  const results: PlatformVideo[] = all.map((edge) =>
+    SourceVideoToGrayjayVideo(config.id, edge?.node),
+  );
+
+  const params = {
+    query: context.q,
+    sort: context.sort,
+    filters: context.filters,
+  };
+
+  return new SearchPagerAll(
+    results,
+    videoConnection?.pageInfo?.hasNextPage,
+    params,
+    context.page,
+    getSearchPagerAll,
+  );
+}
+
+function getSavedVideo(url, usePlatformAuth = false) {
+  const id = url.split('/').pop();
+
+  const player_metadata_url = `${BASE_URL_METADATA}/${id}?embedder=https%3A%2F%2Fwww.dailymotion.com%2Fvideo%2Fx8yb2e8&geo=1&player-id=xjnde&locale=en-GB&dmV1st=ce2035cd-bdca-4d7b-baa4-127a17490ca5&dmTs=747022&is_native_app=0&app=com.dailymotion.neon&client_type=webapp&section_type=player&component_style=_`;
+
+  const headers1 = {
+    'User-Agent': USER_AGENT,
+    Accept: '*/*',
+    Referer: 'https://geo.dailymotion.com/',
+    Origin: 'https://geo.dailymotion.com',
+    DNT: '1',
+    Connection: 'keep-alive',
+    'Sec-Fetch-Dest': 'empty',
+    'Sec-Fetch-Mode': 'cors',
+    'Sec-Fetch-Site': 'same-site',
+    Pragma: 'no-cache',
+    'Cache-Control': 'no-cache',
+  };
+
+  if (_settings.hideSensitiveContent) {
+    headers1['Cookie'] = 'ff=on';
+  } else {
+    headers1['Cookie'] = 'ff=off';
+  }
+
+  const videoDetailsRequestBody = JSON.stringify({
+    operationName: 'WATCHING_VIDEO',
+    variables: {
+      xid: id,
+      avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+      thumbnail_resolution:
+        THUMBNAIL_HEIGHT[_settings?.thumbnailResolutionOptionIndex],
+    },
+    query: WATCHING_VIDEO,
+  });
+
+  const videoDetailsRequestHeaders: IDictionary<string> = {
+    'Content-Type': 'application/json',
+    'User-Agent': USER_AGENT,
+    Accept: '*/*, */*',
+    Referer: `${BASE_URL_VIDEO}/${id}`,
+    'X-DM-AppInfo-Id': X_DM_AppInfo_Id,
+    'X-DM-AppInfo-Type': X_DM_AppInfo_Type,
+    'X-DM-AppInfo-Version': X_DM_AppInfo_Version,
+    'X-DM-Neon-SSR': X_DM_Neon_SSR,
+    'X-DM-Preferred-Country': getPreferredCountry(
+      _settings?.preferredCountryOptionIndex,
+    ),
+    Origin: BASE_URL,
+    DNT: '1',
+    Connection: 'keep-alive',
+    'Sec-Fetch-Dest': 'empty',
+    'Sec-Fetch-Mode': 'cors',
+    'Sec-Fetch-Site': 'same-site',
+    Priority: 'u=4',
+    Pragma: 'no-cache',
+    'Cache-Control': 'no-cache',
+  };
+
+  if (!usePlatformAuth) {
+    videoDetailsRequestHeaders.Authorization =
+      state.anonymousUserAuthorizationToken;
+  }
+
+  const responses = http
+    .batch()
+    .GET(player_metadata_url, headers1, usePlatformAuth)
+    .POST(
+      BASE_URL_API,
+      videoDetailsRequestBody,
+      videoDetailsRequestHeaders,
+      usePlatformAuth,
+    )
+    .execute();
+
+  const player_metadataResponse = responses[0];
+  const video_details_response = responses[1];
+
+  if (!player_metadataResponse.isOk) {
+    throw new UnavailableException('Unable to get player metadata');
+  }
+
+  const player_metadata = JSON.parse(player_metadataResponse.body);
+
+  if (player_metadata.error) {
+    if (
+      player_metadata.error.code &&
+      ERROR_TYPES[player_metadata.error.code] !== undefined
+    ) {
+      throw new UnavailableException(ERROR_TYPES[player_metadata.error.code]);
+    }
+
+    throw new UnavailableException('This content is not available');
+  }
+
+  if (video_details_response.code != 200) {
+    throw new UnavailableException('Failed to get video details');
+  }
+
+  const video_details = JSON.parse(video_details_response.body);
+
+  const video = video_details?.data?.video as Video;
+
+  const platformVideoDetails: PlatformVideoDetailsDef =
+    SourceVideoToPlatformVideoDetailsDef(config.id, video, player_metadata);
+
+  return new PlatformVideoDetails(platformVideoDetails);
+}
+
+function getSearchChannelPager(context) {
+  const searchResponse = executeGqlQuery(http, {
+    operationName: 'SEARCH_QUERY',
+    variables: {
+      query: context.q,
+      page: context.page ?? 1,
+      limit: VIDEOS_PER_PAGE_OPTIONS[_settings.videosPerPageOptionIndex],
+      avatar_size: CREATOR_AVATAR_HEIGHT[_settings?.avatarSizeOptionIndex],
+    },
+    query: SEARCH_CHANNEL,
+  });
+
+  const results = searchResponse?.data?.search?.channels?.edges.map((edge) => {
+    const channel = edge.node as Channel;
+
+    return SourceChannelToGrayjayChannel(config.id, channel);
+  });
+
+  const params = {
+    query: context.q,
+  };
+
+  return new SearchChannelPager(
+    results,
+    searchResponse?.data?.search?.channels?.pageInfo?.hasNextPage,
+    params,
+    context.page,
+    getSearchChannelPager,
+  );
+}
+
+function getChannelPlaylists(
+  url: string,
+  page: number = 1,
+): SearchPlaylistPager {
+  const headers = {
+    'Content-Type': 'application/json',
+    'User-Agent': USER_AGENT,
+    'Accept-Language': 'en-GB',
+    Referer: `${BASE_URL}/library/subscriptions`,
+    'X-DM-AppInfo-Id': X_DM_AppInfo_Id,
+    'X-DM-AppInfo-Type': X_DM_AppInfo_Type,
+    'X-DM-AppInfo-Version': X_DM_AppInfo_Version,
+    'X-DM-Neon-SSR': '0',
+    'X-DM-Preferred-Country': getPreferredCountry(
+      _settings?.preferredCountryOptionIndex,
+    ),
+    Origin: BASE_URL,
+    DNT: '1',
+    Connection: 'keep-alive',
+    'Sec-Fetch-Dest': 'empty',
+    'Sec-Fetch-Mode': 'cors',
+    'Sec-Fetch-Site': 'same-site',
+    Priority: 'u=4',
+    Pragma: 'no-cache',
+    'Cache-Control': 'no-cache',
+  };
+
+  const usePlatformAuth = false;
+  const channel_name = getChannelNameFromUrl(url);
+
+  const gqlResponse = executeGqlQuery(http, {
+    operationName: 'CHANNEL_PLAYLISTS_QUERY',
+    variables: {
+      channel_name,
+      sort: 'recent',
+      page,
+      first: PLAYLISTS_PER_PAGE_OPTIONS[_settings.playlistsPerPageOptionIndex],
+      avatar_size: CREATOR_AVATAR_HEIGHT[_settings.avatarSizeOptionIndex],
+      thumbnail_resolution:
+        THUMBNAIL_HEIGHT[_settings.thumbnailResolutionOptionIndex],
+    },
+    headers,
+    query: CHANNEL_PLAYLISTS_QUERY,
+    usePlatformAuth,
+  });
+
+  const channel = gqlResponse.data.channel as Channel;
+
+  const content: PlatformPlaylist[] = (channel?.collections?.edges ?? [])
+    .filter(
+      (e) => e?.node?.metrics?.engagement?.videos?.edges?.[0]?.node?.total,
+    ) //exclude empty playlists. could be empty doe to geographic restrictions
+    .map((edge) => {
+      return SourceCollectionToGrayjayPlaylist(config.id, edge?.node);
+    });
+
+  if (content?.length === 0) {
+    return new ChannelPlaylistPager([]);
+  }
+
+  const params = {
+    url,
+  };
+
+  const hasMore = channel?.collections?.pageInfo?.hasNextPage ?? false;
+
+  return new ChannelPlaylistPager(
+    content,
+    hasMore,
+    params,
+    page,
+    getChannelPlaylists,
+  );
+}
+
+function isTokenValid() {
+  const currentTime = Date.now();
+  return state.anonymousUserAuthorizationTokenExpirationDate > currentTime;
+}
+
+function executeGqlQuery(httpClient, requestOptions) {
+  const headersToAdd = requestOptions.headers || {
+    'User-Agent': USER_AGENT,
+    Accept: '*/*',
+    // "Accept-Language": Accept_Language,
+    Referer: BASE_URL,
+    Origin: BASE_URL,
+    DNT: '1',
+    Connection: 'keep-alive',
+    'Sec-Fetch-Dest': 'empty',
+    'Sec-Fetch-Mode': 'cors',
+    'Sec-Fetch-Site': 'same-site',
+    Pragma: 'no-cache',
+    'Cache-Control': 'no-cache',
+  };
+
+  const gql = JSON.stringify({
+    operationName: requestOptions.operationName,
+    variables: requestOptions.variables,
+    query: requestOptions.query,
+  });
+
+  const usePlatformAuth =
+    requestOptions.usePlatformAuth == undefined
+      ? false
+      : requestOptions.usePlatformAuth;
+
+  const throwOnError =
+    requestOptions.throwOnError == undefined
+      ? true
+      : requestOptions.throwOnError;
+
+  if (!usePlatformAuth) {
+    headersToAdd.Authorization = state.anonymousUserAuthorizationToken;
+  }
+
+  const res = httpClient.POST(BASE_URL_API, gql, headersToAdd, usePlatformAuth);
+
+  if (!res.isOk) {
+    console.error('Failed to execute request', res);
+    if (throwOnError) {
+      throw new ScriptException('Failed to execute request', res);
+    }
+  }
+
+  const body = JSON.parse(res.body);
+
+  // some errors may be returned in the body with a status code 200
+  if (body.errors) {
+    const message = body.errors.map((e) => e.message).join(', ');
+
+    if (throwOnError) {
+      throw new UnavailableException(message);
+    }
+  }
+
+  return body;
+}
+
+function getPages<TI, TO>(
+  httpClient: IHttp,
+  query: string,
+  operationName: string,
+  variables: any,
+  usePlatformAuth: boolean,
+  setRoot: (gqlResponse: any) => TI,
+  hasNextCallback: (page: TI) => boolean,
+  getNextPage: (page: TI, currentPage) => number,
+  map: (page: any) => TO[],
+): TO[] {
+  let all: TO[] = [];
+
+  if (!hasNextCallback) {
+    hasNextCallback = () => false;
+  }
+
+  let hasNext = true;
+  let nextPage = 1;
+
+  do {
+    variables = { ...variables, page: nextPage };
+
+    const gqlResponse = executeGqlQuery(httpClient, {
+      operationName,
+      variables,
+      query,
+      usePlatformAuth,
+    });
+
+    const root = setRoot(gqlResponse);
+
+    nextPage = getNextPage(root, nextPage);
+
+    const items = map(root);
+
+    hasNext = hasNextCallback(root);
+
+    all = all.concat(items);
+  } while (hasNext);
+
+  return all;
+}
+
+function getLikePlaylist(
+  pluginId: string,
+  httpClient: IHttp,
+  usePlatformAuth: boolean = false,
+  thumbnailResolutionIndex: number = 0,
+): PlatformPlaylistDetails {
+  return getPlatformSystemPlaylist({
+    pluginId,
+    httpClient,
+    query: USER_LIKED_VIDEOS_QUERY,
+    operationName: 'USER_LIKED_VIDEOS_QUERY',
+    rootObject: 'likedMedias',
+    playlistName: 'Liked Videos',
+    usePlatformAuth,
+    thumbnailResolutionIndex,
+  });
+}
+
+function getFavoritesPlaylist(
+  pluginId: string,
+  httpClient: IHttp,
+  usePlatformAuth: boolean = false,
+  thumbnailResolutionIndex: number = 0,
+): PlatformPlaylistDetails {
+  return getPlatformSystemPlaylist({
+    pluginId,
+    httpClient,
+    query: USER_WATCH_LATER_VIDEOS_QUERY,
+    operationName: 'USER_WATCH_LATER_VIDEOS_QUERY',
+    rootObject: 'watchLaterMedias',
+    playlistName: 'Favorites',
+    usePlatformAuth,
+    thumbnailResolutionIndex,
+  });
+}
+
+function getRecentlyWatchedPlaylist(
+  pluginId: string,
+  httpClient: IHttp,
+  usePlatformAuth: boolean = false,
+  thumbnailResolutionIndex: number = 0,
+): PlatformPlaylistDetails {
+  return getPlatformSystemPlaylist({
+    pluginId,
+    httpClient,
+    query: USER_WATCHED_VIDEOS_QUERY,
+    operationName: 'USER_WATCHED_VIDEOS_QUERY',
+    rootObject: 'watchedVideos',
+    playlistName: 'Recently Watched',
+    usePlatformAuth,
+    thumbnailResolutionIndex,
+  });
+}
+
+function getPlatformSystemPlaylist(
+  opts: IPlatformSystemPlaylist,
+): PlatformPlaylistDetails {
+  const videos: PlatformVideo[] = getPages<Maybe<User>, PlatformVideo>(
+    opts.httpClient,
+    opts.query,
+    opts.operationName,
+    {
+      page: 1,
+      thumbnail_resolution: THUMBNAIL_HEIGHT[opts.thumbnailResolutionIndex],
+    },
+    opts.usePlatformAuth,
+    (gqlResponse) => gqlResponse?.data?.me, //set root
+    (me) => (me?.[opts.rootObject]?.edges?.length ?? 0) > 0, //hasNextCallback
+    (me, currentPage) => ++currentPage, //getNextPage
+    (me) =>
+      me?.[opts.rootObject]?.edges.map((edge) => {
+        return SourceVideoToGrayjayVideo(opts.pluginId, edge.node as Video);
+      }),
+  );
+
+  const collection = {
+    id: generateUUIDv4(),
+    name: opts.playlistName,
+    creator: {},
+  };
+
+  return SourceCollectionToGrayjayPlaylistDetails(
+    opts.pluginId,
+    collection as Collection,
+    videos,
+  );
+}
+
+function getPreferredCountry(preferredCountryIndex) {
+  const country = COUNTRY_NAMES_TO_CODE[preferredCountryIndex];
+  const parts = country.split('-');
+  const code = parts[0] ?? '';
+  return (code || '').toLowerCase();
+}
+
+log('LOADED');
diff --git a/src/Mappers.ts b/src/Mappers.ts
index ffadacbc04e1fe1dfdcf73005780c8453520cd16..7759fac148643e033856ae6a089243f9761f00f7 100644
--- a/src/Mappers.ts
+++ b/src/Mappers.ts
@@ -1,228 +1,334 @@
-import { Channel, Collection, Live, Maybe, Video } from "../types/CodeGenDailymotion";
-import { BASE_URL, BASE_URL_PLAYLIST, BASE_URL_VIDEO, NEGATIVE_RATINGS_LABELS, PLATFORM, PLATFORM_CLAIMTYPE, POSITIVE_RATINGS_LABELS } from "./constants";
-
-export const SourceChannelToGrayjayChannel = (pluginId: string, sourceChannel: Channel): PlatformChannel => {
-    const externalLinks = sourceChannel?.externalLinks ?? {};
-
-    const links = Object.keys(externalLinks).reduce((acc, key) => {
-        if (externalLinks[key]) {
-            acc[key.replace('URL', '')] = externalLinks[key];
-        }
-        return acc;
-    }, {} as Record<string, string>);
-
-    return new PlatformChannel({
-        id: new PlatformID(PLATFORM, sourceChannel?.id ?? "", pluginId, PLATFORM_CLAIMTYPE),
-        name: sourceChannel?.displayName ?? "",
-        thumbnail: sourceChannel?.avatar?.url ?? "",
-        banner: sourceChannel.banner?.url ?? "",
-        subscribers: sourceChannel?.metrics?.engagement?.followers?.edges[0]?.node?.total ?? 0,
-        description: sourceChannel?.description ?? "",
-        url: `${BASE_URL}/${sourceChannel.name}`,
-        links
-    });
-}
-
-export const SourceAuthorToGrayjayPlatformAuthorLink = (pluginId: string, creator?: Maybe<Channel>): PlatformAuthorLink => {
-    return new PlatformAuthorLink(
-        new PlatformID(PLATFORM, creator?.id ?? "", pluginId, PLATFORM_CLAIMTYPE),
-        creator?.displayName ?? "",
-        creator?.name ? `${BASE_URL}/${creator?.name}` : "",
-        creator?.avatar?.url ?? "",
-        creator?.followers?.totalCount ?? creator?.stats?.followers?.total ?? creator?.metrics?.engagement?.followers?.edges[0]?.node?.total ?? 0
-    );
-}
-
-export const SourceVideoToGrayjayVideo = (pluginId: string, sourceVideo?: Video | Live): PlatformVideo => {
-
-
-    const isLive = getIsLive(sourceVideo);
-    const viewCount = getViewCount(sourceVideo);
-
-    const video: PlatformVideoDef = {
-        id: new PlatformID(PLATFORM, sourceVideo?.id ?? "", pluginId, PLATFORM_CLAIMTYPE),
-        description: sourceVideo?.description ?? '',
-        name: sourceVideo?.title ?? "",
-        thumbnails: new Thumbnails([
-            new Thumbnail(sourceVideo?.thumbnail?.url ?? "", 0)
-        ]),
-        author: SourceAuthorToGrayjayPlatformAuthorLink(pluginId, sourceVideo?.creator),
-        uploadDate: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
-        datetime: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
-        url: `${BASE_URL_VIDEO}/${sourceVideo?.xid}`,
-        duration: (sourceVideo as Video)?.duration ?? 0,
-        viewCount,
-        isLive
-    };
-
-    return new PlatformVideo(video);
-}
-
-export const SourceCollectionToGrayjayPlaylistDetails = (pluginId: string, sourceCollection: Collection, videos: PlatformVideo[] = []): PlatformPlaylistDetails => {  
-    return new PlatformPlaylistDetails({
-        url: sourceCollection?.xid ? `${BASE_URL_PLAYLIST}/${sourceCollection?.xid}` : "",
-        id: new PlatformID(PLATFORM, sourceCollection?.xid ?? "", pluginId, PLATFORM_CLAIMTYPE),
-        author: sourceCollection?.creator ? SourceAuthorToGrayjayPlatformAuthorLink(pluginId, sourceCollection?.creator) : {},
-        name: sourceCollection.name,
-        thumbnail: sourceCollection?.thumbnail?.url,
-        videoCount: videos.length ?? 0,
-        contents: new VideoPager(videos)
-    });
-}
-
-export const SourceCollectionToGrayjayPlaylist = (pluginId: string, sourceCollection?: Maybe<Collection>): PlatformPlaylist => {
-    return new PlatformPlaylist({
-        url: `${BASE_URL_PLAYLIST}/${sourceCollection?.xid}`,
-        id: new PlatformID(PLATFORM, sourceCollection?.xid ?? "", pluginId, PLATFORM_CLAIMTYPE),
-        author: SourceAuthorToGrayjayPlatformAuthorLink(pluginId, sourceCollection?.creator),
-        name: sourceCollection?.name,
-        thumbnail: sourceCollection?.thumbnail?.url,
-        videoCount: sourceCollection?.metrics?.engagement?.videos?.edges[0]?.node?.total,
-    });
-}
-
-const getIsLive = (sourceVideo?: Video | Live): boolean => {
-    return (sourceVideo as Live)?.isOnAir === true || (sourceVideo as Video)?.duration == undefined;
-}
-
-const getViewCount = (sourceVideo: Video | Live): number => {
-
-    let viewCount = 0;
-
-    if (getIsLive(sourceVideo)) {
-        viewCount = sourceVideo?.audienceCount ?? sourceVideo?.viewCount ?? sourceVideo?.stats?.views?.total ?? 0;
-    } else {
-        viewCount = sourceVideo?.viewCount ?? sourceVideo?.stats?.views?.total ?? 0;
-    }
-
-    return viewCount;
-}
+import {
+  Channel,
+  Collection,
+  Live,
+  Maybe,
+  Video,
+} from '../types/CodeGenDailymotion';
+
+import {
+  DailymotionStreamingContent,
+  IDailymotionSubtitle,
+} from '../types/types';
+
+import {
+  BASE_URL,
+  BASE_URL_PLAYLIST,
+  BASE_URL_VIDEO,
+  NEGATIVE_RATINGS_LABELS,
+  PLATFORM,
+  PLATFORM_CLAIMTYPE,
+  POSITIVE_RATINGS_LABELS,
+} from './constants';
+
+export const SourceChannelToGrayjayChannel = (
+  pluginId: string,
+  sourceChannel: Channel,
+): PlatformChannel => {
+  const externalLinks = sourceChannel?.externalLinks ?? {};
+
+  const links = Object.keys(externalLinks).reduce(
+    (acc, key) => {
+      if (externalLinks[key]) {
+        acc[key.replace('URL', '')] = externalLinks[key];
+      }
+      return acc;
+    },
+    {} as Record<string, string>,
+  );
+
+  let description = '';
+
+  if (
+    sourceChannel?.tagline &&
+    sourceChannel?.tagline != sourceChannel?.description
+  ) {
+    description = `${sourceChannel?.tagline}\n\n`;
+  }
+
+  description += `${sourceChannel?.description ?? ''}`;
+
+  return new PlatformChannel({
+    id: new PlatformID(
+      PLATFORM,
+      sourceChannel?.id ?? '',
+      pluginId,
+      PLATFORM_CLAIMTYPE,
+    ),
+    name: sourceChannel?.displayName ?? '',
+    thumbnail: sourceChannel?.avatar?.url ?? '',
+    banner: sourceChannel.banner?.url ?? '',
+    subscribers:
+      sourceChannel?.metrics?.engagement?.followers?.edges?.[0]?.node?.total ??
+      0,
+    description,
+    url: `${BASE_URL}/${sourceChannel.name}`,
+    links,
+  });
+};
+
+export const SourceAuthorToGrayjayPlatformAuthorLink = (
+  pluginId: string,
+  creator?: Maybe<Channel>,
+): PlatformAuthorLink => {
+  return new PlatformAuthorLink(
+    new PlatformID(PLATFORM, creator?.id ?? '', pluginId, PLATFORM_CLAIMTYPE),
+    creator?.displayName ?? '',
+    creator?.name ? `${BASE_URL}/${creator?.name}` : '',
+    creator?.avatar?.url ?? '',
+    creator?.followers?.totalCount ??
+      creator?.metrics?.engagement?.followers?.edges?.[0]?.node?.total ??
+      0,
+  );
+};
+
+export const SourceVideoToGrayjayVideo = (
+  pluginId: string,
+  sourceVideo?: DailymotionStreamingContent,
+): PlatformVideo => {
+  const isLive = getIsLive(sourceVideo);
+  const viewCount = getViewCount(sourceVideo);
+
+  const video: PlatformVideoDef = {
+    id: new PlatformID(
+      PLATFORM,
+      sourceVideo?.id ?? '',
+      pluginId,
+      PLATFORM_CLAIMTYPE,
+    ),
+    description: sourceVideo?.description ?? '',
+    name: sourceVideo?.title ?? '',
+    thumbnails: new Thumbnails([
+      new Thumbnail(sourceVideo?.thumbnail?.url ?? '', 0),
+    ]),
+    author: SourceAuthorToGrayjayPlatformAuthorLink(
+      pluginId,
+      sourceVideo?.creator,
+    ),
+    uploadDate: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
+    datetime: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
+    url: `${BASE_URL_VIDEO}/${sourceVideo?.xid}`,
+    duration: (sourceVideo as Video)?.duration ?? 0,
+    viewCount,
+    isLive,
+  };
+
+  return new PlatformVideo(video);
+};
+
+export const SourceCollectionToGrayjayPlaylistDetails = (
+  pluginId: string,
+  sourceCollection: Collection,
+  videos: PlatformVideo[] = [],
+): PlatformPlaylistDetails => {
+  return new PlatformPlaylistDetails({
+    url: sourceCollection?.xid
+      ? `${BASE_URL_PLAYLIST}/${sourceCollection?.xid}`
+      : '',
+    id: new PlatformID(
+      PLATFORM,
+      sourceCollection?.xid ?? '',
+      pluginId,
+      PLATFORM_CLAIMTYPE,
+    ),
+    author: sourceCollection?.creator
+      ? SourceAuthorToGrayjayPlatformAuthorLink(
+          pluginId,
+          sourceCollection?.creator,
+        )
+      : {},
+    name: sourceCollection.name,
+    thumbnail: sourceCollection?.thumbnail?.url,
+    videoCount: videos.length ?? 0,
+    contents: new VideoPager(videos),
+  });
+};
+
+export const SourceCollectionToGrayjayPlaylist = (
+  pluginId: string,
+  sourceCollection?: Maybe<Collection>,
+): PlatformPlaylist => {
+  return new PlatformPlaylist({
+    url: `${BASE_URL_PLAYLIST}/${sourceCollection?.xid}`,
+    id: new PlatformID(
+      PLATFORM,
+      sourceCollection?.xid ?? '',
+      pluginId,
+      PLATFORM_CLAIMTYPE,
+    ),
+    author: SourceAuthorToGrayjayPlatformAuthorLink(
+      pluginId,
+      sourceCollection?.creator,
+    ),
+    name: sourceCollection?.name,
+    thumbnail: sourceCollection?.thumbnail?.url,
+    videoCount:
+      sourceCollection?.metrics?.engagement?.videos?.edges?.[0]?.node?.total,
+  });
+};
+
+const getIsLive = (sourceVideo?: DailymotionStreamingContent): boolean => {
+  return (
+    (sourceVideo as Live)?.isOnAir === true ||
+    (sourceVideo as Video)?.duration == undefined
+  );
+};
+
+const getViewCount = (sourceVideo?: DailymotionStreamingContent): number => {
+  let viewCount = 0;
+
+  if (getIsLive(sourceVideo)) {
+    const live = sourceVideo as Live;
+
+    //TODO: live?.audienceCount and live.stats.views.total are deprecated
+    //live?.metrics?.engagement?.audience?.edges?.[0]?.node?.total is still empty
+    viewCount =
+      live?.metrics?.engagement?.audience?.edges?.[0]?.node?.total ??
+      live?.audienceCount ??
+      live?.stats?.views?.total ??
+      0;
+  } else {
+    const video = sourceVideo as Video;
+
+    // TODO: both fields are deprecated.
+    // video?.stats?.views?.total replaced video?.viewCount
+    // now video?.viewCount is deprecated too but there replacement is not accessible yet
+    viewCount = video?.viewCount ?? video?.stats?.views?.total ?? 0;
+  }
+
+  return viewCount;
+};
 
 export const SourceVideoToPlatformVideoDetailsDef = (
-    pluginId: string,
-    sourceVideo: Video | Live,
-    player_metadata
+  pluginId: string,
+  sourceVideo: Video | Live,
+  player_metadata,
 ): PlatformVideoDetailsDef => {
+  let positiveRatingCount = 0;
 
-    let positiveRatingCount = 0;
-    
-    let negativeRatingCount = 0;
+  let negativeRatingCount = 0;
 
-    const ratings = sourceVideo?.metrics?.engagement?.likes?.edges ?? [];
+  const ratings = sourceVideo?.metrics?.engagement?.likes?.edges ?? [];
 
-    for (const edge of ratings) {
+  for (const edge of ratings) {
+    const ratingName = edge?.node?.rating as string;
+    const ratingTotal = edge?.node?.total as number;
 
-        const ratingName = edge?.node?.rating as string;
-        const ratingTotal = edge?.node?.total as number;
-
-        if (POSITIVE_RATINGS_LABELS.includes(ratingName)) {
-            positiveRatingCount += ratingTotal;
-        } else if (NEGATIVE_RATINGS_LABELS.includes(ratingName)) {
-            negativeRatingCount += ratingTotal;
-        }
+    if (POSITIVE_RATINGS_LABELS.includes(ratingName)) {
+      positiveRatingCount += ratingTotal;
+    } else if (NEGATIVE_RATINGS_LABELS.includes(ratingName)) {
+      negativeRatingCount += ratingTotal;
     }
-
-    const isLive = getIsLive(sourceVideo);
-    const viewCount = getViewCount(sourceVideo);
-    const duration = isLive ? 0 : (sourceVideo as Video)?.duration ?? 0;
-
-    const source = new HLSSource({
-        name: isLive ? 'live': 'source',
-        duration,
-        url: player_metadata?.qualities?.auto[0]?.url,
-    });
-  
-    const sources = [
-        source
-    ];
-    
-    const platformVideoDetails: PlatformVideoDetailsDef = {
-        id: new PlatformID(PLATFORM, sourceVideo?.id ?? "", pluginId, PLATFORM_CLAIMTYPE),
-        name: sourceVideo?.title ?? "",
-        thumbnails: new Thumbnails([new Thumbnail(sourceVideo?.thumbnail?.url ?? "", 0)]), 
-        author: SourceAuthorToGrayjayPlatformAuthorLink(pluginId, sourceVideo?.creator),
-        uploadDate: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
-        datetime: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
-        duration,
-        viewCount,
-        url: sourceVideo?.xid ? `${BASE_URL_VIDEO}/${sourceVideo.xid}` : "",
-        isLive,
-        description: sourceVideo?.description ?? "",
-        video: new VideoSourceDescriptor(sources),
-        rating: new RatingLikesDislikes(positiveRatingCount, negativeRatingCount),
-        dash: null,
-        live: null,
-        hls: null,
-        subtitles: []
-    };
-    
-    const sourceSubtitle = player_metadata?.subtitles as IDailymotionSubtitle;
-  
-
-    if (sourceSubtitle?.enable && sourceSubtitle?.data) {
-        Object.keys(sourceSubtitle.data).forEach(key => {
-            const subtitleData = sourceSubtitle.data[key];
-
-            if (subtitleData) {
-                const subtitleUrl = subtitleData.urls[0];
-
-                platformVideoDetails.subtitles.push({
-                    name: subtitleData.label,
-                    url: subtitleUrl,
-                    format: "text/vtt",
-                    getSubtitles() {
-                        try {
-                            const subResp = http.GET(subtitleUrl, {});
-
-                            if (!subResp.isOk) {
-                                if (IS_TESTING) {
-                                    bridge.log(`Failed to fetch subtitles from ${subtitleUrl}`);
-                                }
-                                return "";
-                            }
-                            return convertSRTtoVTT(subResp.body);
-                        } catch (error: any) {
-                            if (IS_TESTING) {
-                                bridge.log(`Error fetching subtitles: ${error?.message}`);
-                            }
-                            return "";
-                        }
-                    }
-                });
+  }
+
+  const isLive = getIsLive(sourceVideo);
+  const viewCount = getViewCount(sourceVideo);
+  const duration = isLive ? 0 : ((sourceVideo as Video)?.duration ?? 0);
+
+  const source = new HLSSource({
+    name: 'HLS',
+    duration,
+    url: player_metadata?.qualities?.auto[0]?.url,
+  });
+
+  const sources = [source];
+
+  const platformVideoDetails: PlatformVideoDetailsDef = {
+    id: new PlatformID(
+      PLATFORM,
+      sourceVideo?.id ?? '',
+      pluginId,
+      PLATFORM_CLAIMTYPE,
+    ),
+    name: sourceVideo?.title ?? '',
+    thumbnails: new Thumbnails([
+      new Thumbnail(sourceVideo?.thumbnail?.url ?? '', 0),
+    ]),
+    author: SourceAuthorToGrayjayPlatformAuthorLink(
+      pluginId,
+      sourceVideo?.creator,
+    ),
+    //TODO: sourceVideo?.createdAt is deprecated but sourceVideo?.createDate requires authentication
+    uploadDate: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
+    datetime: Math.floor(new Date(sourceVideo?.createdAt).getTime() / 1000),
+    duration,
+    viewCount,
+    url: sourceVideo?.xid ? `${BASE_URL_VIDEO}/${sourceVideo.xid}` : '',
+    isLive,
+    description: sourceVideo?.description ?? '',
+    video: new VideoSourceDescriptor(sources),
+    rating: new RatingLikesDislikes(positiveRatingCount, negativeRatingCount),
+    dash: null,
+    live: null,
+    hls: null,
+    subtitles: [],
+  };
+
+  const sourceSubtitle = player_metadata?.subtitles as IDailymotionSubtitle;
+
+  if (sourceSubtitle?.enable && sourceSubtitle?.data) {
+    Object.keys(sourceSubtitle.data).forEach((key) => {
+      const subtitleData = sourceSubtitle.data[key];
+
+      if (subtitleData) {
+        const subtitleUrl = subtitleData.urls[0];
+
+        platformVideoDetails.subtitles.push({
+          name: subtitleData.label,
+          url: subtitleUrl,
+          format: 'text/vtt',
+          getSubtitles() {
+            try {
+              const subResp = http.GET(subtitleUrl, {});
+
+              if (!subResp.isOk) {
+                if (IS_TESTING) {
+                  bridge.log(`Failed to fetch subtitles from ${subtitleUrl}`);
+                }
+                return '';
+              }
+              return convertSRTtoVTT(subResp.body);
+            } catch (error: any) {
+              if (IS_TESTING) {
+                bridge.log(`Error fetching subtitles: ${error?.message}`);
+              }
+              return '';
             }
+          },
         });
-    }
+      }
+    });
+  }
 
-    return platformVideoDetails;
-}
+  return platformVideoDetails;
+};
 
 /**
  * Converts SRT subtitle format to VTT format.
- * 
+ *
  * @param {string} srt - The SRT subtitle string.
  * @returns {string} - The converted VTT subtitle string.
  */
 export const convertSRTtoVTT = (srt) => {
-    // Initialize the VTT output with the required header
-    const vtt = ['WEBVTT\n\n'];
-    // Split the SRT input into blocks based on double newlines
-    const srtBlocks = srt.split('\n\n');
-
-    // Process each block individually
-    srtBlocks.forEach((block) => {
-        // Split each block into lines
-        const lines = block.split('\n');
-        if (lines.length >= 3) {
-            // Extract and convert the timestamp line
-            const timestamp = lines[1].replace(/,/g, '.');
-            // Extract the subtitle text lines
-            const subtitleText = lines.slice(2).join('\n');
-            // Add the converted block to the VTT output
-            vtt.push(`${timestamp}\n${subtitleText}\n\n`);
-        }
-    });
-
-    // Join the VTT array into a single string and return it
-    return vtt.join('');
-}
+  // Initialize the VTT output with the required header
+  const vtt = ['WEBVTT\n\n'];
+  // Split the SRT input into blocks based on double newlines
+  const srtBlocks = srt.split('\n\n');
+
+  // Process each block individually
+  srtBlocks.forEach((block) => {
+    // Split each block into lines
+    const lines = block.split('\n');
+    if (lines.length >= 3) {
+      // Extract and convert the timestamp line
+      const timestamp = lines[1].replace(/,/g, '.');
+      // Extract the subtitle text lines
+      const subtitleText = lines.slice(2).join('\n');
+      // Add the converted block to the VTT output
+      vtt.push(`${timestamp}\n${subtitleText}\n\n`);
+    }
+  });
 
+  // Join the VTT array into a single string and return it
+  return vtt.join('');
+};
diff --git a/src/Pagers.ts b/src/Pagers.ts
index 4ee15c7b8bbb4735f3506114992e3e306d037fd9..037b4c198d615035c5ba98484c8d226d087d4e81 100644
--- a/src/Pagers.ts
+++ b/src/Pagers.ts
@@ -1,87 +1,115 @@
-export class SearchPagerAll extends VideoPager {
-    cb: Function;
-
-    constructor(results: PlatformVideo[], hasMore: boolean, params: any, page: number, cb: Function) {
-        super(results, hasMore, { params, page });
-        this.cb = cb;
-    }
-
-    nextPage() {
-        this.context.page += 1;
-
-        const opts = {
-            q: this.context.params.query,
-            sort: this.context.params.sort,
-            page: this.context.page,
-            filters: this.context.params.filters
-        };
-
-        return this.cb(opts);
-    }
-}
-
-export class SearchChannelPager extends ChannelPager {
-    cb: any;
-    constructor(results, hasNextPage, params, page, cb) {
-        super(results, hasNextPage, { params, page })
-        this.cb = cb;
-    }
-
-    nextPage() {
-        const opts = { q: this.context.params.query, page: this.context.page += 1 };
-        return this.cb(opts);
-    }
-}
-
-
-
-export class ChannelVideoPager extends VideoPager {
-    cb: Function;
-    constructor(results: PlatformVideo[], hasNextPage: boolean, params, cb: Function) {
-        super(results, hasNextPage, { ...params });
-        this.cb = cb;
-    }
-
-    nextPage() {
-        this.context.page += 1;
-        return this.cb(this.context.url, this.context.page, this.context.type, this.context.order);
-    }
-}
-
-
-export class ChannelPlaylistPager extends PlaylistPager {
-    cb: Function;
-    constructor(results: PlatformPlaylist[], hasMore: boolean, params: any, page: number, cb: Function) {
-        super(results, hasMore, { params, page });
-        this.cb = cb;
-    }
-
-    nextPage() {
-
-        this.context.page += 1;
-
-        return this.cb(this.context.params.url, this.context.page)
-    }
-}
-
-export class SearchPlaylistPager extends PlaylistPager {
-    cb: Function;
-    constructor(results: PlatformPlaylist[], hasMore: boolean, params: any, page: number, cb: Function) {
-        super(results, hasMore, { params, page });
-        this.cb = cb;
-    }
-
-    nextPage() {
-
-        this.context.page = this.context.page + 1
-
-        const opts = {
-            q: this.context.params.query,
-            sort: this.context.params.sort,
-            page: this.context.page,
-            filters: this.context.params.filters
-        };
-
-        return this.cb(opts)
-    }
-}
\ No newline at end of file
+export class SearchPagerAll extends VideoPager {
+  cb: Function;
+
+  constructor(
+    results: PlatformVideo[],
+    hasMore: boolean,
+    params: any,
+    page: number,
+    cb: Function,
+  ) {
+    super(results, hasMore, { params, page });
+    this.cb = cb;
+  }
+
+  nextPage() {
+    this.context.page += 1;
+
+    const opts = {
+      q: this.context.params.query,
+      sort: this.context.params.sort,
+      page: this.context.page,
+      filters: this.context.params.filters,
+    };
+
+    return this.cb(opts);
+  }
+}
+
+export class SearchChannelPager extends ChannelPager {
+  cb: any;
+  constructor(results, hasNextPage, params, page, cb) {
+    super(results, hasNextPage, { params, page });
+    this.cb = cb;
+  }
+
+  nextPage() {
+    const page = (this.context.page += 1);
+
+    const opts = {
+      q: this.context.params.query,
+      page,
+    };
+    return this.cb(opts);
+  }
+}
+
+export class ChannelVideoPager extends VideoPager {
+  cb: Function;
+  constructor(
+    results: PlatformVideo[],
+    hasNextPage: boolean,
+    params,
+    cb: Function,
+  ) {
+    super(results, hasNextPage, { ...params });
+    this.cb = cb;
+  }
+
+  nextPage() {
+    this.context.page += 1;
+    return this.cb(
+      this.context.url,
+      this.context.page,
+      this.context.type,
+      this.context.order,
+    );
+  }
+}
+
+export class ChannelPlaylistPager extends PlaylistPager {
+  cb: Function;
+  constructor(
+    results: PlatformPlaylist[],
+    hasMore: boolean,
+    params: any,
+    page: number,
+    cb: Function,
+  ) {
+    super(results, hasMore, { params, page });
+    this.cb = cb;
+  }
+
+  nextPage() {
+    this.context.page += 1;
+
+    return this.cb(this.context.params.url, this.context.page);
+  }
+}
+
+export class SearchPlaylistPager extends PlaylistPager {
+  cb: Function;
+  constructor(
+    results: PlatformPlaylist[],
+    hasMore: boolean,
+    params: any,
+    page: number,
+    cb: Function,
+  ) {
+    super(results, hasMore, { params, page });
+    this.cb = cb;
+  }
+
+  nextPage() {
+    this.context.page = this.context.page + 1;
+
+    const opts = {
+      q: this.context.params.query,
+      sort: this.context.params.sort,
+      page: this.context.page,
+      filters: this.context.params.filters,
+    };
+
+    return this.cb(opts);
+  }
+}
diff --git a/src/constants.ts b/src/constants.ts
index da09d92ff5365ac42a3577b6cba7ad24ee045905..9506801d9d72645d2e0521c0d5d2d3fecabee3eb 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1,120 +1,149 @@
-export const BASE_URL = "https://www.dailymotion.com";
-export const BASE_URL_API = "https://graphql.api.dailymotion.com";
-export const BASE_URL_COMMENTS = "https://api-2-0.spot.im/v1.0.0/conversation/read";
-export const BASE_URL_COMMENTS_AUTH = "https://api-2-0.spot.im/v1.0.0/authenticate";
-export const BASE_URL_COMMENTS_THUMBNAILS = "https://images.spot.im/image/upload";
+export const BASE_URL = 'https://www.dailymotion.com';
+
+export const BASE_URL_API = 'https://graphql.api.dailymotion.com';
+
+export const BASE_URL_COMMENTS =
+  'https://api-2-0.spot.im/v1.0.0/conversation/read';
+
+export const BASE_URL_COMMENTS_AUTH =
+  'https://api-2-0.spot.im/v1.0.0/authenticate';
+
+export const BASE_URL_COMMENTS_THUMBNAILS =
+  'https://images.spot.im/image/upload';
+
 export const BASE_URL_API_AUTH = `${BASE_URL_API}/oauth/token`;
+
 export const BASE_URL_VIDEO = `${BASE_URL}/video`;
+
 export const BASE_URL_PLAYLIST = `${BASE_URL}/playlist`;
+
 export const BASE_URL_METADATA = `${BASE_URL}/player/metadata/video`;
 
-export const USER_AGENT = 'Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.230 Mobile Safari/537.36'
+export const REGEX_VIDEO_URL =
+  /^https:\/\/(?:www\.)?dailymotion\.com\/video\/[a-zA-Z0-9]+$/i;
+
+export const REGEX_VIDEO_URL_1 = /^https:\/\/dai\.ly\/[a-zA-Z0-9]+$/i;
+
+export const REGEX_VIDEO_URL_EMBED =
+  /^https:\/\/(?:www\.)?dailymotion\.com\/embed\/video\/[a-zA-Z0-9]+(\?.*)?$/i;
+
+export const REGEX_VIDEO_CHANNEL_URL =
+  /^https:\/\/(?:www\.)?dailymotion\.com\/[a-zA-Z0-9-]+$/i;
+  
+export const REGEX_VIDEO_PLAYLIST_URL =
+  /^https:\/\/(?:www\.)?dailymotion\.com\/playlist\/[a-zA-Z0-9]+$/i;
+
+export const REGEX_INITIAL_DATA_API_AUTH_1 =
+  /(?<=window\.__LOADABLE_LOADED_CHUNKS__=.*)\b[a-f0-9]{20}\b|\b[a-f0-9]{40}\b/g;
+
+export const createAuthRegexByTextLength = (length: number) =>
+  new RegExp(`\\b\\w+\\s*=\\s*"([a-zA-Z0-9]{${length}})"`);
+
+export const USER_AGENT =
+  'Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.230 Mobile Safari/537.36';
 
 // Those are used even for not logged users to make requests on the graphql api.
-//TODO: check how to get them dynamically
-export const CLIENT_ID = 'f1a362d288c1b98099c7';
-export const CLIENT_SECRET = 'eea605b96e01c796ff369935357eca920c5da4c5';
+export const FALLBACK_CLIENT_ID = 'f1a362d288c1b98099c7';
+export const FALLBACK_CLIENT_SECRET =
+  'eea605b96e01c796ff369935357eca920c5da4c5';
 
-export const X_DM_AppInfo_Id = "com.dailymotion.neon"
-export const X_DM_AppInfo_Type = "website"
-export const X_DM_AppInfo_Version = "v2024-07-02T13:55:47.186Z" //TODO check how to get this dynamically
-export const X_DM_Neon_SSR = "0"
+export const X_DM_AppInfo_Id = 'com.dailymotion.neon';
+export const X_DM_AppInfo_Type = 'website';
+export const X_DM_AppInfo_Version = 'v2024-07-02T13:55:47.186Z'; //TODO check how to get this dynamically
+export const X_DM_Neon_SSR = '0';
 
-export const PLATFORM = "Dailymotion";
+export const PLATFORM = 'Dailymotion';
 export const PLATFORM_CLAIMTYPE = 27;
 
-
 // search capabilities - upload date
-export const LESS_THAN_MINUTE = "LESS_THAN_MINUTE"
-export const ONE_TO_FIVE_MINUTES = "ONE_TO_FIVE_MINUTES"
-export const FIVE_TO_THIRTY_MINUTES = "FIVE_TO_THIRTY_MINUTES"
-export const THIRTY_TO_ONE_HOUR = "THIRTY_TO_ONE_HOUR"
-export const MORE_THAN_ONE_HOUR = "MORE_THAN_ONE_HOUR"
+export const LESS_THAN_MINUTE = 'LESS_THAN_MINUTE';
+export const ONE_TO_FIVE_MINUTES = 'ONE_TO_FIVE_MINUTES';
+export const FIVE_TO_THIRTY_MINUTES = 'FIVE_TO_THIRTY_MINUTES';
+export const THIRTY_TO_ONE_HOUR = 'THIRTY_TO_ONE_HOUR';
+export const MORE_THAN_ONE_HOUR = 'MORE_THAN_ONE_HOUR';
 
-
-export const DURATION_THRESHOLDS = {}
+export const DURATION_THRESHOLDS = {};
 DURATION_THRESHOLDS[LESS_THAN_MINUTE] = { min: 0, max: 60 };
 DURATION_THRESHOLDS[ONE_TO_FIVE_MINUTES] = { min: 60, max: 300 };
 DURATION_THRESHOLDS[FIVE_TO_THIRTY_MINUTES] = { min: 300, max: 1800 };
 DURATION_THRESHOLDS[THIRTY_TO_ONE_HOUR] = { min: 1800, max: 3600 };
 DURATION_THRESHOLDS[MORE_THAN_ONE_HOUR] = { min: 3600, max: null };
 
+export const LIKE_PLAYLIST_ID = 'LIKE_PLAYLIST';
+export const FAVORITES_PLAYLIST_ID = 'FAVORITES_PLAYLIST';
+export const RECENTLY_WATCHED_PLAYLIST_ID = 'RECENTLY_WATCHED_PLAYLIST';
 
 /** The possible values which liked media connections can be sorted by. */
 export const LikedMediaSort = {
-    /** Sort liked medias by most recent. */
-    Recent : 'recent',
-    /** Sort liked medias by most viewed. */
-    Visited : 'visited'
-}
-
+  /** Sort liked medias by most recent. */
+  Recent: 'recent',
+  /** Sort liked medias by most viewed. */
+  Visited: 'visited',
+};
 
 // This platform uses a scale system for rating the videos.
 // Ratings are grouped into positive and negative to calculate likes and dislikes.
 export const POSITIVE_RATINGS_LABELS = [
-    "STAR_STRUCK", // amazing
-    "SMILING_FACE_WITH_SUNGLASSES", // cool
-    "WINKING_FACE" // interesting
+  'STAR_STRUCK', // amazing
+  'SMILING_FACE_WITH_SUNGLASSES', // cool
+  'WINKING_FACE', // interesting
 ];
 
 export const NEGATIVE_RATINGS_LABELS = [
-    "SLEEPING_FACE", // boring
-    "FISHING_POLE" // waste of time
+  'SLEEPING_FACE', // boring
+  'FISHING_POLE', // waste of time
 ];
 
 export const ERROR_TYPES = {
-    "DM001": "No video has been specified, you need to specify one.",
-    "DM002": "Content has been deleted.",
-    "DM003": "Live content is not available, i.e. it may not have started yet.",
-    "DM004": "Copyrighted content, access forbidden.",
-    "DM005": "Content rejected (this video may have been removed due to a breach of the terms of use, a copyright claim or an infringement upon third party rights).",
-    "DM006": "Publishing in progress…",
-    "DM007": "Video geo-restricted by its owner.",
-    "DM008": "Explicit content. Explicit content can be enabled using the plugin settings",
-    "DM009": "Explicit content (offsite embed)",
-    "DM010": "Private content",
-    "DM011": "An encoding error occurred",
-    "DM012": "Encoding in progress",
-    "DM013": "This video has no preset (no video stream)",
-    "DM014": "This video has not been made available on your device by its owner",
-    "DM015": "Kids host error",
-    "DM016": "Content not available on this website, it can only be watched on Dailymotion",
-    "DM019": "This content has been uploaded by an inactive channel and its access is limited"
+  DM001: 'No video has been specified, you need to specify one.',
+  DM002: 'Content has been deleted.',
+  DM003: 'Live content is not available, i.e. it may not have started yet.',
+  DM004: 'Copyrighted content, access forbidden.',
+  DM005:
+    'Content rejected (this video may have been removed due to a breach of the terms of use, a copyright claim or an infringement upon third party rights).',
+  DM006: 'Publishing in progress…',
+  DM007: 'Video geo-restricted by its owner.',
+  DM008:
+    'Explicit content. Explicit content can be enabled using the plugin settings',
+  DM009: 'Explicit content (offsite embed)',
+  DM010: 'Private content',
+  DM011: 'An encoding error occurred',
+  DM012: 'Encoding in progress',
+  DM013: 'This video has no preset (no video stream)',
+  DM014: 'This video has not been made available on your device by its owner',
+  DM015: 'Kids host error',
+  DM016:
+    'Content not available on this website, it can only be watched on Dailymotion',
+  DM019:
+    'This content has been uploaded by an inactive channel and its access is limited',
 };
 
-
 export const SEARCH_CAPABILITIES = {
-    types: [
-        Type.Feed.Mixed
-    ],
-    sorts: [
-        "Most Recent",
-        "Most Viewed",
-        "Most Relevant"
-    ],
-    filters: [
-        {
-            id: "uploaddate",
-            name: "Upload Date",
-            isMultiSelect: false,
-            filters: [
-                { name: "Today", value: "today" },
-                { name: "Past week", value: "thisweek" },
-                { name: "Past month", value: "thismonth" },
-                { name: "Past year", value: "thisyear" }
-            ]
-        },
-        {
-            id: "duration",
-            name: "Duration",
-            isMultiSelect: false,
-            filters: [
-                { name: "< 1 min", value: LESS_THAN_MINUTE },
-                { name: "1 - 5 min", value: ONE_TO_FIVE_MINUTES },
-                { name: "5 - 30 min", value: FIVE_TO_THIRTY_MINUTES },
-                { name: "30 min - 1 hour", value: THIRTY_TO_ONE_HOUR },
-                { name: "> 1 hour", value: MORE_THAN_ONE_HOUR }
-            ]
-        }
-    ]
-}
\ No newline at end of file
+  types: [Type.Feed.Mixed],
+  sorts: ['Most Recent', 'Most Viewed', 'Most Relevant'],
+  filters: [
+    {
+      id: 'uploaddate',
+      name: 'Upload Date',
+      isMultiSelect: false,
+      filters: [
+        { name: 'Today', value: 'today' },
+        { name: 'Past week', value: 'thisweek' },
+        { name: 'Past month', value: 'thismonth' },
+        { name: 'Past year', value: 'thisyear' },
+      ],
+    },
+    {
+      id: 'duration',
+      name: 'Duration',
+      isMultiSelect: false,
+      filters: [
+        { name: '< 1 min', value: LESS_THAN_MINUTE },
+        { name: '1 - 5 min', value: ONE_TO_FIVE_MINUTES },
+        { name: '5 - 30 min', value: FIVE_TO_THIRTY_MINUTES },
+        { name: '30 min - 1 hour', value: THIRTY_TO_ONE_HOUR },
+        { name: '> 1 hour', value: MORE_THAN_ONE_HOUR },
+      ],
+    },
+  ],
+};
diff --git a/src/extraction.ts b/src/extraction.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d45974981cec526d601c3195c61051298e46ec69
--- /dev/null
+++ b/src/extraction.ts
@@ -0,0 +1,182 @@
+import { AnonymousUserAuthorization } from '../types/types';
+import {
+  BASE_URL,
+  BASE_URL_API_AUTH,
+  createAuthRegexByTextLength,
+  FALLBACK_CLIENT_ID,
+  FALLBACK_CLIENT_SECRET,
+  REGEX_INITIAL_DATA_API_AUTH_1,
+  USER_AGENT,
+} from './constants';
+import { objectToUrlEncodedString } from './util';
+
+export function oauthClientCredentialsRequest(
+  httpClient: IHttp,
+  url: string,
+  clientId: string,
+  secret: string,
+  throwOnInvalid = false,
+): HttpResponse {
+  if (!httpClient || !url || !clientId || !secret) {
+    throw new ScriptException(
+      'Invalid parameters provided to oauthClientCredentialsRequest',
+    );
+  }
+
+  const body = objectToUrlEncodedString({
+    client_id: clientId,
+    client_secret: secret,
+    grant_type: 'client_credentials',
+  });
+
+  try {
+    return httpClient.POST(
+      url,
+      body,
+      {
+        'User-Agent': USER_AGENT,
+        'Content-Type': 'application/x-www-form-urlencoded',
+        Origin: BASE_URL,
+        DNT: '1',
+        'Sec-GPC': '1',
+        Connection: 'keep-alive',
+        'Sec-Fetch-Dest': 'empty',
+        'Sec-Fetch-Mode': 'cors',
+        'Sec-Fetch-Site': 'same-site',
+        Priority: 'u=4',
+        Pragma: 'no-cache',
+        'Cache-Control': 'no-cache',
+      },
+      false,
+    );
+  } catch (error) {
+    console.error('Error making OAuth client credentials request:', error);
+    if (throwOnInvalid) {
+      throw new ScriptException('Failed to obtain OAuth client credentials');
+    }
+    return null;
+  }
+}
+
+export function extractClientCredentials(httpClient: IHttp) {
+  const detailsRequestHtml = httpClient.GET(BASE_URL, {}, false);
+
+  if (!detailsRequestHtml.isOk) {
+    throw new ScriptException('Failed to fetch page to extract auth details');
+  }
+
+  const result = [
+    {
+      clientId: FALLBACK_CLIENT_ID,
+      secret: FALLBACK_CLIENT_SECRET,
+    },
+  ];
+
+  const match = detailsRequestHtml.body.match(REGEX_INITIAL_DATA_API_AUTH_1);
+
+  if (match?.length === 2 && match[0] && match[1]) {
+    result.unshift({
+      clientId: match[0],
+      secret: match[1],
+    });
+    console.log('Successfully extracted API credentials from page:', match[1]);
+  } else {
+    console.log(
+      'Failed to extract API credentials from page using regex. Using DOM parsing.',
+    );
+
+    const htmlElement = domParser.parseFromString(
+      detailsRequestHtml.body,
+      'text/html',
+    );
+    const extractedId = getScriptVariableByTextLength(htmlElement, 20);
+    const extractedSecret = getScriptVariableByTextLength(htmlElement, 40);
+
+    if (extractedId && extractedSecret) {
+      result.unshift({
+        clientId: extractedId,
+        secret: extractedSecret,
+      });
+
+      console.log(
+        'Successfully extracted API credentials from page using DOM parsing:',
+        extractedId,
+      );
+    } else {
+      console.log(
+        'Failed to extract API credentials using DOM parsing with exact text length.',
+      );
+    }
+  }
+
+  return result;
+}
+
+export function getScriptVariableByTextLength(htmlElement, length: number) {
+  const scriptTags = htmlElement.querySelectorAll(
+    'script[type="text/javascript"]',
+  );
+
+  if (!scriptTags.length) {
+    console.error('No script tags found.');
+    return null; // or throw an error, depending on your use case
+  }
+
+  let pageContent = '';
+
+  scriptTags.forEach((tag) => {
+    pageContent += tag.outerHTML;
+  });
+
+  let matches = createAuthRegexByTextLength(length).exec(pageContent);
+
+  if (matches?.length == 2) {
+    return matches[1];
+  }
+}
+
+export function getTokenFromClientCredentials(
+  httpClient: IHttp,
+  credentials,
+  throwOnInvalid = false,
+) {
+  let result: AnonymousUserAuthorization = {
+    isValid: false,
+  };
+
+  for (const credential of credentials) {
+    const res = oauthClientCredentialsRequest(
+      httpClient,
+      BASE_URL_API_AUTH,
+      credential.clientId,
+      credential.secret,
+    );
+
+    if (res?.isOk) {
+      const anonymousTokenResponse = JSON.parse(res.body);
+
+      if (
+        !anonymousTokenResponse.token_type ||
+        !anonymousTokenResponse.access_token
+      ) {
+        console.error('Invalid token response', res);
+        if (throwOnInvalid) {
+          throw new ScriptException('', 'Invalid token response: ' + res.body);
+        }
+      }
+
+      result = {
+        anonymousUserAuthorizationToken: `${anonymousTokenResponse.token_type} ${anonymousTokenResponse.access_token}`,
+        anonymousUserAuthorizationTokenExpirationDate:
+          Date.now() + anonymousTokenResponse.expires_in * 1000,
+        isValid: true,
+      };
+
+      break;
+    } else {
+      console.error('Failed to get token', res);
+    }
+  }
+
+  return result;
+}
diff --git a/src/gqlQueries.ts b/src/gqlQueries.ts
index 279ed40008c0e5523f25ec2eb0030ee190ebdfcc..ec582a1749a9467558ac4b3c022a682d44f91d3a 100644
--- a/src/gqlQueries.ts
+++ b/src/gqlQueries.ts
@@ -1,1043 +1,882 @@
-export const AUTOCOMPLETE_QUERY = `
-query AUTOCOMPLETE_QUERY($query: String!) {
-	search {
-		id
-		suggestedVideos: autosuggestions(
-		query: {eq: $query}
-		filter: {story: {eq: VIDEO}}
-		) {
-		edges {
-			node {
-			name
-			}
-		}
-		}
-	}
-}
-`
-export const CHANNEL_QUERY_DESKTOP = `
-query CHANNEL_QUERY_DESKTOP(
-	$channel_name: String!
-	$avatar_size: AvatarHeight!
-) {
-	channel(name: $channel_name) {
-		id
-		xid
-		name
-		displayName
-		description
-		avatar(height:$avatar_size) {
-			url
-		}
-		banner(width:LANDSCAPE_1920) {
-			url
-		}
-		tagline
-		metrics {
-			engagement {
-				followers {
-					edges {
-						node {
-							total
-						}
-					}
-				}
-				followings {
-					edges {
-						node {
-							total
-						}
-					}
-				}
-			}
-		}
-		stats {
-			id
-			views {
-				id
-				total
-			}
-			videos {
-				id
-				total
-			}
-		}
-		externalLinks {
-			facebookURL
-			twitterURL
-			websiteURL
-			instagramURL
-			pinterestURL
-		}
-	}
-}
-`
-export const SEACH_DISCOVERY_QUERY = `	
-fragment SEARCH_DISCOVERY_VIDEO_FRAGMENT on Video {
-	id
-	xid
-	title
-	isPublished
-	thumbnail(height:$thumbnail_resolution) {
-		url
-	}
-	createdAt
-	creator {
-		id
-		xid
-		name
-		displayName
-		avatar(height:$avatar_size) {
-			url
-		}
-	}
-	duration
-	viewCount
-	stats {
-		views {
-			total
-		}
-	}
-}
-
-query SEACH_DISCOVERY_QUERY($avatar_size: AvatarHeight!, $thumbnail_resolution: ThumbnailHeight!) {
-	home: views {
-		id
-		neon {
-			id
-			sections(space: "home") {
-				edges {
-					node {
-						id
-						name
-						title
-						description
-						components {
-							pageInfo {
-								hasNextPage
-							}
-							edges {
-								node {
-									... on Media {
-										...SEARCH_DISCOVERY_VIDEO_FRAGMENT
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-}`
-
-
-export const CHANNEL_VIDEOS_QUERY = `
-query CHANNEL_VIDEOS_QUERY(
-  $channel_name: String!
-  $first: Int!
-  $sort: String
-  $page: Int!
-  $allowExplicit: Boolean
-  $avatar_size: AvatarHeight!
-  $thumbnail_resolution: ThumbnailHeight!
-  $shouldLoadLives: Boolean!
-  $shouldLoadVideos: Boolean!
-) {
-  channel(name: $channel_name) {
-    id
-    xid
-    lives(
-      page: $page
-      first: $first
-      allowExplicit: $allowExplicit
-    ) @include(if: $shouldLoadLives) {
-      pageInfo {
-        hasNextPage
-        nextPage
-      }
-      totalCount
-      edges {
-        node {
-          id
-          xid
-          title
-          thumbnail(height: $thumbnail_resolution) {
-            url
-          }
-          description
-          metrics {
-            engagement {
-              audience {
-                totalCount
-              }
-            }
-          }
-          audienceCount
-          isOnAir
-          stats {
-            views {
-              total
-            }
-          }
-          creator {
-            id
-            xid
-            name
-            displayName
-            avatar(height: $avatar_size) {
-              url
-            }
-          }
-        }
-      }
-    }
-    videos(
-      page: $page
-      first: $first
-      allowExplicit: $allowExplicit
-      sort: $sort
-    ) @include(if: $shouldLoadVideos) {
-      pageInfo {
-        hasNextPage
-        nextPage
-      }
-      edges {
-        node {
-          id
-          xid
-          title
-          thumbnail(height: $thumbnail_resolution) {
-            url
-          }
-          bestAvailableQuality
-          duration
-          createdAt
-          creator {
-            id
-            name
-            displayName
-            avatar(height: $avatar_size) {
-              url
-            }
-          }
-          metrics {
-            engagement {
-              likes {
-                totalCount
-              }
-            }
-          }
-          viewCount
-          stats {
-            views {
-              total
-            }
-          }
-        }
-      }
-    }
-  }
-}`
-
-
-export const SEARCH_QUERY = ` 
-fragment VIDEO_BASE_FRAGMENT on Video {
-	id
-	xid
-	title
-	createdAt
-	metrics {
-		engagement {
-			likes {
-				edges {
-					node {
-						rating
-						total
-					}
-				}
-			}
-		}
-	}
-	stats {
-		id
-		views {
-			id
-			total
-		}
-	}
-	creator {
-		id
-		xid
-		name
-		displayName
-		description
-		avatar(height:$avatar_size) {
-			url
-		}
-	}
-	duration
-	thumbnail(height:$thumbnail_resolution) {
-		url
-	}
-	
-}
-
-fragment VIDEO_FAVORITES_FRAGMENT on Media {
-	... on Video {
-		id
-		viewerEngagement {
-			id
-			favorited
-		}
-	}
-	... on Live {
-		id
-		viewerEngagement {
-			id
-			favorited
-		}
-	}
-}
-
-fragment CHANNEL_BASE_FRAG on Channel {
-	id
-	xid
-	name
-	displayName
-	description
-	avatar(height:$avatar_size) {
-		url
-	}
-}
-
-fragment PLAYLIST_BASE_FRAG on Collection {
-	id
-	xid
-	name
-	description
-	thumbnail(height:$thumbnail_resolution) {
-		url
-	}
-	creator {
-		id
-		xid
-		name
-		displayName
-		avatar(height:$avatar_size) {
-			url
-		}
-	}
-	description
-	stats {
-		id
-		videos {
-			id
-			total
-		}
-	}
-	metrics {
-		engagement {
-			videos {
-				edges {
-					node {
-						total
-					}
-				}
-			}
-		}
-	}
-}
-
-query SEARCH_QUERY(
-	$query: String!
-	$shouldIncludeVideos: Boolean!
-	$shouldIncludeChannels: Boolean!
-	$shouldIncludePlaylists: Boolean!
-	$shouldIncludeLives: Boolean!
-	$page: Int
-	$limit: Int
-	$sortByVideos: SearchVideoSort
-	$durationMinVideos: Int
-	$durationMaxVideos: Int
-	$createdAfterVideos: DateTime
-	$avatar_size: AvatarHeight!
-	$thumbnail_resolution: ThumbnailHeight!
-) {
-	search {
-		id
-		videos(
-			query: $query
-			first: $limit
-			page: $page
-			sort: $sortByVideos
-			durationMin: $durationMinVideos
-			durationMax: $durationMaxVideos
-			createdAfter: $createdAfterVideos
-		) @include(if: $shouldIncludeVideos) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					...VIDEO_BASE_FRAGMENT
-					...VIDEO_FAVORITES_FRAGMENT
-				}
-			}
-		}
-		lives(query: $query, first: $limit, page: $page)
-			@include(if: $shouldIncludeLives) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					xid
-					title
-					thumbnail(height:$thumbnail_resolution) {
-						url
-					}
-					description
-					metrics {
-						engagement {
-							audience {
-								totalCount
-							}
-						}
-					}
-					audienceCount
-					isOnAir
-					creator {
-						id
-						xid
-						name
-						displayName
-						avatar(height:$avatar_size){
-							url
-						}
-					}
-				}
-			}
-		}
-		channels(query: $query, first: $limit, page: $page)
-			@include(if: $shouldIncludeChannels) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					...CHANNEL_BASE_FRAG
-				}
-			}
-		}
-		playlists: collections(query: $query, first: $limit, page: $page)
-			@include(if: $shouldIncludePlaylists) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					...PLAYLIST_BASE_FRAG
-				}
-			}
-		}
-	}
-}`;
-
-
-export const WATCHING_VIDEO = `
-fragment VIDEO_FRAGMENT on Video {
-	id
-	xid
-	isPublished
-	duration
-	title
-	description
-	thumbnail(height:$thumbnail_resolution) {
-		url
-	}
-	bestAvailableQuality
-	createdAt
-	isPrivate
-	isCreatedForKids
-	isExplicit
-	videoWidth: width
-	videoHeight: height
-	status
-	metrics {
-		engagement {
-			likes {
-				totalCount
-				edges {
-					node {
-						rating
-						total
-					}
-				}
-			}
-		}
-	}
-	stats {
-		id
-		views {
-			id
-			total
-		}
-	}
-	creator {
-		id
-		xid
-		name
-		displayName
-		avatar(height:$avatar_size) {
-			url
-			height
-			width
-		}
-		metrics {
-			engagement {
-				followers {
-					totalCount
-					edges {
-						node {
-							total
-						}
-					}
-				}
-			}
-		}
-		stats {
-			id
-			views {
-				id
-				total
-			}
-			followers {
-				id
-				total
-			}
-			videos {
-				id
-				total
-			}
-		}
-	}
-}
-
-fragment LIVE_FRAGMENT on Live {
-	id
-	xid
-	startAt
-	endAt
-	isPublished
-	title
-	description
-	audienceCount
-	isOnAir
-	thumbnail(height:$thumbnail_resolution){
-		url
-		height
-		width
-	}
-	category
-	createdAt
-	isPrivate
-	isExplicit
-	isCreatedForKids
-	bestAvailableQuality
-	canDisplayAds
-	videoWidth: width
-	videoHeight: height
-	metrics {
-		engagement {
-			likes {
-				edges {
-					node {
-						rating
-						total
-					}
-				}
-			}
-		}
-	}
-	stats {
-		id
-		views {
-			id
-			total
-		}
-	}
-	creator {
-		id
-		xid
-		name
-		displayName
-		avatar(height:$avatar_size) {
-			url
-			height
-			width
-		}
-		coverURLx375: coverURL(size: "x375")
-		stats {
-			id
-			views {
-				id
-				total
-			}
-			followers {
-				id
-				total
-			}
-			videos {
-				id
-				total
-			}
-		}
-		country {
-			id
-			codeAlpha2
-		}
-	}
-	language {
-		id
-		codeAlpha2
-	}
-	tags {
-		edges {
-			node {
-				id
-				label
-			}
-		}
-	}
-	geoblockedCountries {
-		id
-		allowed
-		denied
-	}
-}
-
-query WATCHING_VIDEO(
-	$xid: String!
-	$avatar_size: AvatarHeight!
-	$thumbnail_resolution: ThumbnailHeight!
-) {
-	video: media(xid: $xid) {
-		... on Video {
-			id
-			...VIDEO_FRAGMENT
-		}
-		... on Live {
-			id
-			...LIVE_FRAGMENT
-		}
-	}
-}`
-
-
-export const SEARCH_CHANNEL = `		
-query SEARCH_QUERY($query: String!, $page: Int, $limit: Int, $avatar_size: AvatarHeight!) {
-	search {
-		id
-		channels(query: $query, first: $limit, page: $page) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			totalCount
-			edges {
-				node {
-					id
-					id
-					xid
-					name
-					displayName
-					description
-					avatar(height:$avatar_size) {
-						url
-						height
-						width
-					}
-					metrics {
-						engagement {
-							followers {
-								edges {
-									node {
-										total
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-}`
-
-
-export const PLAYLIST_DETAILS_QUERY = `
-query PLAYLIST_VIDEO_QUERY($xid: String!, $numberOfVideos: Int = 100, $avatar_size: AvatarHeight!, $thumbnail_resolution: ThumbnailHeight!) {
-	collection(xid: $xid) {
-		id
-		id
-		xid
-		updatedAt
-		name
-		thumbnail(height:$thumbnail_resolution) {
-			url
-		}
-		creator {
-			id
-			name
-			displayName
-			xid
-			avatar(height:$avatar_size) {
-				url
-			}
-			metrics {
-				engagement {
-					followers {
-						edges {
-							node {
-								total
-							}
-						}
-					}
-				}
-			}
-		}
-		metrics {
-			engagement {
-				videos {
-					edges {
-						node {
-							total
-						}
-					}
-				}
-			}
-		}
-		videos(first: $numberOfVideos) {
-			edges {
-				node {
-					id
-					xid
-					duration
-					title
-					description
-					url
-					createdAt
-					thumbnail(height:$thumbnail_resolution) {
-						url
-					}
-					creator {
-						id
-						name
-						displayName
-						xid
-						avatar(height:$avatar_size) {
-							url
-						}
-						metrics {
-							engagement {
-								followers {
-									edges {
-										node {
-											total
-										}
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-}`
-
-export const GET_USER_SUBSCRIPTIONS = `
-query SUBSCRIPTIONS_QUERY($first: Int, $page: Int) {
-	me {
-		channel {
-			followings(first: $first, page: $page) {
-				totalCount
-				edges {
-					node {
-						creator {
-							name
-						}
-					}
-				}
-			}
-		}
-	}
-}`;
-
-export const GET_CHANNEL_PLAYLISTS_XID = `
-query CHANNEL_PLAYLISTS_QUERY(
-	$channel_name: String!
-	$sort: String
-	$page: Int!
-	$first: Int!
-) {
-	channel(name: $channel_name) {
-		collections(
-			sort: $sort
-			page: $page
-			first: $first
-		) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			edges {
-				node {
-					xid
-						}
-					}
-				}
-			}
-}`
-
-export const SUBSCRIPTIONS_QUERY = `
-query SUBSCRIPTIONS_QUERY {
-	me {
-		xid
-		channel {
-			name
-		}
-	}
-}
-`;
-
-
-export const CHANNEL_PLAYLISTS_QUERY = `
-query CHANNEL_PLAYLISTS_QUERY(
-	$channel_name: String!
-	$sort: String
-	$page: Int!
-	$first: Int!
-	$avatar_size: AvatarHeight!, 
-	$thumbnail_resolution: ThumbnailHeight!
-) {
-	channel(name: $channel_name) {
-		id
-		xid
-		collections(sort: $sort, page: $page, first: $first) {
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-			edges {
-				node {
-					id
-					xid
-					updatedAt
-					createdAt
-					name
-					description
-					metrics {
-						engagement {
-							videos {
-								edges {
-									node {
-										total
-									}
-								}
-								totalCount
-							}
-						}
-					}
-					thumbnail(height:$thumbnail_resolution) {
-						url
-					}
-					stats {
-						id
-						videos {
-							id
-							total
-						}
-					}
-					videos {
-						edges {
-							node {
-								createdAt
-								creator {
-									id
-									name
-									xid
-									avatar(height:$avatar_size) {
-										url
-									}
-									displayName
-								}
-							}
-						}
-					}
-				}
-			}
-		}
-	}
-}
-
-`
-
-
-export const USER_LIKED_VIDEOS_QUERY = `
-query USER_LIKED_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
-	me {
-		id
-		likedMedias(first: 100, page: $page) {
-			edges {
-				node {
-					... on Video {
-						id
-						xid
-						title
-						duration
-						thumbnail(height:$thumbnail_resolution) {
-							url
-						}
-						aspectRatio
-						viewerEngagement {
-							id
-							liked
-						}
-						channel {
-							id
-							logoURLx25: logoURL(size: "x25")
-							displayName
-							accountType	
-						}
-					}
-					... on Live {
-						
-						id
-						xid
-						title
-						isOnAir
-						thumbnail(height:$thumbnail_resolution) {
-							url
-						}
-						viewerEngagement {
-							id
-							liked
-						}
-						channel {
-							id
-							logoURLx25: logoURL(size: "x25")
-							displayName
-							accountType
-						}
-					}
-				}
-			}
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-		}
-	}
-}`
-
-export const USER_WATCH_LATER_VIDEOS_QUERY = `
-	query USER_WATCH_LATER_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
-	me {
-		id
-		watchLaterMedias(first: 100, page: $page) {
-			edges {
-				node {
-					... on Video {
-						id
-						xid
-						title
-						duration
-						thumbnail(height:$thumbnail_resolution) {
-							url
-						}
-						aspectRatio
-						channel {
-							id
-							logoURLx25: logoURL(size: "x25")
-							displayName
-							accountType
-						}
-						viewerEngagement {
-							id
-							favorited
-						}
-					}
-					... on Live {
-						id
-						xid
-						title
-						isOnAir
-						thumbnail(height:$thumbnail_resolution) {
-							url
-						}
-						channel {
-							id
-							logoURLx25: logoURL(size: "x25")
-							displayName
-							accountType
-						}
-						viewerEngagement {
-							id
-							favorited
-						}
-					}
-				}
-			}
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-		}
-	}
-}`
-
-
-export const USER_WATCHED_VIDEOS_QUERY = `
-	query USER_WATCHED_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
-	me {
-		id
-		watchedVideos(first: 100, page: $page) {
-			edges {
-				node {
-					id
-					xid
-					title
-					duration
-					thumbnail(height:$thumbnail_resolution) {
-						url
-					}
-					aspectRatio
-					channel {
-						id
-						logoURLx25: logoURL(size: "x25")
-						displayName
-						accountType
-					}
-				}
-			}
-			pageInfo {
-				hasNextPage
-				nextPage
-			}
-		}
-	}
-}`
\ No newline at end of file
+export const AUTOCOMPLETE_QUERY = `
+query AUTOCOMPLETE_QUERY($query: String!) {
+  search {
+    suggestedVideos: autosuggestions(
+      query: { eq: $query }
+      filter: { story: { eq: VIDEO } }
+    ) {
+      edges {
+        node {
+          name
+        }
+      }
+    }
+  }
+}`;
+
+export const CHANNEL_QUERY_DESKTOP = `
+query CHANNEL_QUERY_DESKTOP(
+	$channel_name: String!
+	$avatar_size: AvatarHeight!
+) {
+	channel(name: $channel_name) {
+		id
+		xid
+		name
+		displayName
+		description
+		avatar(height:$avatar_size) {
+			url
+		}
+		banner(width:LANDSCAPE_1920) {
+			url
+		}
+		tagline
+		metrics {
+			engagement {
+				followers {
+					edges {
+						node {
+							total
+						}
+					}
+				}
+				followings {
+					edges {
+						node {
+							total
+						}
+					}
+				}
+			}
+		}
+		stats {
+			views {
+				total
+			}
+			videos {
+				total
+			}
+		}
+		externalLinks {
+			facebookURL
+			twitterURL
+			websiteURL
+			instagramURL
+			pinterestURL
+		}
+	}
+}`;
+
+export const SEACH_DISCOVERY_QUERY = `	
+fragment SEARCH_DISCOVERY_VIDEO_FRAGMENT on Video {
+	id
+	xid
+	title
+	thumbnail(height:$thumbnail_resolution) {
+		url
+	}
+	createdAt
+	creator {
+		id
+		xid
+		name
+		displayName
+		avatar(height:$avatar_size) {
+			url
+		}
+	}
+	duration
+	viewCount
+	stats {
+		views {
+			total
+		}
+	}
+}
+
+query SEACH_DISCOVERY_QUERY($avatar_size: AvatarHeight!, $thumbnail_resolution: ThumbnailHeight!) {
+	home: views {
+		neon {
+			sections(space: "home") {
+				edges {
+					node {
+						id
+						name
+						title
+						description
+						components {
+							pageInfo {
+								hasNextPage
+							}
+							edges {
+								node {
+									... on Media {
+										...SEARCH_DISCOVERY_VIDEO_FRAGMENT
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}`;
+
+export const CHANNEL_VIDEOS_QUERY = `
+query CHANNEL_VIDEOS_QUERY(
+  $channel_name: String!
+  $first: Int!
+  $sort: String
+  $page: Int!
+  $allowExplicit: Boolean
+  $avatar_size: AvatarHeight!
+  $thumbnail_resolution: ThumbnailHeight!
+  $shouldLoadLives: Boolean!
+  $shouldLoadVideos: Boolean!
+) {
+  channel(name: $channel_name) {
+    id
+    xid
+    lives(
+      page: $page
+      first: $first
+      allowExplicit: $allowExplicit
+    ) @include(if: $shouldLoadLives) {
+      pageInfo {
+        hasNextPage
+        nextPage
+      }
+      totalCount
+      edges {
+        node {
+          id
+          xid
+          title
+          thumbnail(height: $thumbnail_resolution) {
+            url
+          }
+          description
+          metrics {
+            engagement {
+              audience {
+                totalCount
+              }
+            }
+          }
+          audienceCount
+          isOnAir
+          stats {
+            views {
+              total
+            }
+          }
+          creator {
+            id
+            xid
+            name
+            displayName
+            avatar(height: $avatar_size) {
+              url
+            }
+          }
+        }
+      }
+    }
+    videos(
+      page: $page
+      first: $first
+      allowExplicit: $allowExplicit
+      sort: $sort
+    ) @include(if: $shouldLoadVideos) {
+      pageInfo {
+        hasNextPage
+        nextPage
+      }
+      edges {
+        node {
+          id
+          xid
+          title
+          thumbnail(height: $thumbnail_resolution) {
+            url
+          }
+          duration
+          createdAt
+          creator {
+            id
+            name
+            displayName
+            avatar(height: $avatar_size) {
+              url
+            }
+          }
+          metrics {
+            engagement {
+              likes {
+                totalCount
+              }
+            }
+          }
+          viewCount
+          stats {
+            views {
+              total
+            }
+          }
+        }
+      }
+    }
+  }
+}`;
+
+export const SEARCH_QUERY = ` 
+fragment VIDEO_BASE_FRAGMENT on Video {
+	id
+	xid
+	title
+	createdAt
+	metrics {
+		engagement {
+			likes {
+				edges {
+					node {
+						rating
+						total
+					}
+				}
+			}
+		}
+	}
+	stats {
+		views {
+			total
+		}
+	}
+	creator {
+		id
+		xid
+		name
+		displayName
+		description
+		avatar(height:$avatar_size) {
+			url
+		}
+	}
+	duration
+	thumbnail(height:$thumbnail_resolution) {
+		url
+	}
+}
+
+fragment PLAYLIST_BASE_FRAG on Collection {
+	id
+	xid
+	name
+	description
+	thumbnail(height:$thumbnail_resolution) {
+		url
+	}
+	creator {
+		id
+		xid
+		name
+		displayName
+		avatar(height:$avatar_size) {
+			url
+		}
+	}
+	description
+	stats {
+		videos {
+			total
+		}
+	}
+	metrics {
+		engagement {
+			videos {
+				edges {
+					node {
+						total
+					}
+				}
+			}
+		}
+	}
+}
+
+query SEARCH_QUERY(
+	$query: String!
+	$shouldIncludeVideos: Boolean!
+	$shouldIncludePlaylists: Boolean!
+	$shouldIncludeLives: Boolean!
+	$page: Int
+	$limit: Int
+	$sortByVideos: SearchVideoSort
+	$durationMinVideos: Int
+	$durationMaxVideos: Int
+	$createdAfterVideos: DateTime
+	$avatar_size: AvatarHeight!
+	$thumbnail_resolution: ThumbnailHeight!
+) {
+	search {
+		videos(
+			query: $query
+			first: $limit
+			page: $page
+			sort: $sortByVideos
+			durationMin: $durationMinVideos
+			durationMax: $durationMaxVideos
+			createdAfter: $createdAfterVideos
+		) @include(if: $shouldIncludeVideos) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			totalCount
+			edges {
+				node {
+					id
+					...VIDEO_BASE_FRAGMENT
+				}
+			}
+		}
+		lives(query: $query, first: $limit, page: $page)
+			@include(if: $shouldIncludeLives) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			totalCount
+			edges {
+				node {
+					id
+					xid
+					title
+					thumbnail(height:$thumbnail_resolution) {
+						url
+					}
+					description
+					metrics {
+						engagement {
+							audience {
+								totalCount
+							}
+						}
+					}
+					audienceCount
+					isOnAir
+					creator {
+						id
+						xid
+						name
+						displayName
+						avatar(height:$avatar_size){
+							url
+						}
+					}
+				}
+			}
+		}
+		playlists: collections(query: $query, first: $limit, page: $page)
+			@include(if: $shouldIncludePlaylists) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			totalCount
+			edges {
+				node {
+					id
+					...PLAYLIST_BASE_FRAG
+				}
+			}
+		}
+	}
+}`;
+
+export const WATCHING_VIDEO = `
+fragment VIDEO_FRAGMENT on Video {
+	id
+	xid
+	duration
+	title
+	description
+	thumbnail(height:$thumbnail_resolution) {
+		url
+	}
+	createdAt
+	metrics {
+		engagement {
+			likes {
+				totalCount
+				edges {
+					node {
+						rating
+						total
+					}
+				}
+			}
+		}
+	}
+	stats {
+		views {
+			total
+		}
+	}
+	creator {
+		id
+		xid
+		name
+		displayName
+		avatar(height:$avatar_size) {
+			url
+			height
+			width
+		}
+		metrics {
+			engagement {
+				followers {
+					totalCount
+					edges {
+						node {
+							total
+						}
+					}
+				}
+			}
+		}
+		stats {
+			views {
+				total
+			}
+			followers {
+				total
+			}
+			videos {
+				total
+			}
+		}
+	}
+}
+
+fragment LIVE_FRAGMENT on Live {
+	id
+	xid
+	startAt
+	endAt
+	title
+	description
+	audienceCount
+	isOnAir
+	thumbnail(height:$thumbnail_resolution){
+		url
+	}
+	createdAt
+	videoWidth: width
+	videoHeight: height
+	metrics {
+		engagement {
+			likes {
+				edges {
+					node {
+						rating
+						total
+					}
+				}
+			}
+		}
+	}
+	stats {
+		views {
+			total
+		}
+	}
+	creator {
+		id
+		xid
+		name
+		displayName
+		avatar(height:$avatar_size) {
+			url
+			height
+			width
+		}
+		stats {
+			views {
+				total
+			}
+			followers {
+				total
+			}
+			videos {
+				total
+			}
+		}
+	}
+}
+
+query WATCHING_VIDEO(
+	$xid: String!
+	$avatar_size: AvatarHeight!
+	$thumbnail_resolution: ThumbnailHeight!
+) {
+	video: media(xid: $xid) {
+		... on Video {
+			id
+			...VIDEO_FRAGMENT
+		}
+		... on Live {
+			id
+			...LIVE_FRAGMENT
+		}
+	}
+}`;
+
+export const SEARCH_CHANNEL = `		
+query SEARCH_QUERY($query: String!, $page: Int, $limit: Int, $avatar_size: AvatarHeight!) {
+	search {
+		channels(query: $query, first: $limit, page: $page) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			totalCount
+			edges {
+				node {
+					id
+					xid
+					name
+					displayName
+					description
+					avatar(height:$avatar_size) {
+						url
+					}
+					metrics {
+						engagement {
+							followers {
+								edges {
+									node {
+										total
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}`;
+
+export const PLAYLIST_DETAILS_QUERY = `
+query PLAYLIST_VIDEO_QUERY($xid: String!, $numberOfVideos: Int = 100, $avatar_size: AvatarHeight!, $thumbnail_resolution: ThumbnailHeight!) {
+	collection(xid: $xid) {
+		id
+		xid
+		name
+		thumbnail(height:$thumbnail_resolution) {
+			url
+		}
+		creator {
+			id
+			name
+			displayName
+			xid
+			avatar(height:$avatar_size) {
+				url
+			}
+			metrics {
+				engagement {
+					followers {
+						edges {
+							node {
+								total
+							}
+						}
+					}
+				}
+			}
+		}
+		metrics {
+			engagement {
+				videos {
+					edges {
+						node {
+							total
+						}
+					}
+				}
+			}
+		}
+		videos(first: $numberOfVideos) {
+			edges {
+				node {
+					id
+					xid
+					duration
+					title
+					description
+					url
+					createdAt
+					thumbnail(height:$thumbnail_resolution) {
+						url
+					}
+					creator {
+						id
+						name
+						displayName
+						xid
+						avatar(height:$avatar_size) {
+							url
+						}
+						metrics {
+							engagement {
+								followers {
+									edges {
+										node {
+											total
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}`;
+
+export const GET_USER_SUBSCRIPTIONS = `
+query SUBSCRIPTIONS_QUERY($first: Int, $page: Int) {
+	me {
+		channel {
+			followings(first: $first, page: $page) {
+				totalCount
+				edges {
+					node {
+						creator {
+							name
+						}
+					}
+				}
+			}
+		}
+	}
+}`;
+
+export const GET_CHANNEL_PLAYLISTS_XID = `
+query CHANNEL_PLAYLISTS_QUERY(
+	$channel_name: String!
+	$sort: String
+	$page: Int!
+	$first: Int!
+) {
+	channel(name: $channel_name) {
+		collections(
+			sort: $sort
+			page: $page
+			first: $first
+		) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			edges {
+				node {
+					xid
+						}
+					}
+				}
+			}
+}`;
+
+export const SUBSCRIPTIONS_QUERY = `
+query SUBSCRIPTIONS_QUERY {
+	me {
+		xid
+		channel {
+			name
+		}
+	}
+}
+`;
+
+export const CHANNEL_PLAYLISTS_QUERY = `
+query CHANNEL_PLAYLISTS_QUERY(
+	$channel_name: String!
+	$sort: String
+	$page: Int!
+	$first: Int!
+	$avatar_size: AvatarHeight!, 
+	$thumbnail_resolution: ThumbnailHeight!
+) {
+	channel(name: $channel_name) {
+		id
+		xid
+		collections(sort: $sort, page: $page, first: $first) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			edges {
+				node {
+					id
+					xid
+					createdAt
+					name
+					description
+					metrics {
+						engagement {
+							videos {
+								edges {
+									node {
+										total
+									}
+								}
+								totalCount
+							}
+						}
+					}
+					thumbnail(height:$thumbnail_resolution) {
+						url
+					}
+					stats {
+						videos {
+							total
+						}
+					}
+					videos {
+						edges {
+							node {
+								createdAt
+								creator {
+									id
+									name
+									xid
+									avatar(height:$avatar_size) {
+										url
+									}
+									displayName
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+`;
+
+export const USER_LIKED_VIDEOS_QUERY = `
+query USER_LIKED_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
+	me {
+		likedMedias(first: 100, page: $page) {
+			edges {
+				node {
+					... on Video {
+						id
+						xid
+						title
+						duration
+						thumbnail(height:$thumbnail_resolution) {
+							url
+						}
+						channel {
+							displayName
+						}
+					}
+					... on Live {
+						
+						id
+						xid
+						title
+						isOnAir
+						thumbnail(height:$thumbnail_resolution) {
+							url
+						}
+						channel {
+							displayName
+						}
+					}
+				}
+			}
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+		}
+	}
+}`;
+
+export const USER_WATCH_LATER_VIDEOS_QUERY = `
+	query USER_WATCH_LATER_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
+	me {
+		id
+		watchLaterMedias(first: 100, page: $page) {
+			edges {
+				node {
+					... on Video {
+						id
+						xid
+						title
+						duration
+						thumbnail(height:$thumbnail_resolution) {
+							url
+						}
+						channel {
+							displayName
+						}
+					}
+					... on Live {
+						id
+						xid
+						title
+						isOnAir
+						thumbnail(height:$thumbnail_resolution) {
+							url
+						}
+						channel {
+							displayName
+						}
+					}
+				}
+			}
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+		}
+	}
+}`;
+
+export const USER_WATCHED_VIDEOS_QUERY = `
+	query USER_WATCHED_VIDEOS_QUERY($page: Int!, $thumbnail_resolution: ThumbnailHeight!) {
+	me {
+		id
+		watchedVideos(first: 100, page: $page) {
+			edges {
+				node {
+					id
+					xid
+					title
+					duration
+					thumbnail(height:$thumbnail_resolution) {
+						url
+					}
+					channel {
+						displayName
+					}
+				}
+			}
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+		}
+	}
+}`;
diff --git a/src/util.ts b/src/util.ts
index af3af178b51103c1df6f2fb4ea93f637a0d6c415..4d8b8af5bfde5e8541e883047e510009a36e25bc 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -1,118 +1,118 @@
-import {
-    BASE_URL,
-    DURATION_THRESHOLDS
-} from './constants'
-
-export const objectToUrlEncodedString = (obj) => {
-
-    const encodedParams: string[] = [];
-
-    for (const key in obj) {
-        if (obj.hasOwnProperty(key)) {
-
-            const encodedKey = encodeURIComponent(key);
-            const encodedValue = encodeURIComponent(obj[key]);
-            encodedParams.push(`${encodedKey}=${encodedValue}`);
-        }
-    }
-
-    return encodedParams.join('&');
-}
-
-
-export function getChannelNameFromUrl(url) {
-    const channel_name = url.split('/').pop();
-    return channel_name;
-}
-
-export function isUsernameUrl(url) {
-
-    const regex = new RegExp('^' + BASE_URL.replace(/\./g, '\\.') + '/[^/]+$');
-
-    return regex.test(url);
-}
-
-export const parseUploadDateFilter = (filter) => {
-    let createdAfterVideos;
-
-    const now = new Date();
-
-    switch (filter) {
-        case "today":
-            // Last 24 hours from now
-            const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
-            createdAfterVideos = yesterday.toISOString();
-            break;
-        case "thisweek":
-            // Adjusts to the start of the current week (assuming week starts on Sunday)
-            const startOfWeek = new Date(now.setDate(now.getDate() - now.getDay()));
-            createdAfterVideos = new Date(startOfWeek.getFullYear(), startOfWeek.getMonth(), startOfWeek.getDate()).toISOString();
-            break;
-        case "thismonth":
-            // Adjusts to the start of the month
-            createdAfterVideos = new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
-            break;
-        case "thisyear":
-            // Adjusts to the start of the year
-            createdAfterVideos = new Date(now.getFullYear(), 0, 1).toISOString();
-            break;
-        default:
-            createdAfterVideos = null;
-    }
-    return createdAfterVideos;
-}
-
-
-export const parseSort = (order) => {
-    let sort;
-    switch (order) {
-        //TODO: refact this to use constants
-        case "Most Recent":
-            sort = "RECENT";
-            break;
-        case "Most Viewed":
-            sort = "VIEW_COUNT";
-            break;
-        case "Most Relevant":
-            sort = "RELEVANCE";
-            break;
-        default:
-            sort = order; // Default to the original order if no match
-    }
-    return sort
-}
-
-export const getQuery = (context) => {
-    context.sort = parseSort(context.order);
-
-    if (!context.filters) {
-        context.filters = {};
-    }
-
-    if (!context.page) {
-        context.page = 1;
-    }
-
-    if (context?.filters.duration) {
-        context.filters.durationMinVideos = DURATION_THRESHOLDS[context.filters.duration].min;
-        context.filters.durationMaxVideos = DURATION_THRESHOLDS[context.filters.duration].max;
-    } else {
-        context.filters.durationMinVideos = null;
-        context.filters.durationMaxVideos = null;
-    }
-
-    if (context.filters.uploaddate) {
-        context.filters.createdAfterVideos = parseUploadDateFilter(context.filters.uploaddate[0]);
-    }
-
-    return context;
-}
-
-
-export function generateUUIDv4() {
-    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
-        const r = Math.random() * 16 | 0;
-        const v = c === 'x' ? r : (r & 0x3 | 0x8);
-        return v.toString(16);
-    });
-}
\ No newline at end of file
+import { BASE_URL, DURATION_THRESHOLDS } from './constants';
+
+export const objectToUrlEncodedString = (obj) => {
+  const encodedParams: string[] = [];
+
+  for (const key in obj) {
+    if (obj.hasOwnProperty(key)) {
+      const encodedKey = encodeURIComponent(key);
+      const encodedValue = encodeURIComponent(obj[key]);
+      encodedParams.push(`${encodedKey}=${encodedValue}`);
+    }
+  }
+
+  return encodedParams.join('&');
+};
+
+export function getChannelNameFromUrl(url) {
+  const channel_name = url.split('/').pop();
+  return channel_name;
+}
+
+export const parseUploadDateFilter = (filter: string): string | null => {
+  let createdAfterVideos: string | null = null;
+  const now = new Date();
+
+  switch (filter) {
+    case 'today': {
+      // Last 24 hours from now
+      const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
+      createdAfterVideos = yesterday.toISOString();
+      break;
+    }
+    case 'thisweek': {
+      // Adjusts to the start of the current week (assuming week starts on Sunday)
+      const startOfWeek = new Date(now.getTime());
+      startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay());
+      createdAfterVideos = new Date(
+        startOfWeek.getFullYear(),
+        startOfWeek.getMonth(),
+        startOfWeek.getDate(),
+      ).toISOString();
+      break;
+    }
+    case 'thismonth': {
+      // Adjusts to the start of the month
+      createdAfterVideos = new Date(
+        now.getFullYear(),
+        now.getMonth(),
+        1,
+      ).toISOString();
+      break;
+    }
+    case 'thisyear': {
+      // Adjusts to the start of the year
+      createdAfterVideos = new Date(now.getFullYear(), 0, 1).toISOString();
+      break;
+    }
+  }
+
+  return createdAfterVideos;
+};
+
+export const parseSort = (order: string) => {
+  let sort;
+  switch (order) {
+    //TODO: refact this to use constants
+    case 'Most Recent':
+      sort = 'RECENT';
+      break;
+    case 'Most Viewed':
+      sort = 'VIEW_COUNT';
+      break;
+    case 'Most Relevant':
+      sort = 'RELEVANCE';
+      break;
+    default:
+      sort = order; // Default to the original order if no match
+  }
+  return sort;
+};
+
+export const getQuery = (context) => {
+  context.sort = parseSort(context.order);
+
+  if (!context.filters) {
+    context.filters = {};
+  }
+
+  if (!context.page) {
+    context.page = 1;
+  }
+
+  if (context?.filters.duration) {
+    context.filters.durationMinVideos =
+      DURATION_THRESHOLDS[context.filters.duration].min;
+    context.filters.durationMaxVideos =
+      DURATION_THRESHOLDS[context.filters.duration].max;
+  } else {
+    context.filters.durationMinVideos = null;
+    context.filters.durationMaxVideos = null;
+  }
+
+  if (context.filters.uploaddate) {
+    context.filters.createdAfterVideos = parseUploadDateFilter(
+      context.filters.uploaddate[0],
+    );
+  }
+
+  return context;
+};
+
+export function generateUUIDv4() {
+  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+    const r = (Math.random() * 16) | 0;
+    const v = c === 'x' ? r : (r & 0x3) | 0x8;
+    return v.toString(16);
+  });
+}
diff --git a/types/plugin.d.ts b/types/plugin.d.ts
index e0be05a22b35ec09054ac06a284199cdc42c651e..74a026f04725775611f71856e57c4c0418c429fb 100644
--- a/types/plugin.d.ts
+++ b/types/plugin.d.ts
@@ -1,1442 +1,1515 @@
-//Reference Scriptfile
-//Intended exclusively for auto-complete in your IDE, not for execution
-
-declare class ScriptException extends Error {
-
-    plugin_type: string;
-    msg: string;
-    message: string;
-
-    //If only one parameter is provided, acts as msg
-    constructor(type: string, msg: string) {
-        if (arguments.length == 1) {
-            super(arguments[0]);
-            this.plugin_type = "ScriptException";
-            this.message = arguments[0];
-        }
-        else {
-            super(msg);
-            this.plugin_type = type ?? ""; //string
-            this.msg = msg ?? ""; //string
-        }
-    }
-}
-
-declare class LoginRequiredException extends ScriptException {
-    constructor(msg: string) {
-        super("ScriptLoginRequiredException", msg);
-    }
-}
-
-//Alias
-declare class ScriptLoginRequiredException extends ScriptException {
-    constructor(msg: string) {
-        super("ScriptLoginRequiredException", msg);
-    }
-}
-
-declare class CaptchaRequiredException extends ScriptException {
-
-    plugin_type: string;
-    url: string;
-    body: any;
-
-    constructor(url: string, body: string) {
-        super(JSON.stringify({ 'plugin_type': 'CaptchaRequiredException', url, body }));
-        this.plugin_type = "CaptchaRequiredException";
-        this.url = url;
-        this.body = body;
-    }
-}
-
-declare class CriticalException extends ScriptException {
-    constructor(msg: string) {
-        super("CriticalException", msg);
-    }
-}
-
-declare class UnavailableException extends ScriptException {
-    constructor(msg: string) {
-        super("UnavailableException", msg);
-    }
-}
-
-declare class AgeException extends ScriptException {
-    constructor(msg: string) {
-        super("AgeException", msg);
-    }
-}
-
-declare class TimeoutException extends ScriptException {
-    plugin_type: string;
-
-    constructor(msg: string) {
-        super(msg);
-        this.plugin_type = "ScriptTimeoutException";
-    }
-}
-
-declare class ScriptImplementationException extends ScriptException {
-    plugin_type: string;
-
-    constructor(msg: string) {
-        super(msg);
-        this.plugin_type = "ScriptImplementationException";
-    }
-}
-
-declare class Thumbnails {
-    constructor(thumbnails: Thumbnail[]) {
-        this.sources = thumbnails ?? []; // Thumbnail[]
-    }
-}
-declare class Thumbnail {
-    constructor(url: string, quality: number) {
-        this.url = url ?? ""; //string
-        this.quality = quality ?? 0; //integer
-    }
-}
-
-declare class PlatformID {
-    constructor(platform: string, id: string, pluginId: string, claimType?: number = 0, claimFieldType?: number = -1) {
-        this.platform = platform ?? ""; //string
-        this.pluginId = pluginId; //string
-        this.value = id; //string
-        this.claimType = claimType ?? 0; //int
-        this.claimFieldType = claimFieldType ?? -1; //int
-    }
-}
-
-declare class PlatformContent {
-
-    contentType: number;
-    id: PlatformID;
-    name: string;
-    thumbnails: Thumbnail[];
-    author: PlatformAuthorLink;
-    datetime: number;
-    url: string;
-
-    constructor(obj: any, type: number) {
-        this.contentType = type;
-        obj = obj ?? {};
-        this.id = obj.id ?? PlatformID();   //PlatformID
-        this.name = obj.name ?? ""; //string
-        this.thumbnails = obj.thumbnails; //Thumbnail[]
-        this.author = obj.author; //PlatformAuthorLink
-        this.datetime = obj.datetime ?? obj.uploadDate ?? 0; //OffsetDateTime (Long)
-        this.url = obj.url ?? ""; //String
-    }
-}
-
-declare class PlatformContentDetails {
-    contentType: number;
-    constructor(type) {
-        this.contentType = type;
-    }
-}
-
-declare class PlatformNestedMediaContent extends PlatformContent {
-
-    contentUrl: string;
-    contentName: any;
-    contentDescription: any;
-    contentProvider: any;
-    contentThumbnails: Thumbnails;
-
-    constructor(obj) {
-        super(obj, 11);
-        obj = obj ?? {};
-        this.contentUrl = obj.contentUrl ?? "";
-        this.contentName = obj.contentName;
-        this.contentDescription = obj.contentDescription;
-        this.contentProvider = obj.contentProvider;
-        this.contentThumbnails = obj.contentThumbnails ?? new Thumbnails();
-    }
-}
-declare class PlatformLockedContent extends PlatformContent {
-
-    contentName: any;
-    contentThumbnails: Thumbnails;
-    unlockUrl: string;
-    lockDescription: any;
-
-    constructor(obj) {
-        super(obj, 70);
-        obj = obj ?? {};
-        this.contentName = obj.contentName;
-        this.contentThumbnails = obj.contentThumbnails ?? new Thumbnails();
-        this.unlockUrl = obj.unlockUrl ?? "";
-        this.lockDescription = obj.lockDescription;
-    }
-}
-
-//Playlist
-declare class PlatformPlaylist extends PlatformContent {
-    plugin_type: string;
-    videoCount: number;
-    thumbnail: any;
-    constructor(obj) {
-        super(obj, 4);
-        this.plugin_type = "PlatformPlaylist";
-        this.videoCount = obj.videoCount ?? 0;
-        this.thumbnail = obj.thumbnail;
-    }
-}
-
-declare class PlatformPlaylistDetails extends PlatformPlaylist {
-    plugin_type: string;
-    contents: any;
-    constructor(obj) {
-        super(obj);
-        this.plugin_type = "PlatformPlaylistDetails";
-        this.contents = obj.contents;
-    }
-}
-
-//Ratings
-declare class RatingLikes {
-
-    type: number;
-    likes: number;
-
-    constructor(likes) {
-        this.type = 1;
-        this.likes = likes;
-    }
-}
-
-declare class RatingLikesDislikes {
-    type: number;
-    likes: number;
-    dislikes: number;
-    constructor(likes: number, dislikes: number) {
-        this.type = 2;
-        this.likes = likes;
-        this.dislikes = dislikes;
-    }
-}
-
-declare class RatingScaler {
-
-    type: number;
-    value: any;
-    constructor(value) {
-        this.type = 3;
-        this.value = value;
-    }
-}
-
-declare class PlatformComment {
-
-    plugin_type: string;
-    contextUrl: string;
-    author: PlatformAuthorLink;
-    message: string;
-    rating: IRating;
-    date: number;
-    replyCount: number;
-    context: any;
-
-    constructor(obj) {
-        this.plugin_type = "Comment";
-        this.contextUrl = obj.contextUrl ?? "";
-        this.author = obj.author ?? new PlatformAuthorLink(null, "", "", null);
-        this.message = obj.message ?? "";
-        this.rating = obj.rating ?? new RatingLikes(0);
-        this.date = obj.date ?? 0;
-        this.replyCount = obj.replyCount ?? 0;
-        this.context = obj.context ?? {};
-    }
-}
-
-
-//Sources
-declare class VideoSourceDescriptor {
-
-    plugin_type: string;
-    isUnMuxed: boolean;
-    videoSources: any[];
-
-    constructor(obj) {
-        obj = obj ?? {};
-        this.plugin_type = "MuxVideoSourceDescriptor";
-        this.isUnMuxed = false;
-
-        if (obj.constructor === Array)
-            this.videoSources = obj;
-        else
-            this.videoSources = obj.videoSources ?? [];
-    }
-}
-
-declare class UnMuxVideoSourceDescriptor {
-
-    plugin_type: string;
-    isUnMuxed: boolean;
-    videoSources: any[];
-    audioSources: any[];
-
-    constructor(videoSourcesOrObj, audioSources) {
-        videoSourcesOrObj = videoSourcesOrObj ?? {};
-        this.plugin_type = "UnMuxVideoSourceDescriptor";
-        this.isUnMuxed = true;
-
-        if (videoSourcesOrObj.constructor === Array) {
-            this.videoSources = videoSourcesOrObj;
-            this.audioSources = audioSources;
-        }
-        else {
-            this.videoSources = videoSourcesOrObj.videoSources ?? [];
-            this.audioSources = videoSourcesOrObj.audioSources ?? [];
-        }
-    }
-}
-
-declare class VideoUrlSource {
-
-    plugin_type: string;
-    width: number;
-    height: number;
-    container: string;
-    codec: string;
-    name: string;
-    bitrate: number;
-    duration: number;
-    url: string;
-    requestModifier?: any;
-
-    constructor(obj) {
-        obj = obj ?? {};
-        this.plugin_type = "VideoUrlSource";
-        this.width = obj.width ?? 0;
-        this.height = obj.height ?? 0;
-        this.container = obj.container ?? "";
-        this.codec = obj.codec ?? "";
-        this.name = obj.name ?? "";
-        this.bitrate = obj.bitrate ?? 0;
-        this.duration = obj.duration ?? 0;
-        this.url = obj.url;
-        if (obj.requestModifier)
-            this.requestModifier = obj.requestModifier;
-    }
-}
-
-declare class VideoUrlRangeSource extends VideoUrlSource {
-
-    plugin_type: string;
-    itagId: any;
-    initStart: any;
-    initEnd: any;
-    indexStart: any;
-    indexEnd: any;
-
-    constructor(obj) {
-        super(obj);
-        this.plugin_type = "VideoUrlRangeSource";
-
-        this.itagId = obj.itagId ?? null;
-        this.initStart = obj.initStart ?? null;
-        this.initEnd = obj.initEnd ?? null;
-        this.indexStart = obj.indexStart ?? null;
-        this.indexEnd = obj.indexEnd ?? null;
-    }
-}
-
-declare class AudioUrlSource {
-
-    plugin_type: string;
-    name: string;
-    bitrate: number;
-    container: string;
-    codec: string;
-    duration: number;
-    url: string;
-    language: Language;
-    requestModifier?: any;
-
-    constructor(obj) {
-        obj = obj ?? {};
-        this.plugin_type = "AudioUrlSource";
-        this.name = obj.name ?? "";
-        this.bitrate = obj.bitrate ?? 0;
-        this.container = obj.container ?? "";
-        this.codec = obj.codec ?? "";
-        this.duration = obj.duration ?? 0;
-        this.url = obj.url;
-        this.language = obj.language ?? Language.UNKNOWN;
-        if (obj.requestModifier)
-            this.requestModifier = obj.requestModifier;
-    }
-}
-
-declare class AudioUrlWidevineSource extends AudioUrlSource {
-
-    plugin_type: string;
-    bearerToken: any;
-    licenseUri: any;
-
-    constructor(obj) {
-        super(obj);
-        this.plugin_type = "AudioUrlWidevineSource";
-
-        this.bearerToken = obj.bearerToken;
-        this.licenseUri = obj.licenseUri;
-    }
-}
-
-declare class AudioUrlRangeSource extends AudioUrlSource {
-
-    plugin_type: string;
-    itagId: any;
-    initStart: any;
-    initEnd: any;
-    indexStart: any;
-    indexEnd: any;
-    audioChannels: number;
-
-    constructor(obj) {
-        super(obj);
-        this.plugin_type = "AudioUrlRangeSource";
-
-        this.itagId = obj.itagId ?? null;
-        this.initStart = obj.initStart ?? null;
-        this.initEnd = obj.initEnd ?? null;
-        this.indexStart = obj.indexStart ?? null;
-        this.indexEnd = obj.indexEnd ?? null;
-        this.audioChannels = obj.audioChannels ?? 2;
-    }
-}
-
-declare class HLSSource {
-
-    plugin_type: string;
-    name: string;
-    duration: number;
-    url: string;
-    priority: boolean;
-    language?: any;
-    requestModifier?: any;
-
-    constructor(obj) {
-        obj = obj ?? {};
-        this.plugin_type = "HLSSource";
-        this.name = obj.name ?? "HLS";
-        this.duration = obj.duration ?? 0;
-        this.url = obj.url;
-        this.priority = obj.priority ?? false;
-        if (obj.language)
-            this.language = obj.language;
-        if (obj.requestModifier)
-            this.requestModifier = obj.requestModifier;
-    }
-}
-
-declare class DashSource {
-
-    plugin_type: string;
-    name: string;
-    duration: number;
-    url: string;
-    language?: any;
-    requestModifier?: any;
-
-    constructor(obj) {
-        obj = obj ?? {};
-        this.plugin_type = "DashSource";
-        this.name = obj.name ?? "Dash";
-        this.duration = obj.duration ?? 0;
-        this.url = obj.url;
-        if (obj.language)
-            this.language = obj.language;
-        if (obj.requestModifier)
-            this.requestModifier = obj.requestModifier;
-    }
-}
-
-declare class RequestModifier {
-
-    allowByteSkip: any;
-
-    constructor(obj) {
-        obj = obj ?? {};
-        this.allowByteSkip = obj.allowByteSkip; //Kinda deprecated.. wip
-    }
-}
-
-interface PluginSetting {
-    variable?: string;
-    name?: string;
-    description?: string;
-    type?: string;
-    default?: string;
-    options?: string[];
-}
-
-declare class Config {
-    name?: string;
-    platformUrl?: string;
-    description?: string;
-    author?: string;
-    authorUrl?: string;
-    sourceUrl?: string;
-    scriptUrl?: string;
-    repositoryUrl?: string;
-    version?: number;
-    iconUrl?: string;
-    id: string;
-    scriptSignature?: string;
-    scriptPublicKey?: string;
-    packages?: string[];
-    allowEval?: boolean;
-    allowUrls?: string[];
-    settings?: PluginSetting[];
-    allowAllHttpHeaderAccess?: boolean;
-}
-
-declare class ResultCapabilities {
-
-    types: string[];
-    sorts: string[];
-    filters?: FilterGroup[];
-
-    constructor(types: string[], sorts: string[], filters: FilterGroup[]) {
-        this.types = types ?? [];
-        this.sorts = sorts ?? [];
-        this.filters = filters ?? [];
-    }
-}
-
-declare class FilterGroup {
-
-    name: string;
-    filters: any[];
-    isMultiSelect: boolean;
-    id: any;
-
-    constructor(name: string, filters: string[], isMultiSelect: boolean, id: string) {
-        if (!name) throw new ScriptException("No name for filter group");
-        if (!filters) throw new ScriptException("No filter provided");
-
-        this.name = name
-        this.filters = filters
-        this.isMultiSelect = isMultiSelect;
-        this.id = id;
-    }
-}
-
-declare class FilterCapability {
-
-    name: string;
-    value: any;
-    id: any;
-
-    constructor(name: string, value: string, id: string) {
-        if (!name) throw new ScriptException("No name for filter");
-        if (!value) throw new ScriptException("No filter value");
-
-        this.name = name;
-        this.value = value;
-        this.id = id;
-    }
-}
-
-declare class PlatformAuthorLink {
-
-    id: PlatformID;
-    name: string;
-    url: string;
-    thumbnail: string;
-    subscribers?: any;
-    membershipUrl?: string | null;
-
-    constructor(id: PlatformID, name: string, url: string, thumbnail: string, subscribers?: any, membershipUrl?: string | null) {
-        this.id = id ?? PlatformID(); //PlatformID
-        this.name = name ?? ""; //string
-        this.url = url ?? ""; //string
-        this.thumbnail = thumbnail; //string
-        if (subscribers)
-            this.subscribers = subscribers;
-        if (membershipUrl)
-            this.membershipUrl = membershipUrl ?? null; //string (for backcompat)
-    }
-}
-
-declare class PlatformAuthorMembershipLink {
-
-    id: PlatformID;
-    name: string;
-    url: string;
-    thumbnail: string;
-    subscribers?: any;
-    membershipUrl?: string | null;
-
-    constructor(id: PlatformID, name: string, url: string, thumbnail: string, subscribers?: any, membershipUrl?: string | null) {
-        this.id = id ?? PlatformID(); //PlatformID
-        this.name = name ?? ""; //string
-        this.url = url ?? ""; //string
-        this.thumbnail = thumbnail; //string
-        if (subscribers)
-            this.subscribers = subscribers;
-        if (membershipUrl)
-            this.membershipUrl = membershipUrl ?? null; //string
-    }
-}
-
-declare interface PlatformVideoDef {
-    id: PlatformID,
-    name: string,
-    description: string,
-    thumbnails: Thumbnails,
-    author: PlatformAuthorLink,
-    uploadDate?: number,
-    datetime: number,
-    url: string,
-    duration?: number,
-    viewCount: number,
-    isLive: boolean,
-    shareUrl?: any
-}
-
-declare class PlatformVideo extends PlatformContent {
-
-    plugin_type: string;
-    shareUrl: any;
-    duration: number;
-    viewCount: number;
-    isLive: boolean;
-
-    constructor(obj: PlatformVideoDef) {
-        super(obj, 1);
-        obj = obj ?? {};
-        this.plugin_type = "PlatformVideo";
-        this.shareUrl = obj.shareUrl;
-
-        this.duration = obj.duration ?? -1; //Long
-        this.viewCount = obj.viewCount ?? -1; //Long
-
-        this.isLive = obj.isLive ?? false; //Boolean
-    }
-}
-
-declare interface PlatformVideoDetailsDef extends PlatformVideoDef {
-    description: string,
-    video: VideoSourceDescriptor,
-    dash: DashSource | null,
-    hls: HLSSource | null,
-    live: IVideoSource | null,
-    rating: RatingLikesDislikes,
-    subtitles: ISubtitleSource[]
-}
-
-
-interface ISubtitleSource {
-    name: String;
-    url: String?;
-    format: String?;
-    getSubtitles?: Function;
-}
-
-declare class PlatformVideoDetails extends PlatformVideo {
-
-    plugin_type: string;
-    description: string;
-    video: VideoSourceDescriptor;
-    dash: any;
-    hls: any;
-    live: any;
-    rating: any;
-    subtitles: any[];
-
-    constructor(obj: PlatformVideoDetailsDef) {
-        super(obj);
-        obj = obj ?? {};
-        this.plugin_type = "PlatformVideoDetails";
-
-        this.description = obj.description ?? "";//String
-        this.video = obj.video ?? {}; //VideoSourceDescriptor
-        this.dash = obj.dash ?? null; //DashSource, deprecated
-        this.hls = obj.hls ?? null; //HLSSource, deprecated
-        this.live = obj.live ?? null; //VideoSource
-
-        this.rating = obj.rating ?? null; //IRating
-        this.subtitles = obj.subtitles ?? [];
-    }
-}
-
-declare interface PlatformContentDef {
-    id: PlatformID,
-    name: string,
-    thumbnails: Thumbnails,
-    author: PlatformAuthorLink,
-    datetime: integer,
-    url: string
-}
-
-declare interface PlatformPostDef extends PlatformContentDef {
-    thumbnails: string[],
-    thumbnails: Thumbnails[],
-    images: string[],
-    description: string
-}
-
-class PlatformPost extends PlatformContent {
-    plugin_type: string;
-    thumbnails: Thumbnails[];
-    images: any[];
-    description: string;
-
-    constructor(obj) {
-        super(obj, 2);
-        obj = obj ?? {};
-        this.plugin_type = "PlatformPost";
-        this.thumbnails = obj.thumbnails ?? [];
-        this.images = obj.images ?? [];
-        this.description = obj.description ?? "";
-    }
-}
-
-class PlatformPostDetails extends PlatformPost {
-
-    plugin_type: string;
-    rating: any;
-    textType: number;
-    content: string;
-
-    constructor(obj) {
-        super(obj);
-        obj = obj ?? {};
-        this.plugin_type = "PlatformPostDetails";
-        this.rating = obj.rating ?? RatingLikes(-1);
-        this.textType = obj.textType ?? 0;
-        this.content = obj.content ?? "";
-    }
-}
-
-// Sources
-declare interface IVideoSourceDescriptor { }
-
-declare interface MuxVideoSourceDescriptorDef {
-    isUnMuxed: boolean,
-    videoSources: VideoSource[]
-}
-declare class MuxVideoSourceDescriptor implements IVideoSourceDescriptor {
-    constructor(obj: MuxVideoSourceDescriptorDef);
-}
-
-declare interface UnMuxVideoSourceDescriptorDef {
-    isUnMuxed: boolean,
-    videoSources: VideoSource[]
-}
-declare class UnMuxVideoSourceDescriptor implements IVideoSourceDescriptor {
-    constructor(videoSourcesOrObj: VideoSource[] | UnMuxVideoSourceDescriptorDef, audioSources?: AudioSource[]);
-}
-
-declare interface IVideoSource { }
-
-declare interface IAudioSource { }
-
-declare interface VideoUrlSourceDef extends IVideoSource {
-    width: number,
-    height: number,
-    container: string,
-    codec: string,
-    name: string,
-    bitrate: number,
-    duration: number,
-    url: string
-}
-declare class VideoUrlSource {
-    constructor(obj: VideoUrlSourceDef);
-}
-
-declare interface YTVideoSourceDef extends VideoUrlSourceDef {
-    itagId: number,
-    initStart: number,
-    initEnd: number,
-    indexStart: number,
-    indexEnd: number,
-}
-declare class YTVideoSource extends VideoUrlSource {
-    constructor(obj: YTVideoSourceDef);
-}
-
-declare interface AudioUrlSourceDef extends IAudioSource {
-    name: string,
-    bitrate: number,
-    container: string,
-    codecs: string,
-    duration: number,
-    url: string,
-    language: string
-}
-declare class AudioUrlSource {
-    constructor(obj: AudioUrlSourceDef);
-}
-
-declare interface YTAudioSourceDef extends AudioUrlSourceDef {
-    itagId: number,
-    initStart: number,
-    initEnd: number,
-    indexStart: number,
-    indexEnd: number,
-    audioChannels: number
-}
-declare class YTAudioSource extends AudioUrlSource {
-    constructor(obj: YTAudioSourceDef);
-}
-
-declare interface HLSSourceDef {
-    name: string,
-    duration: number,
-    url: string
-}
-declare class HLSSource implements IVideoSource {
-    constructor(obj: HLSSourceDef);
-}
-
-declare interface DashSourceDef {
-    name: string,
-    duration: number,
-    url: string
-}
-declare class DashSource implements IVideoSource {
-    constructor(obj: DashSourceDef);
-}
-
-// Channel
-declare interface PlatformChannelDef {
-    id: PlatformID,
-    name: string,
-    thumbnail: string,
-    banner: string,
-    subscribers: number,
-    description: string,
-    url: string,
-    links?: Map<string>
-}
-
-declare class PlatformChannel {
-
-    plugin_type: string;
-    id: string;
-    name: string;
-    thumbnail: string;
-    banner: string;
-    subscribers: number;
-    description: string;
-    url: string;
-    urlAlternatives: string[];
-    links: Map<string>
-
-    constructor(obj: PlatformChannelDef) {
-        obj = obj ?? {};
-        this.plugin_type = "PlatformChannel";
-        this.id = obj.id ?? ""; //string
-        this.name = obj.name ?? ""; //string
-        this.thumbnail = obj.thumbnail; //string
-        this.banner = obj.banner; //string
-        this.subscribers = obj.subscribers ?? 0; //integer
-        this.description = obj.description; //string
-        this.url = obj.url ?? ""; //string
-        this.urlAlternatives = obj.urlAlternatives ?? [];
-        this.links = obj.links ?? {} //Map<string,string>
-    }
-}
-
-// Ratings
-declare interface IRating {
-    type: number
-}
-declare class RatingLikes implements IRating {
-    constructor(likes: number);
-}
-declare class RatingLikesDislikes implements IRating {
-    constructor(likes: number, dislikes: number);
-}
-declare class RatingScaler implements IRating {
-    constructor(value: number);
-}
-
-declare interface CommentDef {
-    contextUrl: string,
-    author: PlatformAuthorLink,
-    message: string,
-    rating: IRating,
-    date: number,
-    replyCount: number,
-    context: any
-}
-
-//Temporary backwards compat
-declare class Comment extends PlatformComment {
-    constructor(obj: CommentDef) {
-        super(obj);
-    }
-}
-
-declare class PlaybackTracker {
-
-    nextRequest: number;
-
-    constructor(interval) {
-        this.nextRequest = interval ?? 10 * 1000;
-    }
-
-    setProgress(seconds: number): void {
-        throw new ScriptImplementationException("Missing required setProgress(seconds) on PlaybackTracker");
-    }
-}
-
-declare class LiveEventPager {
-
-    plugin_type: string;
-    _entries: { [key: string]: any };
-
-    constructor(results: LiveEvent[], hasMore: boolean, context: any) {
-        this.plugin_type = "LiveEventPager";
-        this.results = results ?? [];
-        this.hasMore = hasMore ?? false;
-        this.context = context ?? {};
-        this.nextRequest = 4000;
-    }
-
-    hasMorePagers(): boolean { return this.hasMore; }
-    nextPage(): LiveEventPager { return new Pager([], false, this.context) }; //Could be self
-
-    delete(name: string): void;
-    get(name: string): any;
-    getAll(name: string): any[];
-    has(name: string): boolean;
-    set(name: string, value: any): void;
-    forEach(callback: (value: any, name: string, pager: LiveEventPager) => void): void;
-    keys(): IterableIterator<string>;
-    values(): IterableIterator<any>;
-    entries(): IterableIterator<[string, any]>;
-    clear(): void;
-}
-
-
-declare class LiveEvent {
-
-    plugin_type: string;
-    id: string;
-    name: string;
-    description: string;
-    startDate: number;
-    endDate: number;
-    thumbnail: string;
-    state: number;
-    upcomingText: string;
-    viewCount: number;
-    tracker: PlaybackTracker;
-    rating: any;
-
-    constructor(type: string) {
-        this.type = type;
-    }
-}
-declare class LiveEventComment extends LiveEvent {
-    constructor(name: string, message: string, thumbnail?: string, colorName, badges) {
-        super(1);
-        this.name = name;
-        this.message = message;
-        this.thumbnail = thumbnail;
-        this.colorName = colorName;
-        this.badges = badges;
-    }
-}
-
-declare class LiveEventEmojis extends LiveEvent {
-    constructor(emojis) {
-        super(4);
-        this.emojis = emojis;
-    }
-}
-
-declare class LiveEventDonation extends LiveEvent {
-    constructor(amount: number, name: string, message: string, thumbnail?: string, expire?: any, colorDonation?: string) {
-        super(5);
-        this.amount = amount;
-        this.name = name;
-        this.message = message ?? "";
-        this.thumbnail = thumbnail;
-        this.expire = expire;
-        this.colorDonation = colorDonation;
-    }
-}
-
-declare class LiveEventViewCount extends LiveEvent {
-    constructor(viewCount: number) {
-        super(10);
-        this.viewCount = viewCount;
-    }
-}
-
-declare class LiveEventRaid extends LiveEvent {
-    constructor(targetUrl: string, targetName: string, targetThumbnail: string) {
-        super(100);
-        this.targetUrl = targetUrl;
-        this.targetName = targetName;
-        this.targetThumbnail = targetThumbnail;
-    }
-}
-
-//Pagers
-
-declare class ContentPager {
-    constructor(results: [], hasMore: boolean, context: any) {
-        this.plugin_type = "ContentPager";
-        this.results = results ?? [];
-        this.hasMore = hasMore ?? false;
-        this.context = context ?? {};
-    }
-
-    hasMorePagers() { return this.hasMore; }
-    nextPage() { return new ContentPager([], false, this.context) }
-}
-
-declare class VideoPager {
-
-    hasMore: boolean;
-    context: any
-
-    constructor(results: PlatformVideo[], hasMore?: boolean, context?: any) {
-        this.plugin_type = "VideoPager";
-        this.results = results ?? [];
-        this.hasMore = hasMore ?? false;
-        this.context = context ?? {};
-    }
-    hasMorePagers(): boolean { return this.hasMore; }
-    nextPage(): VideoPager { return new VideoPager([], false, this.context) }
-}
-
-declare class ChannelPager {
-
-    hasMore: boolean;
-    context: any
-
-    constructor(results: PlatformVideo[], hasMore: boolean, context: any) {
-        this.plugin_type = "ChannelPager";
-        this.results = results ?? [];
-        this.hasMore = hasMore ?? false;
-        this.context = context ?? {};
-    }
-
-    hasMorePagers(): boolean { return this.hasMore; }
-    nextPage(): ChannelPager { return new Pager([], false, this.context) }
-}
-
-
-declare class PlaylistPager {
-
-    hasMore: boolean;
-    context: any
-
-    constructor(results: PlatformPlaylist[], hasMore?: boolean, context?: any) {
-        this.plugin_type = "PlaylistPager";
-        this.results = results ?? [];
-        this.hasMore = hasMore ?? false;
-        this.context = context ?? {};
-    }
-
-    hasMorePagers() { return this.hasMore; }
-    nextPage() { return new Pager([], false, this.context) }
-}
-
-
-declare class CommentPager {
-    context: any
-
-    constructor(results: PlatformComment[], hasMore: boolean, context: any) {
-        this.plugin_type = "CommentPager";
-        this.results = results ?? [];
-        this.hasMore = hasMore ?? false;
-        this.context = context ?? {};
-    }
-    hasMorePagers(): boolean { return this.hasMore; }
-    nextPage(): CommentPager { return new Pager([], false, this.context) }
-}
-
-declare interface Map<T> {
-    [Key: string]: T;
-}
-
-function throwException(ttype: string, message: string): void {
-    throw new Error("V8EXCEPTION:" + type + "-" + message);
-}
-
-let plugin = {
-    config: {},
-    settings: {}
-};
-
-// Plugin configuration
-// To override by plugin
-interface Source {
-
-    getHome(): VideoPager;
-
-    enable(conf: Config, settings: Map<string>, saveStateStr: string): void;
-
-    setSettings(settings: any): void;
-
-    disable(): void;
-
-    searchSuggestions(query: string): string[];
-    search(query: string, type: string, order: string, filters: FilterGroup[]): VideoPager;
-    getSearchCapabilities(): ResultCapabilities;
-
-    // Optional
-    searchChannelVideos?(channelUrl: string, query: string, type: string, order: string, filters: FilterGroup[]): VideoPager;
-    getSearchChannelVideoCapabilities?(): ResultCapabilities;
-
-    isChannelUrl(url: string): boolean;
-    getChannel(url: string): PlatformChannel | null;
-
-    getChannelVideos(url: string, type: string, order: string, filters: FilterGroup[]): VideoPager;
-    getChannelCapabilities(): ResultCapabilities;
-    getSearchChannelContentsCapabilities(): ResultCapabilities;
-    getPeekChannelTypes(): string[];
-    peekChannelContents(url, type): PlatformVideo[]
-
-    isVideoDetailsUrl(url: string): boolean;
-    getVideoDetails(url: string): PlatformVideoDetails;
-
-    // Optional
-    getComments?(url: string): CommentPager;
-    getSubComments?(comment: Comment): CommentPager;
-
-    // Optional
-    getUserSubscriptions?(): string[];
-    getUserPlaylists?(): string[];
-
-    // Optional
-    isPlaylistUrl?(url: string): boolean;
-
-    searchPlaylists(query, type, order, filters);
-
-    getPlaylist?(url: string): PlatformPlaylistDetails;
-
-    isContentDetailsUrl(url: string): boolean;
-
-    getChannelContents(url: string, type?: string, order?: string, filters?: Map<String, List<String>>): VideoPager;
-
-    searchChannels(query: string): ChannelPager;
-
-    getContentDetails(url: string): PlatformVideoDetails;
-
-    getComments(url: string): CommentPager;
-
-    getSubComments(comment: PlatformComment): CommentPager;
-
-    getChannelPlaylists(url: string): PlaylistPager;
-
-    searchChannelContents(channelUrl: string, query: string, type: string, order: string, filters: FilterGroup[]): VideoPager;
-
-    saveState(): void;
-
-    getChannelTemplateByClaimMap(): any;
-}
-
-
-function parseSettings(settings) {
-    if (!settings)
-        return {};
-    let newSettings = {};
-    for (let key in settings) {
-        if (typeof settings[key] == "string")
-            newSettings[key] = JSON.parse(settings[key]);
-        else
-            newSettings[key] = settings[key];
-    }
-    return newSettings;
-}
-
-function log(obj: string | object) {
-    if (obj) {
-        console.log(obj);
-        if (typeof obj == "string")
-            bridge.log(obj);
-        else
-            bridge.log(JSON.stringify(obj, null, 4));
-    }
-}
-
-function encodePathSegment(segment) {
-    return encodeURIComponent(segment).replace(/[!'()*]/g, function (c) {
-        return '%' + c.charCodeAt(0).toString(16);
-    });
-}
-
-class URLSearchParams {
-    constructor(init) {
-        this._entries = {};
-        if (typeof init === 'string') {
-            if (init !== '') {
-                init = init.replace(/^\?/, '');
-                const attributes = init.split('&');
-                let attribute;
-                for (let i = 0; i < attributes.length; i++) {
-                    attribute = attributes[i].split('=');
-                    this.append(decodeURIComponent(attribute[0]), (attribute.length > 1) ? decodeURIComponent(attribute[1]) : '');
-                }
-            }
-        }
-        else if (init instanceof URLSearchParams) {
-            init.forEach((value, name) => {
-                this.append(value, name);
-            });
-        }
-    }
-    append(name, value) {
-        value = value.toString();
-        if (name in this._entries) {
-            this._entries[name].push(value);
-        }
-        else {
-            this._entries[name] = [value];
-        }
-    }
-    delete(name) {
-        delete this._entries[name];
-    }
-    get(name) {
-        return (name in this._entries) ? this._entries[name][0] : null;
-    }
-    getAll(name) {
-        return (name in this._entries) ? this._entries[name].slice(0) : [];
-    }
-    has(name) {
-        return (name in this._entries);
-    }
-    set(name, value) {
-        this._entries[name] = [value.toString()];
-    }
-    forEach(callback) {
-        let entries;
-        for (let name in this._entries) {
-            if (this._entries.hasOwnProperty(name)) {
-                entries = this._entries[name];
-                for (let i = 0; i < entries.length; i++) {
-                    callback.call(this, entries[i], name, this);
-                }
-            }
-        }
-    }
-    keys() {
-        const items = [];
-        this.forEach((value, name) => { items.push(name); });
-        return createIterator(items);
-    }
-    values() {
-        const items = [];
-        this.forEach((value) => { items.push(value); });
-        return createIterator(items);
-    }
-    entries() {
-        const items = [];
-        this.forEach((value, name) => { items.push([value, name]); });
-        return createIterator(items);
-    }
-    toString() {
-        let searchString = '';
-        this.forEach((value, name) => {
-            if (searchString.length > 0)
-                searchString += '&';
-            searchString += encodeURIComponent(name) + '=' + encodeURIComponent(value);
-        });
-        return searchString;
-    }
-}
-
-const source: Source;
-
-declare var IS_TESTING: boolean;
-
-let Type = {
-    Source: {
-        Dash: "DASH",
-        HLS: "HLS",
-        STATIC: "Static"
-    },
-    Feed: {
-        Videos: "VIDEOS",
-        Streams: "STREAMS",
-        Mixed: "MIXED",
-        Live: "LIVE",
-        Subscriptions: "SUBSCRIPTIONS"
-    },
-    Order: {
-        Chronological: "CHRONOLOGICAL"
-    },
-    Date: {
-        LastHour: "LAST_HOUR",
-        Today: "TODAY",
-        LastWeek: "LAST_WEEK",
-        LastMonth: "LAST_MONTH",
-        LastYear: "LAST_YEAR"
-    },
-    Duration: {
-        Short: "SHORT",
-        Medium: "MEDIUM",
-        Long: "LONG"
-    },
-    Text: {
-        RAW: 0,
-        HTML: 1,
-        MARKUP: 2
-    },
-    Chapter: {
-        NORMAL: 0,
-
-        SKIPPABLE: 5,
-        SKIP: 6,
-        SKIPONCE: 7
-    }
-};
-
-let Language = {
-    UNKNOWN: "Unknown",
-    ARABIC: "ar",
-    SPANISH: "es",
-    FRENCH: "fr",
-    HINDI: "hi",
-    INDONESIAN: "id",
-    KOREAN: "ko",
-    PORTUGUESE: "pt",
-    PORTBRAZIL: "pt",
-    RUSSIAN: "ru",
-    THAI: "th",
-    TURKISH: "tr",
-    VIETNAMESE: "vi",
-    ENGLISH: "en"
-}
-
-
-interface HttpResponse {
-    isOk(): boolean,
-    body: string,
-    code: number
-}
-
-//Package Bridge (variable: bridge)
-let bridge = {
-    /**
-    * @param {String} label
-    * @param {String} data
-    * @return {Unit}
-    **/
-    devSubmit: function (label: string, data: string): Unit { },
-
-    /**
-    * @return {Boolean}
-    **/
-    isLoggedIn: function (): boolean { },
-
-    /**
-    * @param {String} str
-    * @return {Unit}
-    **/
-    log: function (str: string): Unit { },
-
-    /**
-    * @param {String} str
-    * @return {Unit}
-    **/
-    throwTest: function (str: string): Unit { },
-
-    /**
-    * @param {String} str
-    * @return {Unit}
-    **/
-    toast: function (str: string): Unit { },
-
-}
-
-//Package Http (variable: http)
-
-interface IHttp {
-    /**
-    * @param {String} url
-    * @param {Map} headers
-    * @param {Boolean} useAuth
-    * @return {BridgeHttpResponse}
-    **/
-    GET(url: string, headers: Map<string, string>, useAuth?: boolean): BridgeHttpResponse;
-
-    /**
-    * @param {String} url
-    * @param {String} body
-    * @param {Map} headers
-    * @param {Boolean} useAuth
-    * @return {BridgeHttpResponse}
-    **/
-    POST(url: string, body: string, headers: Map<string, string>, useAuth: boolean): BridgeHttpResponse;
-
-    /**
-    * @return {BatchBuilder}
-    **/
-    batch(): BatchBuilder;
-
-    /**
-    * @param {Boolean} withAuth
-    * @return {PackageHttpClient}
-    **/
-    getDefaultClient(withAuth: boolean): PackageHttpClient;
-
-    /**
-    * @param {Boolean} withAuth
-    * @return {PackageHttpClient}
-    **/
-    newClient(withAuth: boolean): PackageHttpClient;
-
-    /**
-    * @param {String} method
-    * @param {String} url
-    * @param {Map} headers
-    * @param {Boolean} useAuth
-    * @return {BridgeHttpResponse}
-    **/
-    request(method: string, url: string, headers: Map<string, string>, useAuth: boolean): BridgeHttpResponse;
-
-    /**
-    * @param {String} method
-    * @param {String} url
-    * @param {String} body
-    * @param {Map} headers
-    * @param {Boolean} useAuth
-    * @return {BridgeHttpResponse}
-    **/
-    requestWithBody(method: string, url: string, body: string, headers: Map<string, string>, useAuth: boolean): BridgeHttpResponse;
-
-    /**
-    * @param {String} url
-    * @param {Map} headers
-    * @param {Boolean} useAuth
-    * @return {SocketResult}
-    **/
-    socket(url: string, headers: Map<string, string>, useAuth: boolean): SocketResult;
-
-    /**
-    * @param {Map} headers
-    * @return {void}
-    **/
-    setDefaultHeaders(headers: Map<string, string>): void
-
-    /**
-     * @param {Boolean} allow
-     * @return {void}
-     * */
-    setDoAllowNewCookies(allow: boolean): void
-}
-
-
-let http: IHttp
-
-
-interface IPager<T> {
-    hasMorePages(): Boolean;
-    nextPage();
-    getResults(): List<T>;
-}
\ No newline at end of file
+//Reference Scriptfile
+//Intended exclusively for auto-complete in your IDE, not for execution
+
+declare class ScriptException extends Error {
+  plugin_type: string;
+  msg: string;
+  message: string;
+
+  //If only one parameter is provided, acts as msg
+  constructor(type: string, msg: string) {
+    if (arguments.length == 1) {
+      super(arguments[0]);
+      this.plugin_type = 'ScriptException';
+      this.message = arguments[0];
+    } else {
+      super(msg);
+      this.plugin_type = type ?? ''; //string
+      this.msg = msg ?? ''; //string
+    }
+  }
+}
+
+declare class LoginRequiredException extends ScriptException {
+  constructor(msg: string) {
+    super('ScriptLoginRequiredException', msg);
+  }
+}
+
+//Alias
+declare class ScriptLoginRequiredException extends ScriptException {
+  constructor(msg: string) {
+    super('ScriptLoginRequiredException', msg);
+  }
+}
+
+declare class CaptchaRequiredException extends ScriptException {
+  plugin_type: string;
+  url: string;
+  body: any;
+
+  constructor(url: string, body: string) {
+    super(
+      JSON.stringify({ plugin_type: 'CaptchaRequiredException', url, body }),
+    );
+    this.plugin_type = 'CaptchaRequiredException';
+    this.url = url;
+    this.body = body;
+  }
+}
+
+declare class CriticalException extends ScriptException {
+  constructor(msg: string) {
+    super('CriticalException', msg);
+  }
+}
+
+declare class UnavailableException extends ScriptException {
+  constructor(msg: string) {
+    super('UnavailableException', msg);
+  }
+}
+
+declare class AgeException extends ScriptException {
+  constructor(msg: string) {
+    super('AgeException', msg);
+  }
+}
+
+declare class TimeoutException extends ScriptException {
+  plugin_type: string;
+
+  constructor(msg: string) {
+    super(msg);
+    this.plugin_type = 'ScriptTimeoutException';
+  }
+}
+
+declare class ScriptImplementationException extends ScriptException {
+  plugin_type: string;
+
+  constructor(msg: string) {
+    super(msg);
+    this.plugin_type = 'ScriptImplementationException';
+  }
+}
+
+declare class Thumbnails {
+  constructor(thumbnails: Thumbnail[]) {
+    this.sources = thumbnails ?? []; // Thumbnail[]
+  }
+}
+declare class Thumbnail {
+  constructor(url: string, quality: number) {
+    this.url = url ?? ''; //string
+    this.quality = quality ?? 0; //integer
+  }
+}
+
+declare class PlatformID {
+  constructor(
+    platform: string,
+    id: string,
+    pluginId: string,
+    claimType?: number = 0,
+    claimFieldType?: number = -1,
+  ) {
+    this.platform = platform ?? ''; //string
+    this.pluginId = pluginId; //string
+    this.value = id; //string
+    this.claimType = claimType ?? 0; //int
+    this.claimFieldType = claimFieldType ?? -1; //int
+  }
+}
+
+declare class PlatformContent {
+  contentType: number;
+  id: PlatformID;
+  name: string;
+  thumbnails: Thumbnail[];
+  author: PlatformAuthorLink;
+  datetime: number;
+  url: string;
+
+  constructor(obj: any, type: number) {
+    this.contentType = type;
+    obj = obj ?? {};
+    this.id = obj.id ?? PlatformID(); //PlatformID
+    this.name = obj.name ?? ''; //string
+    this.thumbnails = obj.thumbnails; //Thumbnail[]
+    this.author = obj.author; //PlatformAuthorLink
+    this.datetime = obj.datetime ?? obj.uploadDate ?? 0; //OffsetDateTime (Long)
+    this.url = obj.url ?? ''; //String
+  }
+}
+
+declare class PlatformContentDetails {
+  contentType: number;
+  constructor(type) {
+    this.contentType = type;
+  }
+}
+
+declare class PlatformNestedMediaContent extends PlatformContent {
+  contentUrl: string;
+  contentName: any;
+  contentDescription: any;
+  contentProvider: any;
+  contentThumbnails: Thumbnails;
+
+  constructor(obj) {
+    super(obj, 11);
+    obj = obj ?? {};
+    this.contentUrl = obj.contentUrl ?? '';
+    this.contentName = obj.contentName;
+    this.contentDescription = obj.contentDescription;
+    this.contentProvider = obj.contentProvider;
+    this.contentThumbnails = obj.contentThumbnails ?? new Thumbnails();
+  }
+}
+declare class PlatformLockedContent extends PlatformContent {
+  contentName: any;
+  contentThumbnails: Thumbnails;
+  unlockUrl: string;
+  lockDescription: any;
+
+  constructor(obj) {
+    super(obj, 70);
+    obj = obj ?? {};
+    this.contentName = obj.contentName;
+    this.contentThumbnails = obj.contentThumbnails ?? new Thumbnails();
+    this.unlockUrl = obj.unlockUrl ?? '';
+    this.lockDescription = obj.lockDescription;
+  }
+}
+
+//Playlist
+declare class PlatformPlaylist extends PlatformContent {
+  plugin_type: string;
+  videoCount: number;
+  thumbnail: any;
+  constructor(obj) {
+    super(obj, 4);
+    this.plugin_type = 'PlatformPlaylist';
+    this.videoCount = obj.videoCount ?? 0;
+    this.thumbnail = obj.thumbnail;
+  }
+}
+
+declare class PlatformPlaylistDetails extends PlatformPlaylist {
+  plugin_type: string;
+  contents: any;
+  constructor(obj) {
+    super(obj);
+    this.plugin_type = 'PlatformPlaylistDetails';
+    this.contents = obj.contents;
+  }
+}
+
+//Ratings
+declare class RatingLikes {
+  type: number;
+  likes: number;
+
+  constructor(likes) {
+    this.type = 1;
+    this.likes = likes;
+  }
+}
+
+declare class RatingLikesDislikes {
+  type: number;
+  likes: number;
+  dislikes: number;
+  constructor(likes: number, dislikes: number) {
+    this.type = 2;
+    this.likes = likes;
+    this.dislikes = dislikes;
+  }
+}
+
+declare class RatingScaler {
+  type: number;
+  value: any;
+  constructor(value) {
+    this.type = 3;
+    this.value = value;
+  }
+}
+
+declare class PlatformComment {
+  plugin_type: string;
+  contextUrl: string;
+  author: PlatformAuthorLink;
+  message: string;
+  rating: IRating;
+  date: number;
+  replyCount: number;
+  context: any;
+
+  constructor(obj) {
+    this.plugin_type = 'Comment';
+    this.contextUrl = obj.contextUrl ?? '';
+    this.author = obj.author ?? new PlatformAuthorLink(null, '', '', null);
+    this.message = obj.message ?? '';
+    this.rating = obj.rating ?? new RatingLikes(0);
+    this.date = obj.date ?? 0;
+    this.replyCount = obj.replyCount ?? 0;
+    this.context = obj.context ?? {};
+  }
+}
+
+//Sources
+declare class VideoSourceDescriptor {
+  plugin_type: string;
+  isUnMuxed: boolean;
+  videoSources: any[];
+
+  constructor(obj) {
+    obj = obj ?? {};
+    this.plugin_type = 'MuxVideoSourceDescriptor';
+    this.isUnMuxed = false;
+
+    if (obj.constructor === Array) this.videoSources = obj;
+    else this.videoSources = obj.videoSources ?? [];
+  }
+}
+
+declare class UnMuxVideoSourceDescriptor {
+  plugin_type: string;
+  isUnMuxed: boolean;
+  videoSources: any[];
+  audioSources: any[];
+
+  constructor(videoSourcesOrObj, audioSources) {
+    videoSourcesOrObj = videoSourcesOrObj ?? {};
+    this.plugin_type = 'UnMuxVideoSourceDescriptor';
+    this.isUnMuxed = true;
+
+    if (videoSourcesOrObj.constructor === Array) {
+      this.videoSources = videoSourcesOrObj;
+      this.audioSources = audioSources;
+    } else {
+      this.videoSources = videoSourcesOrObj.videoSources ?? [];
+      this.audioSources = videoSourcesOrObj.audioSources ?? [];
+    }
+  }
+}
+
+declare class VideoUrlSource {
+  plugin_type: string;
+  width: number;
+  height: number;
+  container: string;
+  codec: string;
+  name: string;
+  bitrate: number;
+  duration: number;
+  url: string;
+  requestModifier?: any;
+
+  constructor(obj) {
+    obj = obj ?? {};
+    this.plugin_type = 'VideoUrlSource';
+    this.width = obj.width ?? 0;
+    this.height = obj.height ?? 0;
+    this.container = obj.container ?? '';
+    this.codec = obj.codec ?? '';
+    this.name = obj.name ?? '';
+    this.bitrate = obj.bitrate ?? 0;
+    this.duration = obj.duration ?? 0;
+    this.url = obj.url;
+    if (obj.requestModifier) this.requestModifier = obj.requestModifier;
+  }
+}
+
+declare class VideoUrlRangeSource extends VideoUrlSource {
+  plugin_type: string;
+  itagId: any;
+  initStart: any;
+  initEnd: any;
+  indexStart: any;
+  indexEnd: any;
+
+  constructor(obj) {
+    super(obj);
+    this.plugin_type = 'VideoUrlRangeSource';
+
+    this.itagId = obj.itagId ?? null;
+    this.initStart = obj.initStart ?? null;
+    this.initEnd = obj.initEnd ?? null;
+    this.indexStart = obj.indexStart ?? null;
+    this.indexEnd = obj.indexEnd ?? null;
+  }
+}
+
+declare class AudioUrlSource {
+  plugin_type: string;
+  name: string;
+  bitrate: number;
+  container: string;
+  codec: string;
+  duration: number;
+  url: string;
+  language: Language;
+  requestModifier?: any;
+
+  constructor(obj) {
+    obj = obj ?? {};
+    this.plugin_type = 'AudioUrlSource';
+    this.name = obj.name ?? '';
+    this.bitrate = obj.bitrate ?? 0;
+    this.container = obj.container ?? '';
+    this.codec = obj.codec ?? '';
+    this.duration = obj.duration ?? 0;
+    this.url = obj.url;
+    this.language = obj.language ?? Language.UNKNOWN;
+    if (obj.requestModifier) this.requestModifier = obj.requestModifier;
+  }
+}
+
+declare class AudioUrlWidevineSource extends AudioUrlSource {
+  plugin_type: string;
+  bearerToken: any;
+  licenseUri: any;
+
+  constructor(obj) {
+    super(obj);
+    this.plugin_type = 'AudioUrlWidevineSource';
+
+    this.bearerToken = obj.bearerToken;
+    this.licenseUri = obj.licenseUri;
+  }
+}
+
+declare class AudioUrlRangeSource extends AudioUrlSource {
+  plugin_type: string;
+  itagId: any;
+  initStart: any;
+  initEnd: any;
+  indexStart: any;
+  indexEnd: any;
+  audioChannels: number;
+
+  constructor(obj) {
+    super(obj);
+    this.plugin_type = 'AudioUrlRangeSource';
+
+    this.itagId = obj.itagId ?? null;
+    this.initStart = obj.initStart ?? null;
+    this.initEnd = obj.initEnd ?? null;
+    this.indexStart = obj.indexStart ?? null;
+    this.indexEnd = obj.indexEnd ?? null;
+    this.audioChannels = obj.audioChannels ?? 2;
+  }
+}
+
+declare class HLSSource {
+  plugin_type: string;
+  name: string;
+  duration: number;
+  url: string;
+  priority: boolean;
+  language?: any;
+  requestModifier?: any;
+
+  constructor(obj) {
+    obj = obj ?? {};
+    this.plugin_type = 'HLSSource';
+    this.name = obj.name ?? 'HLS';
+    this.duration = obj.duration ?? 0;
+    this.url = obj.url;
+    this.priority = obj.priority ?? false;
+    if (obj.language) this.language = obj.language;
+    if (obj.requestModifier) this.requestModifier = obj.requestModifier;
+  }
+}
+
+declare class DashSource {
+  plugin_type: string;
+  name: string;
+  duration: number;
+  url: string;
+  language?: any;
+  requestModifier?: any;
+
+  constructor(obj) {
+    obj = obj ?? {};
+    this.plugin_type = 'DashSource';
+    this.name = obj.name ?? 'Dash';
+    this.duration = obj.duration ?? 0;
+    this.url = obj.url;
+    if (obj.language) this.language = obj.language;
+    if (obj.requestModifier) this.requestModifier = obj.requestModifier;
+  }
+}
+
+declare class RequestModifier {
+  allowByteSkip: any;
+
+  constructor(obj) {
+    obj = obj ?? {};
+    this.allowByteSkip = obj.allowByteSkip; //Kinda deprecated.. wip
+  }
+}
+
+interface PluginSetting {
+  variable?: string;
+  name?: string;
+  description?: string;
+  type?: string;
+  default?: string;
+  options?: string[];
+}
+
+declare class Config {
+  name?: string;
+  platformUrl?: string;
+  description?: string;
+  author?: string;
+  authorUrl?: string;
+  sourceUrl?: string;
+  scriptUrl?: string;
+  repositoryUrl?: string;
+  version?: number;
+  iconUrl?: string;
+  id: string;
+  scriptSignature?: string;
+  scriptPublicKey?: string;
+  packages?: string[];
+  allowEval?: boolean;
+  allowUrls?: string[];
+  settings?: PluginSetting[];
+  allowAllHttpHeaderAccess?: boolean;
+}
+
+declare class ResultCapabilities {
+  types: string[];
+  sorts: string[];
+  filters?: FilterGroup[];
+
+  constructor(types: string[], sorts: string[], filters: FilterGroup[]) {
+    this.types = types ?? [];
+    this.sorts = sorts ?? [];
+    this.filters = filters ?? [];
+  }
+}
+
+declare class FilterGroup {
+  name: string;
+  filters: any[];
+  isMultiSelect: boolean;
+  id: any;
+
+  constructor(
+    name: string,
+    filters: string[],
+    isMultiSelect: boolean,
+    id: string,
+  ) {
+    if (!name) throw new ScriptException('No name for filter group');
+    if (!filters) throw new ScriptException('No filter provided');
+
+    this.name = name;
+    this.filters = filters;
+    this.isMultiSelect = isMultiSelect;
+    this.id = id;
+  }
+}
+
+declare class FilterCapability {
+  name: string;
+  value: any;
+  id: any;
+
+  constructor(name: string, value: string, id: string) {
+    if (!name) throw new ScriptException('No name for filter');
+    if (!value) throw new ScriptException('No filter value');
+
+    this.name = name;
+    this.value = value;
+    this.id = id;
+  }
+}
+
+declare class PlatformAuthorLink {
+  id: PlatformID;
+  name: string;
+  url: string;
+  thumbnail: string;
+  subscribers?: any;
+  membershipUrl?: string | null;
+
+  constructor(
+    id: PlatformID,
+    name: string,
+    url: string,
+    thumbnail: string,
+    subscribers?: any,
+    membershipUrl?: string | null,
+  ) {
+    this.id = id ?? PlatformID(); //PlatformID
+    this.name = name ?? ''; //string
+    this.url = url ?? ''; //string
+    this.thumbnail = thumbnail; //string
+    if (subscribers) this.subscribers = subscribers;
+    if (membershipUrl) this.membershipUrl = membershipUrl ?? null; //string (for backcompat)
+  }
+}
+
+declare class PlatformAuthorMembershipLink {
+  id: PlatformID;
+  name: string;
+  url: string;
+  thumbnail: string;
+  subscribers?: any;
+  membershipUrl?: string | null;
+
+  constructor(
+    id: PlatformID,
+    name: string,
+    url: string,
+    thumbnail: string,
+    subscribers?: any,
+    membershipUrl?: string | null,
+  ) {
+    this.id = id ?? PlatformID(); //PlatformID
+    this.name = name ?? ''; //string
+    this.url = url ?? ''; //string
+    this.thumbnail = thumbnail; //string
+    if (subscribers) this.subscribers = subscribers;
+    if (membershipUrl) this.membershipUrl = membershipUrl ?? null; //string
+  }
+}
+
+declare interface PlatformVideoDef {
+  id: PlatformID;
+  name: string;
+  description: string;
+  thumbnails: Thumbnails;
+  author: PlatformAuthorLink;
+  uploadDate?: number;
+  datetime: number;
+  url: string;
+  duration?: number;
+  viewCount: number;
+  isLive: boolean;
+  shareUrl?: any;
+}
+
+declare class PlatformVideo extends PlatformContent {
+  plugin_type: string;
+  shareUrl: any;
+  duration: number;
+  viewCount: number;
+  isLive: boolean;
+
+  constructor(obj: PlatformVideoDef) {
+    super(obj, 1);
+    obj = obj ?? {};
+    this.plugin_type = 'PlatformVideo';
+    this.shareUrl = obj.shareUrl;
+
+    this.duration = obj.duration ?? -1; //Long
+    this.viewCount = obj.viewCount ?? -1; //Long
+
+    this.isLive = obj.isLive ?? false; //Boolean
+  }
+}
+
+declare interface PlatformVideoDetailsDef extends PlatformVideoDef {
+  description: string;
+  video: VideoSourceDescriptor;
+  dash: DashSource | null;
+  hls: HLSSource | null;
+  live: IVideoSource | null;
+  rating: RatingLikesDislikes;
+  subtitles: ISubtitleSource[];
+}
+
+interface ISubtitleSource {
+  name: String;
+  url: String?;
+  format: String?;
+  getSubtitles?: Function;
+}
+
+declare class PlatformVideoDetails extends PlatformVideo {
+  plugin_type: string;
+  description: string;
+  video: VideoSourceDescriptor;
+  dash: any;
+  hls: any;
+  live: any;
+  rating: any;
+  subtitles: any[];
+
+  constructor(obj: PlatformVideoDetailsDef) {
+    super(obj);
+    obj = obj ?? {};
+    this.plugin_type = 'PlatformVideoDetails';
+
+    this.description = obj.description ?? ''; //String
+    this.video = obj.video ?? {}; //VideoSourceDescriptor
+    this.dash = obj.dash ?? null; //DashSource, deprecated
+    this.hls = obj.hls ?? null; //HLSSource, deprecated
+    this.live = obj.live ?? null; //VideoSource
+
+    this.rating = obj.rating ?? null; //IRating
+    this.subtitles = obj.subtitles ?? [];
+  }
+}
+
+declare interface PlatformContentDef {
+  id: PlatformID;
+  name: string;
+  thumbnails: Thumbnails;
+  author: PlatformAuthorLink;
+  datetime: integer;
+  url: string;
+}
+
+declare interface PlatformPostDef extends PlatformContentDef {
+  thumbnails: string[];
+  thumbnails: Thumbnails[];
+  images: string[];
+  description: string;
+}
+
+class PlatformPost extends PlatformContent {
+  plugin_type: string;
+  thumbnails: Thumbnails[];
+  images: any[];
+  description: string;
+
+  constructor(obj) {
+    super(obj, 2);
+    obj = obj ?? {};
+    this.plugin_type = 'PlatformPost';
+    this.thumbnails = obj.thumbnails ?? [];
+    this.images = obj.images ?? [];
+    this.description = obj.description ?? '';
+  }
+}
+
+class PlatformPostDetails extends PlatformPost {
+  plugin_type: string;
+  rating: any;
+  textType: number;
+  content: string;
+
+  constructor(obj) {
+    super(obj);
+    obj = obj ?? {};
+    this.plugin_type = 'PlatformPostDetails';
+    this.rating = obj.rating ?? RatingLikes(-1);
+    this.textType = obj.textType ?? 0;
+    this.content = obj.content ?? '';
+  }
+}
+
+// Sources
+declare interface IVideoSourceDescriptor {}
+
+declare interface MuxVideoSourceDescriptorDef {
+  isUnMuxed: boolean;
+  videoSources: VideoSource[];
+}
+declare class MuxVideoSourceDescriptor implements IVideoSourceDescriptor {
+  constructor(obj: MuxVideoSourceDescriptorDef);
+}
+
+declare interface UnMuxVideoSourceDescriptorDef {
+  isUnMuxed: boolean;
+  videoSources: VideoSource[];
+}
+declare class UnMuxVideoSourceDescriptor implements IVideoSourceDescriptor {
+  constructor(
+    videoSourcesOrObj: VideoSource[] | UnMuxVideoSourceDescriptorDef,
+    audioSources?: AudioSource[],
+  );
+}
+
+declare interface IVideoSource {}
+
+declare interface IAudioSource {}
+
+declare interface VideoUrlSourceDef extends IVideoSource {
+  width: number;
+  height: number;
+  container: string;
+  codec: string;
+  name: string;
+  bitrate: number;
+  duration: number;
+  url: string;
+}
+declare class VideoUrlSource {
+  constructor(obj: VideoUrlSourceDef);
+}
+
+declare interface YTVideoSourceDef extends VideoUrlSourceDef {
+  itagId: number;
+  initStart: number;
+  initEnd: number;
+  indexStart: number;
+  indexEnd: number;
+}
+declare class YTVideoSource extends VideoUrlSource {
+  constructor(obj: YTVideoSourceDef);
+}
+
+declare interface AudioUrlSourceDef extends IAudioSource {
+  name: string;
+  bitrate: number;
+  container: string;
+  codecs: string;
+  duration: number;
+  url: string;
+  language: string;
+}
+declare class AudioUrlSource {
+  constructor(obj: AudioUrlSourceDef);
+}
+
+declare interface YTAudioSourceDef extends AudioUrlSourceDef {
+  itagId: number;
+  initStart: number;
+  initEnd: number;
+  indexStart: number;
+  indexEnd: number;
+  audioChannels: number;
+}
+declare class YTAudioSource extends AudioUrlSource {
+  constructor(obj: YTAudioSourceDef);
+}
+
+declare interface HLSSourceDef {
+  name: string;
+  duration: number;
+  url: string;
+}
+declare class HLSSource implements IVideoSource {
+  constructor(obj: HLSSourceDef);
+}
+
+declare interface DashSourceDef {
+  name: string;
+  duration: number;
+  url: string;
+}
+declare class DashSource implements IVideoSource {
+  constructor(obj: DashSourceDef);
+}
+
+// Channel
+declare interface PlatformChannelDef {
+  id: PlatformID;
+  name: string;
+  thumbnail: string;
+  banner: string;
+  subscribers: number;
+  description: string;
+  url: string;
+  links?: Map<string>;
+}
+
+declare class PlatformChannel {
+  plugin_type: string;
+  id: string;
+  name: string;
+  thumbnail: string;
+  banner: string;
+  subscribers: number;
+  description: string;
+  url: string;
+  urlAlternatives: string[];
+  links: Map<string>;
+
+  constructor(obj: PlatformChannelDef) {
+    obj = obj ?? {};
+    this.plugin_type = 'PlatformChannel';
+    this.id = obj.id ?? ''; //string
+    this.name = obj.name ?? ''; //string
+    this.thumbnail = obj.thumbnail; //string
+    this.banner = obj.banner; //string
+    this.subscribers = obj.subscribers ?? 0; //integer
+    this.description = obj.description; //string
+    this.url = obj.url ?? ''; //string
+    this.urlAlternatives = obj.urlAlternatives ?? [];
+    this.links = obj.links ?? {}; //Map<string,string>
+  }
+}
+
+// Ratings
+declare interface IRating {
+  type: number;
+}
+declare class RatingLikes implements IRating {
+  constructor(likes: number);
+}
+declare class RatingLikesDislikes implements IRating {
+  constructor(likes: number, dislikes: number);
+}
+declare class RatingScaler implements IRating {
+  constructor(value: number);
+}
+
+declare interface CommentDef {
+  contextUrl: string;
+  author: PlatformAuthorLink;
+  message: string;
+  rating: IRating;
+  date: number;
+  replyCount: number;
+  context: any;
+}
+
+//Temporary backwards compat
+declare class Comment extends PlatformComment {
+  constructor(obj: CommentDef) {
+    super(obj);
+  }
+}
+
+declare class PlaybackTracker {
+  nextRequest: number;
+
+  constructor(interval) {
+    this.nextRequest = interval ?? 10 * 1000;
+  }
+
+  setProgress(seconds: number): void {
+    throw new ScriptImplementationException(
+      'Missing required setProgress(seconds) on PlaybackTracker',
+    );
+  }
+}
+
+declare class LiveEventPager {
+  plugin_type: string;
+  _entries: { [key: string]: any };
+
+  constructor(results: LiveEvent[], hasMore: boolean, context: any) {
+    this.plugin_type = 'LiveEventPager';
+    this.results = results ?? [];
+    this.hasMore = hasMore ?? false;
+    this.context = context ?? {};
+    this.nextRequest = 4000;
+  }
+
+  hasMorePagers(): boolean {
+    return this.hasMore;
+  }
+  nextPage(): LiveEventPager {
+    return new Pager([], false, this.context);
+  } //Could be self
+
+  delete(name: string): void;
+  get(name: string): any;
+  getAll(name: string): any[];
+  has(name: string): boolean;
+  set(name: string, value: any): void;
+  forEach(
+    callback: (value: any, name: string, pager: LiveEventPager) => void,
+  ): void;
+  keys(): IterableIterator<string>;
+  values(): IterableIterator<any>;
+  entries(): IterableIterator<[string, any]>;
+  clear(): void;
+}
+
+declare class LiveEvent {
+  plugin_type: string;
+  id: string;
+  name: string;
+  description: string;
+  startDate: number;
+  endDate: number;
+  thumbnail: string;
+  state: number;
+  upcomingText: string;
+  viewCount: number;
+  tracker: PlaybackTracker;
+  rating: any;
+
+  constructor(type: string) {
+    this.type = type;
+  }
+}
+declare class LiveEventComment extends LiveEvent {
+  constructor(
+    name: string,
+    message: string,
+    thumbnail?: string,
+    colorName,
+    badges,
+  ) {
+    super(1);
+    this.name = name;
+    this.message = message;
+    this.thumbnail = thumbnail;
+    this.colorName = colorName;
+    this.badges = badges;
+  }
+}
+
+declare class LiveEventEmojis extends LiveEvent {
+  constructor(emojis) {
+    super(4);
+    this.emojis = emojis;
+  }
+}
+
+declare class LiveEventDonation extends LiveEvent {
+  constructor(
+    amount: number,
+    name: string,
+    message: string,
+    thumbnail?: string,
+    expire?: any,
+    colorDonation?: string,
+  ) {
+    super(5);
+    this.amount = amount;
+    this.name = name;
+    this.message = message ?? '';
+    this.thumbnail = thumbnail;
+    this.expire = expire;
+    this.colorDonation = colorDonation;
+  }
+}
+
+declare class LiveEventViewCount extends LiveEvent {
+  constructor(viewCount: number) {
+    super(10);
+    this.viewCount = viewCount;
+  }
+}
+
+declare class LiveEventRaid extends LiveEvent {
+  constructor(targetUrl: string, targetName: string, targetThumbnail: string) {
+    super(100);
+    this.targetUrl = targetUrl;
+    this.targetName = targetName;
+    this.targetThumbnail = targetThumbnail;
+  }
+}
+
+//Pagers
+
+declare class ContentPager {
+  constructor(results: [], hasMore: boolean, context: any) {
+    this.plugin_type = 'ContentPager';
+    this.results = results ?? [];
+    this.hasMore = hasMore ?? false;
+    this.context = context ?? {};
+  }
+
+  hasMorePagers() {
+    return this.hasMore;
+  }
+  nextPage() {
+    return new ContentPager([], false, this.context);
+  }
+}
+
+declare class VideoPager {
+  hasMore: boolean;
+  context: any;
+
+  constructor(results: PlatformVideo[], hasMore?: boolean, context?: any) {
+    this.plugin_type = 'VideoPager';
+    this.results = results ?? [];
+    this.hasMore = hasMore ?? false;
+    this.context = context ?? {};
+  }
+  hasMorePagers(): boolean {
+    return this.hasMore;
+  }
+  nextPage(): VideoPager {
+    return new VideoPager([], false, this.context);
+  }
+}
+
+declare class ChannelPager {
+  hasMore: boolean;
+  context: any;
+
+  constructor(results: PlatformVideo[], hasMore: boolean, context: any) {
+    this.plugin_type = 'ChannelPager';
+    this.results = results ?? [];
+    this.hasMore = hasMore ?? false;
+    this.context = context ?? {};
+  }
+
+  hasMorePagers(): boolean {
+    return this.hasMore;
+  }
+  nextPage(): ChannelPager {
+    return new Pager([], false, this.context);
+  }
+}
+
+declare class PlaylistPager {
+  hasMore: boolean;
+  context: any;
+
+  constructor(results: PlatformPlaylist[], hasMore?: boolean, context?: any) {
+    this.plugin_type = 'PlaylistPager';
+    this.results = results ?? [];
+    this.hasMore = hasMore ?? false;
+    this.context = context ?? {};
+  }
+
+  hasMorePagers() {
+    return this.hasMore;
+  }
+  nextPage() {
+    return new Pager([], false, this.context);
+  }
+}
+
+declare class CommentPager {
+  context: any;
+
+  constructor(results: PlatformComment[], hasMore: boolean, context: any) {
+    this.plugin_type = 'CommentPager';
+    this.results = results ?? [];
+    this.hasMore = hasMore ?? false;
+    this.context = context ?? {};
+  }
+  hasMorePagers(): boolean {
+    return this.hasMore;
+  }
+  nextPage(): CommentPager {
+    return new Pager([], false, this.context);
+  }
+}
+
+declare interface Map<T> {
+  [Key: string]: T;
+}
+
+function throwException(ttype: string, message: string): void {
+  throw new Error('V8EXCEPTION:' + type + '-' + message);
+}
+
+let plugin = {
+  config: {},
+  settings: {},
+};
+
+// Plugin configuration
+// To override by plugin
+interface Source {
+  getHome(): VideoPager;
+
+  enable(conf: Config, settings: Map<string>, saveStateStr: string): void;
+
+  setSettings(settings: any): void;
+
+  disable(): void;
+
+  searchSuggestions(query: string): string[];
+  search(
+    query: string,
+    type: string,
+    order: string,
+    filters: FilterGroup[],
+  ): VideoPager;
+  getSearchCapabilities(): ResultCapabilities;
+
+  // Optional
+  searchChannelVideos?(
+    channelUrl: string,
+    query: string,
+    type: string,
+    order: string,
+    filters: FilterGroup[],
+  ): VideoPager;
+  getSearchChannelVideoCapabilities?(): ResultCapabilities;
+
+  isChannelUrl(url: string): boolean;
+  getChannel(url: string): PlatformChannel | null;
+
+  getChannelVideos(
+    url: string,
+    type: string,
+    order: string,
+    filters: FilterGroup[],
+  ): VideoPager;
+  getChannelCapabilities(): ResultCapabilities;
+  getSearchChannelContentsCapabilities(): ResultCapabilities;
+  getPeekChannelTypes(): string[];
+  peekChannelContents(url, type): PlatformVideo[];
+
+  isVideoDetailsUrl(url: string): boolean;
+  getVideoDetails(url: string): PlatformVideoDetails;
+
+  // Optional
+  getComments?(url: string): CommentPager;
+  getSubComments?(comment: Comment): CommentPager;
+
+  // Optional
+  getUserSubscriptions?(): string[];
+  getUserPlaylists?(): string[];
+
+  // Optional
+  isPlaylistUrl?(url: string): boolean;
+
+  searchPlaylists(query, type, order, filters);
+
+  getPlaylist?(url: string): PlatformPlaylistDetails;
+
+  isContentDetailsUrl(url: string): boolean;
+
+  getChannelContents(
+    url: string,
+    type?: string,
+    order?: string,
+    filters?: Map<String, List<String>>,
+  ): VideoPager;
+
+  searchChannels(query: string): ChannelPager;
+
+  getContentDetails(url: string): PlatformVideoDetails;
+
+  getComments(url: string): CommentPager;
+
+  getSubComments(comment: PlatformComment): CommentPager;
+
+  getChannelPlaylists(url: string): PlaylistPager;
+
+  searchChannelContents(
+    channelUrl: string,
+    query: string,
+    type: string,
+    order: string,
+    filters: FilterGroup[],
+  ): VideoPager;
+
+  saveState(): void;
+
+  getChannelTemplateByClaimMap(): any;
+}
+
+function parseSettings(settings) {
+  if (!settings) return {};
+  let newSettings = {};
+  for (let key in settings) {
+    if (typeof settings[key] == 'string')
+      newSettings[key] = JSON.parse(settings[key]);
+    else newSettings[key] = settings[key];
+  }
+  return newSettings;
+}
+
+function log(obj: string | object) {
+  if (obj) {
+    console.log(obj);
+    if (typeof obj == 'string') bridge.log(obj);
+    else bridge.log(JSON.stringify(obj, null, 4));
+  }
+}
+
+function encodePathSegment(segment) {
+  return encodeURIComponent(segment).replace(/[!'()*]/g, function (c) {
+    return '%' + c.charCodeAt(0).toString(16);
+  });
+}
+
+class URLSearchParams {
+  constructor(init) {
+    this._entries = {};
+    if (typeof init === 'string') {
+      if (init !== '') {
+        init = init.replace(/^\?/, '');
+        const attributes = init.split('&');
+        let attribute;
+        for (let i = 0; i < attributes.length; i++) {
+          attribute = attributes[i].split('=');
+          this.append(
+            decodeURIComponent(attribute[0]),
+            attribute.length > 1 ? decodeURIComponent(attribute[1]) : '',
+          );
+        }
+      }
+    } else if (init instanceof URLSearchParams) {
+      init.forEach((value, name) => {
+        this.append(value, name);
+      });
+    }
+  }
+  append(name, value) {
+    value = value.toString();
+    if (name in this._entries) {
+      this._entries[name].push(value);
+    } else {
+      this._entries[name] = [value];
+    }
+  }
+  delete(name) {
+    delete this._entries[name];
+  }
+  get(name) {
+    return name in this._entries ? this._entries[name][0] : null;
+  }
+  getAll(name) {
+    return name in this._entries ? this._entries[name].slice(0) : [];
+  }
+  has(name) {
+    return name in this._entries;
+  }
+  set(name, value) {
+    this._entries[name] = [value.toString()];
+  }
+  forEach(callback) {
+    let entries;
+    for (let name in this._entries) {
+      if (this._entries.hasOwnProperty(name)) {
+        entries = this._entries[name];
+        for (let i = 0; i < entries.length; i++) {
+          callback.call(this, entries[i], name, this);
+        }
+      }
+    }
+  }
+  keys() {
+    const items = [];
+    this.forEach((value, name) => {
+      items.push(name);
+    });
+    return createIterator(items);
+  }
+  values() {
+    const items = [];
+    this.forEach((value) => {
+      items.push(value);
+    });
+    return createIterator(items);
+  }
+  entries() {
+    const items = [];
+    this.forEach((value, name) => {
+      items.push([value, name]);
+    });
+    return createIterator(items);
+  }
+  toString() {
+    let searchString = '';
+    this.forEach((value, name) => {
+      if (searchString.length > 0) searchString += '&';
+      searchString +=
+        encodeURIComponent(name) + '=' + encodeURIComponent(value);
+    });
+    return searchString;
+  }
+}
+
+const source: Source;
+
+declare var IS_TESTING: boolean;
+
+let Type = {
+  Source: {
+    Dash: 'DASH',
+    HLS: 'HLS',
+    STATIC: 'Static',
+  },
+  Feed: {
+    Videos: 'VIDEOS',
+    Streams: 'STREAMS',
+    Mixed: 'MIXED',
+    Live: 'LIVE',
+    Subscriptions: 'SUBSCRIPTIONS',
+  },
+  Order: {
+    Chronological: 'CHRONOLOGICAL',
+  },
+  Date: {
+    LastHour: 'LAST_HOUR',
+    Today: 'TODAY',
+    LastWeek: 'LAST_WEEK',
+    LastMonth: 'LAST_MONTH',
+    LastYear: 'LAST_YEAR',
+  },
+  Duration: {
+    Short: 'SHORT',
+    Medium: 'MEDIUM',
+    Long: 'LONG',
+  },
+  Text: {
+    RAW: 0,
+    HTML: 1,
+    MARKUP: 2,
+  },
+  Chapter: {
+    NORMAL: 0,
+
+    SKIPPABLE: 5,
+    SKIP: 6,
+    SKIPONCE: 7,
+  },
+};
+
+let Language = {
+  UNKNOWN: 'Unknown',
+  ARABIC: 'ar',
+  SPANISH: 'es',
+  FRENCH: 'fr',
+  HINDI: 'hi',
+  INDONESIAN: 'id',
+  KOREAN: 'ko',
+  PORTUGUESE: 'pt',
+  PORTBRAZIL: 'pt',
+  RUSSIAN: 'ru',
+  THAI: 'th',
+  TURKISH: 'tr',
+  VIETNAMESE: 'vi',
+  ENGLISH: 'en',
+};
+
+interface HttpResponse {
+  isOk: boolean;
+  body: string;
+  code: number;
+}
+
+domParser.parseFromString(detailsRequestHtml.body, "text/html")
+
+let domParser = {
+  parseFromString: function (elementText: string, contentType: string): Unit {},
+}
+
+//Package Bridge (variable: bridge)
+let bridge = {
+  /**
+   * @param {String} label
+   * @param {String} data
+   * @return {Unit}
+   **/
+  devSubmit: function (label: string, data: string): Unit {},
+
+  /**
+   * @return {Boolean}
+   **/
+  isLoggedIn: function (): boolean {},
+
+  /**
+   * @param {String} str
+   * @return {Unit}
+   **/
+  log: function (str: string): Unit {},
+
+  /**
+   * @param {String} str
+   * @return {Unit}
+   **/
+  throwTest: function (str: string): Unit {},
+
+  /**
+   * @param {String} str
+   * @return {Unit}
+   **/
+  toast: function (str: string): Unit {},
+};
+
+//Package Http (variable: http)
+
+interface IHttp {
+  /**
+   * @param {String} url
+   * @param {Map} headers
+   * @param {Boolean} useAuth
+   * @return {BridgeHttpResponse}
+   **/
+  GET(
+    url: string,
+    headers: Map<string, string>,
+    useAuth?: boolean,
+  ): BridgeHttpResponse;
+
+  /**
+   * @param {String} url
+   * @param {String} body
+   * @param {Map} headers
+   * @param {Boolean} useAuth
+   * @return {BridgeHttpResponse}
+   **/
+  POST(
+    url: string,
+    body: string,
+    headers: Map<string, string>,
+    useAuth: boolean,
+  ): BridgeHttpResponse;
+
+  /**
+   * @return {BatchBuilder}
+   **/
+  batch(): BatchBuilder;
+
+  /**
+   * @param {Boolean} withAuth
+   * @return {PackageHttpClient}
+   **/
+  getDefaultClient(withAuth: boolean): PackageHttpClient;
+
+  /**
+   * @param {Boolean} withAuth
+   * @return {PackageHttpClient}
+   **/
+  newClient(withAuth: boolean): PackageHttpClient;
+
+  /**
+   * @param {String} method
+   * @param {String} url
+   * @param {Map} headers
+   * @param {Boolean} useAuth
+   * @return {BridgeHttpResponse}
+   **/
+  request(
+    method: string,
+    url: string,
+    headers: Map<string, string>,
+    useAuth: boolean,
+  ): BridgeHttpResponse;
+
+  /**
+   * @param {String} method
+   * @param {String} url
+   * @param {String} body
+   * @param {Map} headers
+   * @param {Boolean} useAuth
+   * @return {BridgeHttpResponse}
+   **/
+  requestWithBody(
+    method: string,
+    url: string,
+    body: string,
+    headers: Map<string, string>,
+    useAuth: boolean,
+  ): BridgeHttpResponse;
+
+  /**
+   * @param {String} url
+   * @param {Map} headers
+   * @param {Boolean} useAuth
+   * @return {SocketResult}
+   **/
+  socket(
+    url: string,
+    headers: Map<string, string>,
+    useAuth: boolean,
+  ): SocketResult;
+
+  /**
+   * @param {Map} headers
+   * @return {void}
+   **/
+  setDefaultHeaders(headers: Map<string, string>): void;
+
+  /**
+   * @param {Boolean} allow
+   * @return {void}
+   * */
+  setDoAllowNewCookies(allow: boolean): void;
+}
+
+let http: IHttp;
+
+interface IPager<T> {
+  hasMorePages(): Boolean;
+  nextPage();
+  getResults(): List<T>;
+}
diff --git a/types/types.d.ts b/types/types.d.ts
index 0795ad7d6f9b512f51ffde8c63a980c0e5fcbc58..490eae833f95d3e7412a3a550c329ae9c1c0e100 100644
--- a/types/types.d.ts
+++ b/types/types.d.ts
@@ -1,29 +1,38 @@
-
-interface IDailymotionPluginSettings {
-    hideSensitiveContent: boolean;
-    preferredCountryOptionIndex: number;
-    avatarSizeOptionIndex: number;
-    thumbnailResolutionOptionIndex: number;
-    videosPerPageOptionIndex: number;
-    playlistsPerPageOptionIndex: number;
-}
-
-interface IDailymotionSubtitle {
-    data: Map<string, string, { urls: string[], label: string }>,
-    enable: boolean
-}
-
-interface IDictionary<T> {
-    [key: string]: T;
-}
-
-interface IPlatformSystemPlaylist {
-    pluginId: string,
-    httpClient: IHttp,
-    query: string,
-    operationName: string,
-    rootObject: string,
-    playlistName: string,
-    usePlatformAuth: boolean,
-    thumbnailResolutionIndex: number
-}
\ No newline at end of file
+import { Video, Live } from "./CodeGenDailymotion";
+
+type DailymotionStreamingContent = Video | Live | null;
+
+interface IDailymotionPluginSettings {
+  hideSensitiveContent: boolean;
+  preferredCountryOptionIndex: number;
+  avatarSizeOptionIndex: number;
+  thumbnailResolutionOptionIndex: number;
+  videosPerPageOptionIndex: number;
+  playlistsPerPageOptionIndex: number;
+}
+
+interface IDailymotionSubtitle {
+  data: Map<string, string, { urls: string[]; label: string }>;
+  enable: boolean;
+}
+
+interface IDictionary<T> {
+  [key: string]: T;
+}
+
+interface IPlatformSystemPlaylist {
+  pluginId: string;
+  httpClient: IHttp;
+  query: string;
+  operationName: string;
+  rootObject: string;
+  playlistName: string;
+  usePlatformAuth: boolean;
+  thumbnailResolutionIndex: number;
+}
+
+type AnonymousUserAuthorization = {
+  anonymousUserAuthorizationToken?: string,
+  anonymousUserAuthorizationTokenExpirationDate?: number,
+  isValid: boolean
+} 
\ No newline at end of file