diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 9234716d9772fc8cb1e8b018673d2f0767c1c2af..1248ab68f832b21cab8ff5090fa8d065ae278c6c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,27 +1,20 @@
 name: Build Typescript
 
 on:
-  # Runs on pushes targeting the default branch
   push:
     branches: ["master"]
-
-  # Allows you to run this workflow manually from the Actions tab
   workflow_dispatch:
 
-# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
 permissions:
-  contents: read
+  contents: write
   pages: write
   id-token: write
 
-# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
-# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
 concurrency:
   group: "pages"
-  cancel-in-progress: false
+  cancel-in-progress: true
 
 jobs:
-  # Single deploy job since we're just deploying
   deploy:
     environment:
       name: github-pages
@@ -30,24 +23,25 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v4
-
-      # Build step
-      - name: Set up Node.js
-        uses: actions/setup-node@v3
         with:
-          node-version: '20'
+          persist-credentials: false # Ensure the token is used for pushing
+          fetch-depth: 0             # Fetch all history for all branches and tags
+
+      # - name: Set up Node.js
+      #   uses: actions/setup-node@v3
+      #   with:
+      #     node-version: '20'
 
-      - name: Install dependencies
-        run: npm install
+      # - name: Install dependencies
+      #   run: npm install
 
-      - name: Build the project
-        run: npm run build
+      # - name: Build the project
+      #   run: npm run build
 
-      - name: Sign the plugin
-        run: |
-          sh sign.sh ./build/DailymotionScript.js ./build/DailymotionConfig.json
-        env:
-          SIGNING_PRIVATE_KEY: ${{ secrets.SIGNING_PRIVATE_KEY }}
+      # - name: Sign the plugin
+      #   run: sh sign.sh ./build/DailymotionScript.js ./build/DailymotionConfig.json
+      #   env:
+      #     SIGNING_PRIVATE_KEY: ${{ secrets.SIGNING_PRIVATE_KEY }}
 
       - name: Setup Pages
         uses: actions/configure-pages@v5
@@ -55,7 +49,6 @@ jobs:
       - name: Upload artifact
         uses: actions/upload-pages-artifact@v3
         with:
-          # Upload the build directory
           path: 'build'
 
       - name: Deploy to GitHub Pages
diff --git a/.gitignore b/.gitignore
index 67fb7a8aafc366dcc17642a3bbe41b1da1219613..cebfe5d35b53f77c9aa6680a77bf3520f4ee1a8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
 .bin
 *.pem
 .env
-node_modules/
-build/
\ No newline at end of file
+node_modules/
\ No newline at end of file
diff --git a/build/DailymotionConfig.json b/build/DailymotionConfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..25e3ffb1752662fef2bfecf19b1bac066c10c782
--- /dev/null
+++ b/build/DailymotionConfig.json
@@ -0,0 +1,296 @@
+{
+  "name": "Dailymotion (Alpha)",
+  "platformUrl": "dailymotion.com",
+  "description": "The latest news, sports, music and entertainment videos on Dailymotion",
+  "author": "Stefan Cruz",
+  "authorUrl": "https://craftwithstefan.com/",
+  "sourceUrl": "https://stefancruz.github.io/GrayjayDailymotion/DailymotionConfig.json",
+  "scriptUrl": "./DailymotionScript.js",
+  "repositoryUrl": "https://github.com/stefancruz/GrayjayDailymotion",
+  "version": 6,
+  "iconUrl": "./dailymotion.png",
+  "id": "9c87e8db-e75d-48f4-afe5-2d203d4b95c5",
+  "scriptSignature": "asuhHJihdvvK4yRn3MoBzmuuHQR+5vzSy6BwEx9HEbhXwFpyJi5DOr+xpDbx3wXwVzmlzImFAsMCTLlOY7J2MHvsG4dTPi5Mn2oJuA6B7BtG1mN2ddXGeLcQbgnjIRws/lBGFSeD26RNe3ATOU8PZSrAs/ldnhKK5HAK4mF+Rumuf9Z3Hv/QMJOenH2lmXhTkN2uUTiDrpcm6Yth2bswChQoFdwDIQr1aE/9C/Ebm6rfLi8Er8rEqYM/3G3HwogFyeof9c2/DAEfhaKSVK2tR1dTYvhn05YBpUHZ0U7DvSPUDBfbitOhyUjeXe9uL13IAVcj5kIhwCEzN8U8mOrpUg==",
+  "scriptPublicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwrBXc5lMXrDD4y5GLT1rnxq64nmmBcE19lvIWL/o+wTK6jebmlQss4CrpH2Pkw8cwczcA4aHQKN8+ThveJFMBxW57gf30odKrAglvvPAv9Rm9D0BZZ501jBXvz+2Rt7su/ICN97JNabjeniAxWBGgr+It3gwvgGAXmWo9FcGfPTooy+g7lErDx5S9Hy/W83b/L3Q64Ytcb4ed0zi6pfZhgpXW0ZJxiZSJRPj6JusTJclPd4OHnl6EwAL979PloyKT0EjMgMX89LAsgctJzSOJkWcQnsbcWV3SE87GyFN4/4Py+GEvvZLKv/iWhY9Dhw9G7j3hy+3QPpHd5L3KSXC4wIDAQAB",
+  "packages": [
+    "Http"
+  ],
+  "allowEval": false,
+  "allowUrls": [
+    "dailymotion.com",
+    "www.dailymotion.com",
+    "graphql.api.dailymotion.com",
+    "vod.cf.dmcdn.net",
+    "s1.dmcdn.net",
+    "s2.dmcdn.net",
+    "s3.dmcdn.net"
+  ],
+  "settings": [
+    {
+      "variable": "hideSensitiveContent",
+      "name": "Hide Sensitive Content",
+      "description": "Do not allow watching sensitive content",
+      "type": "Boolean",
+      "default": "true"
+    },
+    {
+      "variable": "preferredCountry",
+      "name": "Preferred Country",
+      "description": "Select your preferred country to get the best results",
+      "type": "Dropdown",
+      "default": "0",
+      "options": [
+        "",
+        "Afghanistan",
+        "Ã…land Islands",
+        "Albania",
+        "Algeria",
+        "American Samoa",
+        "Andorra",
+        "Angola",
+        "Anguilla",
+        "Antarctica",
+        "Antigua and Barbuda",
+        "Argentina",
+        "Armenia",
+        "Aruba",
+        "Australia",
+        "Austria",
+        "Azerbaijan",
+        "Bahamas",
+        "Bahrain",
+        "Bangladesh",
+        "Barbados",
+        "Belarus",
+        "Belgium",
+        "Belize",
+        "Benin",
+        "Bermuda",
+        "Bhutan",
+        "Bolivia",
+        "Bonaire, Sint Eustatius and Saba",
+        "Bosnia and Herzegovina",
+        "Botswana",
+        "Bouvet Island",
+        "Brazil",
+        "British Indian Ocean Territory",
+        "Brunei Darussalam",
+        "Bulgaria",
+        "Burkina Faso",
+        "Burundi",
+        "Cabo Verde",
+        "Cambodia",
+        "Cameroon",
+        "Canada",
+        "Cayman Islands",
+        "Central African Republic",
+        "Chad",
+        "Chile",
+        "China",
+        "Christmas Island",
+        "Cocos (Keeling) Islands",
+        "Colombia",
+        "Comoros",
+        "Congo",
+        "Congo, Democratic Republic of the",
+        "Cook Islands",
+        "Costa Rica",
+        "Côte d'Ivoire",
+        "Croatia",
+        "Cuba",
+        "Curaçao",
+        "Cyprus",
+        "Czech Republic",
+        "Denmark",
+        "Djibouti",
+        "Dominica",
+        "Dominican Republic",
+        "Ecuador",
+        "Egypt",
+        "El Salvador",
+        "Equatorial Guinea",
+        "Eritrea",
+        "Estonia",
+        "Eswatini",
+        "Ethiopia",
+        "Falkland Islands (Malvinas)",
+        "Faroe Islands",
+        "Fiji",
+        "Finland",
+        "France",
+        "French Guiana",
+        "French Polynesia",
+        "French Southern Territories",
+        "Gabon",
+        "Gambia",
+        "Georgia",
+        "Germany",
+        "Ghana",
+        "Gibraltar",
+        "Greece",
+        "Greenland",
+        "Grenada",
+        "Guadeloupe",
+        "Guam",
+        "Guatemala",
+        "Guernsey",
+        "Guinea",
+        "Guinea-Bissau",
+        "Guyana",
+        "Haiti",
+        "Heard Island and McDonald Islands",
+        "Holy See",
+        "Honduras",
+        "Hong Kong",
+        "Hungary",
+        "Iceland",
+        "India",
+        "Indonesia",
+        "Iran (Islamic Republic of)",
+        "Iraq",
+        "Ireland",
+        "Isle of Man",
+        "Israel",
+        "Italy",
+        "Jamaica",
+        "Japan",
+        "Jersey",
+        "Jordan",
+        "Kazakhstan",
+        "Kenya",
+        "Kiribati",
+        "Korea (Democratic People's Republic of)",
+        "Korea (Republic of)",
+        "Kuwait",
+        "Kyrgyzstan",
+        "Lao People's Democratic Republic",
+        "Latvia",
+        "Lebanon",
+        "Lesotho",
+        "Liberia",
+        "Libya",
+        "Liechtenstein",
+        "Lithuania",
+        "Luxembourg",
+        "Macao",
+        "Madagascar",
+        "Malawi",
+        "Malaysia",
+        "Maldives",
+        "Mali",
+        "Malta",
+        "Marshall Islands",
+        "Martinique",
+        "Mauritania",
+        "Mauritius",
+        "Mayotte",
+        "Mexico",
+        "Micronesia (Federated States of)",
+        "Moldova (Republic of)",
+        "Monaco",
+        "Mongolia",
+        "Montenegro",
+        "Montserrat",
+        "Morocco",
+        "Mozambique",
+        "Myanmar",
+        "Namibia",
+        "Nauru",
+        "Nepal",
+        "Netherlands",
+        "New Caledonia",
+        "New Zealand",
+        "Nicaragua",
+        "Niger",
+        "Nigeria",
+        "Niue",
+        "Norfolk Island",
+        "North Macedonia",
+        "Northern Mariana Islands",
+        "Norway",
+        "Oman",
+        "Pakistan",
+        "Palau",
+        "Palestine, State of",
+        "Panama",
+        "Papua New Guinea",
+        "Paraguay",
+        "Peru",
+        "Philippines",
+        "Pitcairn",
+        "Poland",
+        "Portugal",
+        "Puerto Rico",
+        "Qatar",
+        "Réunion",
+        "Romania",
+        "Russian Federation",
+        "Rwanda",
+        "Saint Barthélemy",
+        "Saint Helena, Ascension and Tristan da Cunha",
+        "Saint Kitts and Nevis",
+        "Saint Lucia",
+        "Saint Martin (French part)",
+        "Saint Pierre and Miquelon",
+        "Saint Vincent and the Grenadines",
+        "Samoa",
+        "San Marino",
+        "Sao Tome and Principe",
+        "Saudi Arabia",
+        "Senegal",
+        "Serbia",
+        "Seychelles",
+        "Sierra Leone",
+        "Singapore",
+        "Sint Maarten (Dutch part)",
+        "Slovakia",
+        "Slovenia",
+        "Solomon Islands",
+        "Somalia",
+        "South Africa",
+        "South Georgia and the South Sandwich Islands",
+        "South Sudan",
+        "Spain",
+        "Sri Lanka",
+        "Sudan",
+        "Suriname",
+        "Svalbard and Jan Mayen",
+        "Sweden",
+        "Switzerland",
+        "Syrian Arab Republic",
+        "Taiwan, Province of China",
+        "Tajikistan",
+        "Tanzania, United Republic of",
+        "Thailand",
+        "Timor-Leste",
+        "Togo",
+        "Tokelau",
+        "Tonga",
+        "Trinidad and Tobago",
+        "Tunisia",
+        "Turkey",
+        "Turkmenistan",
+        "Turks and Caicos Islands",
+        "Tuvalu",
+        "Uganda",
+        "Ukraine",
+        "United Arab Emirates",
+        "United Kingdom of Great Britain and Northern Ireland",
+        "United States Minor Outlying Islands",
+        "United States of America",
+        "Uruguay",
+        "Uzbekistan",
+        "Vanuatu",
+        "Venezuela (Bolivarian Republic of)",
+        "Viet Nam",
+        "Virgin Islands (British)",
+        "Virgin Islands (U.S.)",
+        "Wallis and Futuna",
+        "Western Sahara",
+        "Yemen",
+        "Zambia",
+        "Zimbabwe"
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/build/DailymotionScript.js b/build/DailymotionScript.js
new file mode 100644
index 0000000000000000000000000000000000000000..67df9498903561b20b4ff7b1606bb5fa3b32d02c
--- /dev/null
+++ b/build/DailymotionScript.js
@@ -0,0 +1,1932 @@
+'use strict';
+
+const errorTypes = {
+    "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 countryNamesToCode = {
+    "": "",
+    "Afghanistan": "AF",
+    "Aland Islands": "AX",
+    "Albania": "AL",
+    "Algeria": "DZ",
+    "American Samoa": "AS",
+    "Andorra": "AD",
+    "Angola": "AO",
+    "Anguilla": "AI",
+    "Antarctica": "AQ",
+    "Antigua and Barbuda": "AG",
+    "Argentina": "AR",
+    "Armenia": "AM",
+    "Aruba": "AW",
+    "Australia": "AU",
+    "Austria": "AT",
+    "Azerbaijan": "AZ",
+    "Bahamas": "BS",
+    "Bahrain": "BH",
+    "Bangladesh": "BD",
+    "Barbados": "BB",
+    "Belarus": "BY",
+    "Belgium": "BE",
+    "Belize": "BZ",
+    "Benin": "BJ",
+    "Bermuda": "BM",
+    "Bhutan": "BT",
+    "Bolivia": "BO",
+    "Bonaire, Sint Eustatius and Saba": "BQ",
+    "Bosnia and Herzegovina": "BA",
+    "Botswana": "BW",
+    "Bouvet Island": "BV",
+    "Brazil": "BR",
+    "British Indian Ocean Territory": "IO",
+    "Brunei Darussalam": "BN",
+    "Bulgaria": "BG",
+    "Burkina Faso": "BF",
+    "Burundi": "BI",
+    "Cambodia": "KH",
+    "Cameroon": "CM",
+    "Canada": "CA",
+    "Cabo Verde": "CV",
+    "Cayman Islands": "KY",
+    "Central African Republic": "CF",
+    "Chad": "TD",
+    "Chile": "CL",
+    "China": "CN",
+    "Christmas Island": "CX",
+    "Cocos (Keeling) Islands": "CC",
+    "Colombia": "CO",
+    "Comoros": "KM",
+    "Congo": "CG",
+    "Congo, Democratic Republic of the": "CD",
+    "Cook Islands": "CK",
+    "Costa Rica": "CR",
+    "Cote d'Ivoire": "CI",
+    "Croatia": "HR",
+    "Cuba": "CU",
+    "Curacao": "CW",
+    "Cyprus": "CY",
+    "Czech Republic": "CZ",
+    "Denmark": "DK",
+    "Djibouti": "DJ",
+    "Dominica": "DM",
+    "Dominican Republic": "DO",
+    "Ecuador": "EC",
+    "Egypt": "EG",
+    "El Salvador": "SV",
+    "Equatorial Guinea": "GQ",
+    "Eritrea": "ER",
+    "Estonia": "EE",
+    "Eswatini": "SZ",
+    "Ethiopia": "ET",
+    "Falkland Islands (Malvinas)": "FK",
+    "Faroe Islands": "FO",
+    "Fiji": "FJ",
+    "Finland": "FI",
+    "France": "FR",
+    "French Guiana": "GF",
+    "French Polynesia": "PF",
+    "French Southern Territories": "TF",
+    "Gabon": "GA",
+    "Gambia": "GM",
+    "Georgia": "GE",
+    "Germany": "DE",
+    "Ghana": "GH",
+    "Gibraltar": "GI",
+    "Greece": "GR",
+    "Greenland": "GL",
+    "Grenada": "GD",
+    "Guadeloupe": "GP",
+    "Guam": "GU",
+    "Guatemala": "GT",
+    "Guernsey": "GG",
+    "Guinea": "GN",
+    "Guinea-Bissau": "GW",
+    "Guyana": "GY",
+    "Haiti": "HT",
+    "Heard Island and McDonald Islands": "HM",
+    "Holy See": "VA",
+    "Honduras": "HN",
+    "Hong Kong": "HK",
+    "Hungary": "HU",
+    "Iceland": "IS",
+    "India": "IN",
+    "Indonesia": "ID",
+    "Iran": "IR",
+    "Iraq": "IQ",
+    "Ireland": "IE",
+    "Isle of Man": "IM",
+    "Israel": "IL",
+    "Italy": "IT",
+    "Jamaica": "JM",
+    "Japan": "JP",
+    "Jersey": "JE",
+    "Jordan": "JO",
+    "Kazakhstan": "KZ",
+    "Kenya": "KE",
+    "Kiribati": "KI",
+    "Korea, Democratic People's Republic of": "KP",
+    "Korea, Republic of": "KR",
+    "Kuwait": "KW",
+    "Kyrgyzstan": "KG",
+    "Lao People's Democratic Republic": "LA",
+    "Latvia": "LV",
+    "Lebanon": "LB",
+    "Lesotho": "LS",
+    "Liberia": "LR",
+    "Libya": "LY",
+    "Liechtenstein": "LI",
+    "Lithuania": "LT",
+    "Luxembourg": "LU",
+    "Macao": "MO",
+    "North Macedonia": "MK",
+    "Madagascar": "MG",
+    "Malawi": "MW",
+    "Malaysia": "MY",
+    "Maldives": "MV",
+    "Mali": "ML",
+    "Malta": "MT",
+    "Marshall Islands": "MH",
+    "Martinique": "MQ",
+    "Mauritania": "MR",
+    "Mauritius": "MU",
+    "Mayotte": "YT",
+    "Mexico": "MX",
+    "Micronesia, Federated States of": "FM",
+    "Moldova, Republic of": "MD",
+    "Monaco": "MC",
+    "Mongolia": "MN",
+    "Montenegro": "ME",
+    "Montserrat": "MS",
+    "Morocco": "MA",
+    "Mozambique": "MZ",
+    "Myanmar": "MM",
+    "Namibia": "NA",
+    "Nauru": "NR",
+    "Nepal": "NP",
+    "Netherlands": "NL",
+    "New Caledonia": "NC",
+    "New Zealand": "NZ",
+    "Nicaragua": "NI",
+    "Niger": "NE",
+    "Nigeria": "NG",
+    "Niue": "NU",
+    "Norfolk Island": "NF",
+    "Northern Mariana Islands": "MP",
+    "Norway": "NO",
+    "Oman": "OM",
+    "Pakistan": "PK",
+    "Palau": "PW",
+    "Palestine, State of": "PS",
+    "Panama": "PA",
+    "Papua New Guinea": "PG",
+    "Paraguay": "PY",
+    "Peru": "PE",
+    "Philippines": "PH",
+    "Pitcairn": "PN",
+    "Poland": "PL",
+    "Portugal": "PT",
+    "Puerto Rico": "PR",
+    "Qatar": "QA",
+    "Reunion": "RE",
+    "Romania": "RO",
+    "Russian Federation": "RU",
+    "Rwanda": "RW",
+    "Saint Barthelemy": "BL",
+    "Saint Helena, Ascension and Tristan da Cunha": "SH",
+    "Saint Kitts and Nevis": "KN",
+    "Saint Lucia": "LC",
+    "Saint Martin (French part)": "MF",
+    "Saint Pierre and Miquelon": "PM",
+    "Saint Vincent and the Grenadines": "VC",
+    "Samoa": "WS",
+    "San Marino": "SM",
+    "Sao Tome and Principe": "ST",
+    "Saudi Arabia": "SA",
+    "Senegal": "SN",
+    "Serbia": "RS",
+    "Seychelles": "SC",
+    "Sierra Leone": "SL",
+    "Singapore": "SG",
+    "Sint Maarten (Dutch part)": "SX",
+    "Slovakia": "SK",
+    "Slovenia": "SI",
+    "Solomon Islands": "SB",
+    "Somalia": "SO",
+    "South Africa": "ZA",
+    "South Georgia and the South Sandwich Islands": "GS",
+    "South Sudan": "SS",
+    "Spain": "ES",
+    "Sri Lanka": "LK",
+    "Sudan": "SD",
+    "Suriname": "SR",
+    "Svalbard and Jan Mayen": "SJ",
+    "Sweden": "SE",
+    "Switzerland": "CH",
+    "Syrian Arab Republic": "SY",
+    "Taiwan, Province of China": "TW",
+    "Tajikistan": "TJ",
+    "Tanzania, United Republic of": "TZ",
+    "Thailand": "TH",
+    "Timor-Leste": "TL",
+    "Togo": "TG",
+    "Tokelau": "TK",
+    "Tonga": "TO",
+    "Trinidad and Tobago": "TT",
+    "Tunisia": "TN",
+    "Turkey": "TR",
+    "Turkmenistan": "TM",
+    "Turks and Caicos Islands": "TC",
+    "Tuvalu": "TV",
+    "Uganda": "UG",
+    "Ukraine": "UA",
+    "United Arab Emirates": "AE",
+    "United Kingdom of Great Britain and Northern Ireland": "GB",
+    "United States of America": "US",
+    "United States Minor Outlying Islands": "UM",
+    "Uruguay": "UY",
+    "Uzbekistan": "UZ",
+    "Vanuatu": "VU",
+    "Venezuela, Bolivarian Republic of": "VE",
+    "Vietnam": "VN",
+    "Virgin Islands, British": "VG",
+    "Virgin Islands, U.S.": "VI",
+    "Wallis and Futuna": "WF",
+    "Western Sahara": "EH",
+    "Yemen": "YE",
+    "Zambia": "ZM",
+    "Zimbabwe": "ZW"
+};
+const constants = {
+    countryNamesToCode,
+    countryNames: Object.keys(countryNamesToCode)
+};
+
+const SEARCH_SUGGESTIONS_QUERY = `
+    query AUTOCOMPLETE_QUERY($query: String!) {
+		search {
+		  id
+		  suggestedVideos: autosuggestions(
+			query: {eq: $query}
+			filter: {story: {eq: VIDEO}}
+		  ) {
+			edges {
+			  node {
+				name
+			  }
+			}
+		  }
+		}
+	  }
+    `;
+const CHANNEL_BY_URL_QUERY = `
+fragment CHANNEL_MAIN_FRAGMENT on Channel {
+  id
+  xid
+  name
+  displayName
+  description
+  avatar(height: SQUARE_120) {
+    url
+  }
+  coverURL1024x: coverURL(size: "1024x")
+  coverURL1920x: coverURL(size: "1920x")
+  tagline
+  country {
+    id
+    codeAlpha2
+  }
+  metrics {
+    engagement {
+    followers {
+      edges {
+      node {
+        total
+      }
+      }
+    }
+    followings {
+      edges {
+      node {
+        total
+      }
+      }
+    }
+    }
+  }
+  stats {
+    id
+    views {
+    id
+    total
+    }
+    videos {
+    id
+    total
+    }
+  }
+  externalLinks {
+    facebookURL
+    twitterURL
+    websiteURL
+    instagramURL
+    pinterestURL
+  }
+  }
+  
+  query CHANNEL_QUERY_DESKTOP($channel_name: String!) {
+  channel(name: $channel_name) {
+    id
+    ...CHANNEL_MAIN_FRAGMENT
+  }
+  }
+`;
+const HOME_QUERY = `	
+fragment SEARCH_DISCOVERY_VIDEO_FRAGMENT on Video {
+	id
+	xid
+	title
+	isPublished
+	embedURL
+	thumbnail(height: PORTRAIT_720) {
+		url
+	}
+	createdAt
+	creator {
+		id
+		xid
+		name
+		displayName
+		avatar(height: SQUARE_240) {
+			url
+		}
+	}
+	duration
+	
+}
+
+query SEACH_DISCOVERY_QUERY($shouldQueryPromotedHashtag: Boolean!) {
+	home: views {
+		id
+		neon {
+			id
+			sections(space: "home") {
+				edges {
+					node {
+						id
+						name
+						title
+						description
+						components {
+							pageInfo {
+								hasNextPage
+							}
+							edges {
+								node {
+									__typename
+									... on Media {
+										...SEARCH_DISCOVERY_VIDEO_FRAGMENT
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	featuredContent {
+		id
+		channels(first: 10) {
+			edges {
+				node {
+					id
+					xid
+					displayName
+					name
+					logoURL(size: "x120")
+					stats {
+						id
+						followers {
+							id
+							total
+						}
+					}
+				}
+			}
+		}
+	}
+	conversations(
+		filter: { story: { eq: HASHTAG }, algorithm: { eq: SPONSORED } }
+		first: 1
+	) @include(if: $shouldQueryPromotedHashtag) {
+		edges {
+			node {
+				id
+				story {
+					... on Hashtag {
+						id
+						name
+					}
+				}
+			}
+		}
+	}
+}
+
+
+`;
+const CHANNEL_VIDEOS_BY_CHANNEL_NAME = `
+query CHANNEL_VIDEOS_QUERY(
+	$channel_name: String!
+	$first: Int!
+	$sort: String
+	$page: Int!
+	$allowExplicit: Boolean
+) {
+	channel(name: $channel_name) {
+		id
+		xid
+		channel_videos_all_videos: videos(
+			sort: $sort
+			page: $page
+			first: $first
+			allowExplicit: $allowExplicit
+		) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			edges {
+				node {
+					id
+					xid
+					title
+					thumbnail(height: PORTRAIT_720) {
+						url
+					}
+					bestAvailableQuality
+					duration
+					createdAt
+					creator {
+						id
+						name
+						displayName
+						avatar(height:SQUARE_240) {
+							url
+						}
+
+					}
+					metrics {
+						engagement {
+							likes {
+								totalCount
+							}
+						}
+					}
+
+				}
+			}
+		}
+	}
+}
+		
+	  
+	`;
+const MAIN_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: SQUARE_240) {
+				url
+			}
+		}
+		duration
+		thumbnail(height: PORTRAIT_720) {
+			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: SQUARE_240) {
+			url
+		}
+	}
+	
+	fragment PLAYLIST_BASE_FRAG on Collection {
+		id
+		xid
+		name
+		description
+		thumbnail(height: PORTRAIT_240) {
+			url
+		}
+		creator {
+			id
+			xid
+			name
+			displayName
+			avatar(height:SQUARE_240) {
+				url
+			}
+		}
+		description
+		stats {
+			id
+			videos {
+				id
+				total
+			}
+		}
+		metrics {
+			engagement {
+				videos {
+					edges {
+						node {
+							total
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	fragment TOPIC_BASE_FRAG on Topic {
+		id
+		xid
+		name
+		videos(sort: "recent", first: 5) {
+			pageInfo {
+				hasNextPage
+				nextPage
+			}
+			edges {
+				node {
+					id
+					...VIDEO_BASE_FRAGMENT
+					...VIDEO_FAVORITES_FRAGMENT
+				}
+			}
+		}
+		stats {
+			id
+			videos {
+				id
+				total
+			}
+		}
+	}
+	
+	query SEARCH_QUERY(
+		$query: String!
+		$shouldIncludeVideos: Boolean!
+		$shouldIncludeChannels: Boolean!
+		$shouldIncludePlaylists: Boolean!
+		$shouldIncludeTopics: Boolean!
+		$shouldIncludeLives: Boolean!
+		$page: Int
+		$limit: Int
+		$sortByVideos: SearchVideoSort
+		$durationMinVideos: Int
+		$durationMaxVideos: Int
+		$createdAfterVideos: DateTime
+	) {
+		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: PORTRAIT_720) {
+							url
+						}
+						description
+						metrics {
+							engagement {
+								audience {
+									totalCount
+								}
+							}
+						}
+						audienceCount
+						isOnAir
+						creator {
+							id
+							xid
+							name
+							displayName
+							avatar(height:SQUARE_240){
+								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
+					}
+				}
+			}
+			topics(query: $query, first: $limit, page: $page)
+				@include(if: $shouldIncludeTopics) {
+				pageInfo {
+					hasNextPage
+					nextPage
+				}
+				totalCount
+				edges {
+					node {
+						id
+						...TOPIC_BASE_FRAG
+					}
+				}
+			}
+		}
+	}		
+	`;
+const VIDE_DETAILS_QUERY = `
+	fragment VIDEO_FRAGMENT on Video {
+		id
+		xid
+		isPublished
+		duration
+		title
+		description
+		thumbnail(height: PORTRAIT_720) {
+			url
+		}
+		bestAvailableQuality
+		createdAt
+		isPrivate
+		isCreatedForKids
+		isExplicit
+		canDisplayAds
+		videoWidth: width
+		videoHeight: height
+		status
+		hashtags {
+			edges {
+				node {
+					id
+					name
+				}
+			}
+		}
+		stats {
+			id
+			views {
+				id
+				total
+			}
+		}
+		creator {
+			id
+			xid
+			name
+			displayName
+			avatar(height: SQUARE_240) {
+				url
+				height
+				width
+			}
+			coverURLx375: coverURL(size: "x375")
+			stats {
+				id
+				views {
+					id
+					total
+				}
+				followers {
+					id
+					total
+				}
+				videos {
+					id
+					total
+				}
+			}
+			country {
+				id
+				codeAlpha2
+			}
+			organization @skip(if: $isSEO) {
+				id
+				xid
+				owner {
+					id
+					xid
+				}
+			}
+		}
+		language {
+			id
+			codeAlpha2
+		}
+		tags {
+			edges {
+				node {
+					id
+					label
+				}
+			}
+		}
+		moderation {
+			id
+			reviewedAt
+		}
+		topics(whitelistedOnly: true, first: 3, page: 1) {
+			edges {
+				node {
+					id
+					xid
+					name
+					names {
+						edges {
+							node {
+								id
+								name
+								language {
+									id
+									codeAlpha2
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+		geoblockedCountries {
+			id
+			allowed
+			denied
+		}
+	}
+	
+	fragment LIVE_FRAGMENT on Live {
+		id
+		xid
+		startAt
+		endAt
+		isPublished
+		title
+		description
+		thumbnail(height:PORTRAIT_720){
+			url
+			height
+			width
+		}
+		category
+		createdAt
+		isPrivate
+		isExplicit
+		isCreatedForKids
+		bestAvailableQuality
+		canDisplayAds
+		videoWidth: width
+		videoHeight: height
+		stats {
+			id
+			views {
+				id
+				total
+			}
+		}
+		creator {
+			id
+			xid
+			name
+			displayName
+			avatar(height: SQUARE_240) {
+				url
+				height
+				width
+			}
+			coverURLx375: coverURL(size: "x375")
+			stats {
+				id
+				views {
+					id
+					total
+				}
+				followers {
+					id
+					total
+				}
+				videos {
+					id
+					total
+				}
+			}
+			country {
+				id
+				codeAlpha2
+			}
+			organization @skip(if: $isSEO) {
+				id
+				xid
+				owner {
+					id
+					xid
+				}
+			}
+		}
+		language {
+			id
+			codeAlpha2
+		}
+		tags {
+			edges {
+				node {
+					id
+					label
+				}
+			}
+		}
+		moderation {
+			id
+			reviewedAt
+		}
+		topics(whitelistedOnly: true, first: 3, page: 1) {
+			edges {
+				node {
+					id
+					xid
+					name
+					names {
+						edges {
+							node {
+								id
+								name
+								language {
+									id
+									codeAlpha2
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+		geoblockedCountries {
+			id
+			allowed
+			denied
+		}
+	}
+	
+	query WATCHING_VIDEO($xid: String!, $isSEO: Boolean!) {
+		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) {
+		search {
+			id
+			channels(query: $query, first: $limit, page: $page) {
+				pageInfo {
+					hasNextPage
+					nextPage
+				}
+				totalCount
+				edges {
+					node {
+						id
+						id
+						xid
+						name
+						displayName
+						description
+						avatar(height:SQUARE_240) {
+							url
+							height
+							width
+						}
+						metrics {
+							engagement {
+								followers {
+									edges {
+										node {
+											total
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	
+		`;
+const PLAYLIST_DETAILS_QUERY = `
+query PLAYLIST_VIDEO_QUERY($xid: String!, $numberOfVideos: Int = 100) {
+	collection(xid: $xid) {
+		id
+		id
+		xid
+		updatedAt
+		name
+		thumbnail(height: PORTRAIT_480) {
+			url
+		}
+		creator {
+			id
+			displayName
+			xid
+			avatar(height: SQUARE_240) {
+				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: PORTRAIT_480) {
+						url
+					}
+					creator {
+						id
+						displayName
+						xid
+						avatar(height: SQUARE_240) {
+							url
+						}
+						metrics {
+							engagement {
+								followers {
+									edges {
+										node {
+											total
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+	
+	`;
+const queries = {
+    SEARCH_SUGGESTIONS_QUERY,
+    CHANNEL_BY_URL_QUERY,
+    HOME_QUERY,
+    CHANNEL_VIDEOS_BY_CHANNEL_NAME,
+    MAIN_SEARCH_QUERY,
+    VIDE_DETAILS_QUERY,
+    SEARCH_CHANNEL,
+    PLAYLIST_DETAILS_QUERY
+};
+
+const objectToUrlEncodedString = (obj) => {
+    const encodedParams = [];
+    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('&');
+};
+var util = {
+    objectToUrlEncodedString,
+};
+
+const BASE_URL = "https://www.dailymotion.com";
+const BASE_URL_API = "https://graphql.api.dailymotion.com";
+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 USER_AGENT = '"Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 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';
+var config = {};
+var _settings = {};
+const X_DM_AppInfo_Id = "com.dailymotion.neon";
+const X_DM_AppInfo_Type = "website";
+const X_DM_AppInfo_Version = "v2024-05-16T12:17:57.363Z"; //TODO check how to get this dynamically
+const X_DM_Neon_SSR = "0";
+const X_DM_Preferred_Country = ""; //TODO check how to get this from Grayjay
+const PLATFORM = "Dailymotion";
+const PLATFORM_CLAIMTYPE = 3;
+let AUTHORIZATION_TOKEN_ANONYMOUS_USER = null;
+let AUTHORIZATION_TOKEN_ANONYMOUS_USER_EXPIRATION_DATE = null;
+// 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 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 };
+source.setSettings = function (settings) {
+    _settings = settings;
+};
+//Source Methods
+source.enable = function (conf, settings, saveStateStr) {
+    config = conf ?? {};
+    _settings = settings ?? {};
+    http.GET(BASE_URL, {}, true);
+    AUTHORIZATION_TOKEN_ANONYMOUS_USER = getAnonymousUserTokenSingleton();
+};
+source.getHome = function () {
+    return getVideoPager({}, 0);
+};
+source.searchSuggestions = function (query) {
+    const variables = {
+        "query": query
+    };
+    try {
+        const jsonResponse = executeGqlQuery({
+            operationName: 'AUTOCOMPLETE_QUERY',
+            variables: variables,
+            query: queries.SEARCH_SUGGESTIONS_QUERY
+        }, true);
+        return jsonResponse?.data?.search?.suggestedVideos?.edges?.map(edge => edge?.node?.name);
+    }
+    catch (error) {
+        log('Failed to get search suggestions:' + error?.message);
+        return [];
+    }
+};
+source.getSearchCapabilities = () => {
+    //TODO: refact this to use more constants
+    return {
+        types: [
+            Type.Feed.Videos,
+            Type.Feed.Live
+        ],
+        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 }
+                ]
+            }
+        ]
+    };
+};
+source.search = function (query, type, order, filters) {
+    return getSearchPagerAll({ q: query, page: 1, type, order, filters });
+};
+// source.getSearchChannelContentsCapabilities = function () {
+// };
+// source.searchChannelContents = function (channelUrl, query, 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({
+        operationName: 'CHANNEL_QUERY_DESKTOP',
+        variables: { "channel_name": channel_name },
+        query: queries.CHANNEL_BY_URL_QUERY
+    });
+    const user = channelDetails.data.channel;
+    const banner = user?.coverURL1024x ?? user?.coverURL1920x;
+    const externalLinks = user?.externalLinks ?? {};
+    const links = {};
+    Object
+        .keys(externalLinks)
+        .forEach(key => {
+        if (externalLinks[key]) {
+            links[key.replace('URL', '')] = externalLinks[key];
+        }
+    });
+    return new PlatformChannel({
+        id: new PlatformID(PLATFORM, user?.id, config?.id, PLATFORM_CLAIMTYPE),
+        name: user?.displayName,
+        thumbnail: user?.avatar?.url,
+        banner,
+        subscribers: user?.metrics?.engagement?.followers?.edges[0]?.node?.total,
+        description: user?.description,
+        url,
+        links,
+    });
+};
+source.getChannelContents = function (url) {
+    return getChannelPager({ url, page_size: 20, page: 1 });
+};
+//Video
+source.isContentDetailsUrl = function (url) {
+    return url.startsWith(BASE_URL_VIDEO);
+};
+source.getContentDetails = function (url) {
+    return getSavedVideo(url);
+};
+//Playlist
+source.isPlaylistUrl = (url) => {
+    var isPlaylist = url.startsWith(BASE_URL_PLAYLIST);
+    return isPlaylist;
+};
+source.searchPlaylists = (query, type, order, filters) => {
+    return searchPlaylists({ q: query, type, order, filters });
+};
+source.getPlaylist = (url) => {
+    const xid = url.split('/').pop();
+    const variables = {
+        "xid": xid
+    };
+    const jsonResponse = executeGqlQuery({
+        operationName: 'PLAYLIST_VIDEO_QUERY',
+        variables,
+        query: queries.PLAYLIST_DETAILS_QUERY
+    });
+    const videos = jsonResponse?.data?.collection?.videos?.edges.map(edge => {
+        const resource = edge.node;
+        const opts = {
+            id: new PlatformID(PLATFORM, resource.id, config.id, PLATFORM_CLAIMTYPE),
+            name: resource.title,
+            thumbnails: new Thumbnails([
+                new Thumbnail(resource?.thumbnail?.url, 0)
+            ]),
+            author: new PlatformAuthorLink(new PlatformID(PLATFORM, resource.creatorId, config.id, PLATFORM_CLAIMTYPE), resource.creator.displayName, `${BASE_URL}/${resource.creator.name}`, resource.creator.avatar.url ?? "", 0),
+            uploadDate: parseInt(new Date(resource.createdAt).getTime() / 1000),
+            datetime: parseInt(new Date(resource.createdAt).getTime() / 1000),
+            url: resource.url,
+            duration: resource.duration,
+            viewCount: resource?.viewCount ?? 0,
+            isLive: false
+        };
+        return opts;
+    });
+    const playlist = jsonResponse?.data?.collection;
+    return new PlatformPlaylistDetails({
+        url: `${BASE_URL_PLAYLIST}/${playlist?.xid}`,
+        id: new PlatformID(PLATFORM, playlist?.xid, config.id),
+        author: new PlatformAuthorLink(new PlatformID(PLATFORM, playlist.creator.id, config.id, PLATFORM_CLAIMTYPE), playlist.creator.displayName, `${BASE_URL}/${playlist.creator.name}`, playlist.creator.avatar.url ?? "", 0),
+        name: playlist.name,
+        thumbnail: playlist?.thumbnail?.url,
+        videoCount: playlist?.metrics?.engagement?.videos?.edges[0]?.node?.total,
+        contents: new VideoPager(videos)
+    });
+};
+function 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;
+}
+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,
+        "shouldIncludeTopics": false,
+        "shouldIncludeVideos": false,
+        "shouldIncludeLives": false,
+        "page": context.page,
+        "limit": 20
+    };
+    const jsonResponse = executeGqlQuery({
+        operationName: 'SEARCH_QUERY',
+        variables: variables,
+        query: queries.MAIN_SEARCH_QUERY,
+        headers: undefined
+    });
+    var searchResults = jsonResponse?.data?.search?.playlists?.edges?.map(edge => {
+        const playlist = edge.node;
+        return new PlatformPlaylist({
+            url: `${BASE_URL_PLAYLIST}/${playlist?.xid}`,
+            id: new PlatformID(PLATFORM, playlist?.xid, config.id),
+            author: new PlatformAuthorLink(new PlatformID(PLATFORM, playlist.creator.id, config.id, PLATFORM_CLAIMTYPE), playlist.creator.displayName, `${BASE_URL}/${playlist.creator.name}`, playlist.creator.avatar.url ?? "", 0),
+            name: playlist.name,
+            thumbnail: playlist?.thumbnail?.url,
+            videoCount: playlist?.metrics?.engagement?.videos?.edges[0]?.node?.total,
+        });
+    });
+    const hasMore = jsonResponse?.data?.search?.playlists?.pageInfo?.hasNextPage;
+    if (!searchResults || !searchResults?.length) {
+        return new PlaylistPager([]);
+    }
+    var params = {
+        query: context.q,
+        sort: context.sort,
+        filters: context.filters,
+    };
+    return new SearchPlaylistPager(searchResults, hasMore, params, context.page);
+}
+//Internals
+function getChannelNameFromUrl(url) {
+    const channel_name = url.split('/').pop();
+    return channel_name;
+}
+function isUsernameUrl(url) {
+    // Define the regex pattern to match the username URL
+    var regex = new RegExp('^' + BASE_URL.replace(/\./g, '\\.') + '/[^/]+$');
+    // Test the URL against the regex pattern
+    return regex.test(url);
+}
+function getAnonymousUserTokenSingleton() {
+    // Check if the anonymous user token is available and not expired
+    if (AUTHORIZATION_TOKEN_ANONYMOUS_USER) {
+        const isTokenValid = AUTHORIZATION_TOKEN_ANONYMOUS_USER_EXPIRATION_DATE && new Date().getTime() < AUTHORIZATION_TOKEN_ANONYMOUS_USER_EXPIRATION_DATE;
+        if (isTokenValid) {
+            return AUTHORIZATION_TOKEN_ANONYMOUS_USER;
+        }
+    }
+    // Prepare the request body for obtaining a new token
+    const body = util.objectToUrlEncodedString({
+        client_id: CLIENT_ID,
+        client_secret: CLIENT_SECRET,
+        grant_type: 'client_credentials'
+    });
+    // Make the HTTP POST request to the authorization API
+    const res = http.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'
+    }, true);
+    // Check if the response code indicates success
+    if (res.code !== 200) {
+        console.error('Failed to get token', res);
+        throw new ScriptException("", "Failed to get token: " + res.code + " - " + res.body);
+    }
+    // Parse the response JSON to extract the token information
+    const json = JSON.parse(res.body);
+    // Ensure the response contains the necessary token information
+    if (!json.token_type || !json.access_token) {
+        console.error('Invalid token response', res);
+        throw new ScriptException("", 'Invalid token response: ' + res.body);
+    }
+    // Store the token and its expiration date
+    AUTHORIZATION_TOKEN_ANONYMOUS_USER = `${json.token_type} ${json.access_token}`;
+    AUTHORIZATION_TOKEN_ANONYMOUS_USER_EXPIRATION_DATE = new Date().getTime() + (json.expires_in * 1000);
+    return AUTHORIZATION_TOKEN_ANONYMOUS_USER;
+}
+function getPreferredCountry() {
+    const countryName = constants.countryNames[_settings?.preferredCountry];
+    const code = constants.countryNamesToCode[countryName];
+    const preferredCountry = (code || X_DM_Preferred_Country || '').toLowerCase();
+    return preferredCountry;
+}
+function getVideoPager(params, page) {
+    const count = 20;
+    if (!params) {
+        params = {};
+    }
+    params = { ...params, count };
+    const headersToAdd = {
+        "User-Agent": USER_AGENT,
+        // "Accept-Language": Accept_Language,
+        "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(),
+        "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({
+            operationName: 'SEACH_DISCOVERY_QUERY',
+            variables: { "shouldQueryPromotedHashtag": false },
+            query: queries.HOME_QUERY,
+            headers: headersToAdd
+        }, true);
+    }
+    catch (error) {
+        return new VideoPager([], false, { params });
+    }
+    var results = obj?.data?.home?.neon?.sections?.edges[0]?.node?.components?.edges
+        // ?.filter(edge => edge?.node?.__typename === 'Video')
+        ?.filter(edge => edge?.node?.id)
+        ?.map(edge => {
+        const v = edge.node;
+        return ToPlatformVideo({
+            id: v.id,
+            name: v.title ?? "",
+            thumbnail: v.thumbnail?.url ?? "",
+            createdAt: v.createdAt,
+            creatorId: v?.creator?.id,
+            creatorName: v?.creator?.name,
+            creatorDisplayName: v.creator?.displayName,
+            creatorAvatar: v?.creator?.avatar?.url ?? "",
+            creatorUrl: `${BASE_URL}/${v.creator?.name}`,
+            duration: v.duration,
+            viewCount: 0,
+            url: `${BASE_URL_VIDEO}/${v.xid}`,
+            isLive: false,
+            description: v?.description ?? '',
+        });
+    });
+    const hasMore = obj?.data?.home?.neon?.sections?.edges[0]?.node?.components?.pageInfo?.hasNextPage ?? false;
+    return new SearchPagerAll(results, hasMore, params, page);
+}
+function getChannelPager(context) {
+    const url = context.url;
+    const channel_name = getChannelNameFromUrl(url);
+    const json = executeGqlQuery({
+        operationName: 'CHANNEL_VIDEOS_QUERY',
+        variables: {
+            "channel_name": channel_name,
+            "sort": "recent",
+            "page": context.page ?? 1,
+            "allowExplicit": true,
+            "first": context.page_size ?? 30
+        },
+        query: queries.CHANNEL_VIDEOS_BY_CHANNEL_NAME
+    });
+    const edges = json?.data?.channel?.channel_videos_all_videos?.edges ?? [];
+    let videos = edges.map((edge) => {
+        return ToPlatformVideo({
+            id: edge.node.id,
+            name: edge.node.title,
+            thumbnail: edge?.node?.thumbnail.url ?? "",
+            createdAt: edge?.node?.createdAt,
+            creatorId: edge?.node?.creator?.id,
+            creatorDisplayName: edge?.node?.creator?.displayName,
+            creatorName: edge.node.creator.name,
+            creatorAvatar: edge?.node?.creator?.avatar?.url,
+            creatorUrl: `${BASE_URL}/${edge?.node?.creator?.name}`,
+            duration: edge.node.duration,
+            url: `${BASE_URL_VIDEO}/${edge.node.name}`,
+            viewCount: edge.node.metrics.engagement.likes.totalCount,
+            isLive: false
+        });
+    });
+    if (edges.length > 0) {
+        context.page++;
+    }
+    return new ChannelVideoPager(context, videos, json?.data?.channel?.channel_videos_all_videos?.pageInfo?.hasNextPage);
+}
+function ToPlatformVideo(resource) {
+    const opts = {
+        id: new PlatformID(PLATFORM, resource.id, config.id, PLATFORM_CLAIMTYPE),
+        name: resource.name,
+        thumbnails: new Thumbnails([new Thumbnail(resource.thumbnail, 0)]),
+        author: new PlatformAuthorLink(new PlatformID(PLATFORM, resource.creatorId, config.id, PLATFORM_CLAIMTYPE), resource.creatorDisplayName, resource.creatorUrl, resource.creatorAvatar ?? "", 0),
+        uploadDate: parseInt(new Date(resource.createdAt).getTime() / 1000),
+        url: resource.url,
+        duration: resource.duration,
+        viewCount: resource.viewCount,
+        isLive: resource.isLive
+    };
+    return new PlatformVideo(opts);
+}
+function 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;
+}
+function 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;
+}
+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,
+        "shouldIncludeTopics": false,
+        "shouldIncludeVideos": true,
+        "shouldIncludeLives": true,
+        "page": context.page ?? 1,
+        "limit": 20
+    };
+    const jsonResponse = executeGqlQuery({
+        operationName: 'SEARCH_QUERY',
+        variables: variables,
+        query: queries.MAIN_SEARCH_QUERY,
+        headers: undefined
+    });
+    const results = [];
+    const all = [
+        ...(jsonResponse?.data?.search?.videos?.edges ?? []),
+        ...(jsonResponse?.data?.search?.lives?.edges ?? [])
+    ];
+    for (const edge of all) {
+        const sv = edge.node;
+        const isLive = sv.isOnAir == true;
+        const viewCount = isLive ? sv.audienceCount : sv?.stats?.views?.total;
+        var video = ToPlatformVideo({
+            id: sv.id,
+            name: sv.title,
+            thumbnail: sv?.thumbnail?.url,
+            createdAt: sv.createdAt,
+            creatorId: sv.creator?.id,
+            creatorName: sv.creator?.name,
+            creatorDisplayName: sv.creator?.displayName,
+            creatorUrl: `${BASE_URL}/${sv?.creator?.name}`,
+            creatorAvatar: sv.creator?.avatar?.url ?? "",
+            duration: sv.duration,
+            viewCount,
+            url: `${BASE_URL_VIDEO}/${sv.xid}`,
+            isLive,
+            description: sv?.description ?? '',
+        });
+        results.push(video);
+    }
+    //results, hasMore, path, params, page
+    var params = {
+        query: context.q,
+        sort: context.sort,
+        filters: context.filters,
+    };
+    return new SearchPagerAll(results, jsonResponse?.data?.search?.videos?.pageInfo?.hasNextPage, params, context.page);
+}
+function executeGqlQuery(opts, addAuthorization) {
+    const headersToAdd = opts.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"
+    };
+    {
+        headersToAdd.Authorization = getAnonymousUserTokenSingleton();
+    }
+    const gql = JSON.stringify({
+        operationName: opts.operationName,
+        variables: opts.variables,
+        query: opts.query,
+    });
+    const res = http.POST(BASE_URL_API, gql, headersToAdd);
+    if (!res.isOk) {
+        console.error('Failed to get token', res);
+        throw new ScriptException("Failed to get token", res);
+    }
+    return JSON.parse(res.body);
+}
+function checkHLS(url, headersToAdd, use_authenticated = false) {
+    // const resp = http.GET(url, headersToAdd, true);
+    var resp = http.GET(url, headersToAdd, use_authenticated);
+    if (!resp.isOk) {
+        throw new UnavailableException('This content is not available');
+    }
+}
+function getSavedVideo(url) {
+    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=_`;
+    var headers1 = {
+        "User-Agent": USER_AGENT,
+        "Accept": "*/*",
+        // "Accept-Language": Accept_Language,
+        // "Accept-Encoding": "gzip, deflate, br, zstd",
+        "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";
+    }
+    var player_metadataResponse = http.GET(player_metadata_url, headers1);
+    if (!player_metadataResponse.isOk) {
+        throw new UnavailableException('Unable to get player metadata');
+    }
+    var player_metadata = JSON.parse(player_metadataResponse.body);
+    if (player_metadata.error) {
+        if (player_metadata.error.code && errorTypes[player_metadata.error.code] !== undefined) {
+            throw new UnavailableException(errorTypes[player_metadata.error.code]);
+        }
+        throw new UnavailableException('This content is not available');
+    }
+    const hls_url = player_metadata?.qualities?.auto[0]?.url;
+    checkHLS(hls_url, headers1);
+    const videoDetailsRequestHeaders = {
+        "Content-Type": "application/json",
+        "User-Agent": USER_AGENT,
+        "Accept": "*/*, */*",
+        // "Accept-Language": Accept_Language,
+        "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(),
+        "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",
+        "Authorization": getAnonymousUserTokenSingleton()
+    };
+    const videoDetailsRequestBody = JSON.stringify({
+        "operationName": "WATCHING_VIDEO",
+        "variables": {
+            "xid": id,
+            "isSEO": false
+        },
+        "query": queries.VIDE_DETAILS_QUERY
+    });
+    const video_details_response = http.POST(BASE_URL_API, videoDetailsRequestBody, videoDetailsRequestHeaders);
+    if (video_details_response.code != 200) {
+        throw new UnavailableException('Failed to get video details');
+    }
+    const video_details = JSON.parse(video_details_response.body);
+    const sources = [
+        new HLSSource({
+            name: 'source',
+            duration: player_metadata?.duration,
+            url: hls_url,
+            // priority: true,
+        })
+    ];
+    const thumbnail = player_metadata.thumbnailx720
+        ?? player_metadata.thumbnailx240
+        ?? player_metadata.thumbnailx120
+        ?? player_metadata.thumbnailx60;
+    const video = video_details?.data?.video;
+    var test = {
+        id: new PlatformID(PLATFORM, id, config.id, PLATFORM_CLAIMTYPE),
+        name: player_metadata.title,
+        thumbnails: new Thumbnails([new Thumbnail(thumbnail, 0)]),
+        author: new PlatformAuthorLink(new PlatformID(PLATFORM, player_metadata?.owner?.id, config.id, PLATFORM_CLAIMTYPE), player_metadata?.owner?.screenname, player_metadata?.owner?.url, video?.creator?.avatar?.url ?? '', 0 //dsubscribers
+        ),
+        // datetime: new Date(video?.createdAt).getTime(),
+        uploadDate: parseInt(new Date(video.createdAt).getTime() / 1000),
+        duration: player_metadata?.duration,
+        viewCount: video?.stats?.views?.total, //TODO: get view count
+        url: player_metadata.url,
+        isLive: false,
+        description: video?.description, //TODO: get description
+        video: new VideoSourceDescriptor(sources),
+        dash: null,
+        live: null,
+        hls: null,
+    };
+    return new PlatformVideoDetails(test);
+}
+function getSearchChannelPager(context) {
+    const variables = {
+        query: context.q,
+        page: context.page ?? 1,
+        limit: 20
+    };
+    const json = executeGqlQuery({
+        operationName: "SEARCH_QUERY",
+        variables,
+        query: queries.SEARCH_CHANNEL
+    });
+    const results = json?.data?.search?.channels?.edges.map(edge => {
+        const c = edge.node;
+        return new PlatformChannel({
+            id: new PlatformID(PLATFORM, c.id, config.id, PLATFORM_CLAIMTYPE),
+            name: c.displayName,
+            thumbnail: c?.avatar?.url,
+            subscribers: c?.metrics?.engagement?.followers?.edges[0]?.node?.total ?? 0,
+            url: `${BASE_URL}/${c.name}`,
+            links: [],
+            banner: null,
+            description: c.description,
+        });
+    });
+    var params = {
+        query: context.q,
+    };
+    return new SearchChannelPager(results, json?.data?.search?.channels?.pageInfo?.hasNextPage, params, context.page);
+}
+//Pagers
+class SearchPagerAll extends VideoPager {
+    /**
+     * @param {import("./types.d.ts").SearchContext} context the query params
+     * @param {(PlatformVideo | PlatformChannel)[]} results the initial results
+    */
+    constructor(results, hasMore, params, page) {
+        super(results, hasMore, { params, page });
+    }
+    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 getSearchPagerAll(opts);
+    }
+}
+class SearchChannelPager extends ChannelPager {
+    constructor(results, hasNextPage, params, page) {
+        super(results, hasNextPage, { params, page });
+    }
+    nextPage() {
+        const opts = { q: this.context.params.query, page: this.context.page += 1 };
+        return getSearchChannelPager(opts);
+    }
+}
+class ChannelVideoPager extends VideoPager {
+    /**
+     * @param {import("./types.d.ts").URLContext} context the context
+     * @param {PlatformVideo[]} results the initial results
+     * @param {boolean} hasNextPage if there is a next page
+     */
+    constructor(context, results, hasNextPage) {
+        super(results, hasNextPage, context);
+    }
+    nextPage() {
+        return getChannelPager(this.context);
+    }
+}
+class SearchPlaylistPager extends VideoPager {
+    /**
+     * @param {import("./types.d.ts").SearchContext} context the query params
+     * @param {(PlatformVideo | PlatformChannel)[]} results the initial results
+    */
+    constructor(results, hasMore, params, page) {
+        super(results, hasMore, { params, page });
+    }
+    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 searchPlaylists(opts);
+    }
+}
+log("LOADED");
diff --git a/build/Readme.md b/build/Readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..b209bbabe8a6bb92ab06a0668c6503186e83ead5
--- /dev/null
+++ b/build/Readme.md
@@ -0,0 +1,18 @@
+# Dailymotion plugin for Grayjay - work in progress
+
+
+Click [here](https://stefancruz.github.io/GrayjayDailymotion/index.html) to install into Grayjay
+
+
+npm install
+
+optional to update graphql types:
+npm run get-token
+npm run codegen
+
+npm run build
+
+# notes:
+- Content of the 'build' folder should not be manually changed because 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
diff --git a/build/dailymotion.png b/build/dailymotion.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce8159b0f64805bc1a684dfc4355d385a58958a0
Binary files /dev/null and b/build/dailymotion.png differ
diff --git a/build/index.html b/build/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..9923fd1006655e37a63d8f7aba21a26896c52abf
--- /dev/null
+++ b/build/index.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Install plugin</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            margin: 0;
+            padding: 0;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            height: 100vh;
+            background-color: #f5f5f5;
+        }
+
+        p {
+            margin: 20px;
+            text-align: center;
+            font-size: 1.2em;
+            color: #333;
+        }
+
+        img {
+            max-width: 100%;
+            height: auto;
+            margin: 20px;
+        }
+
+        @media (min-width: 600px) {
+            p {
+                font-size: 1.5em;
+            }
+        }
+    </style>
+    <script>
+        window.onload = function () {
+            var isSupported = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
+            if (isSupported) {
+                window.location.href = "grayjay://plugin/https://stefancruz.github.io/GrayjayDailymotion/DailymotionConfig.json";
+            } else {
+                console.log("Currently this link is supported on mobile devices only. Use the QR code to install it on Grayjay.");
+            }
+        }
+    </script>
+</head>
+
+<body>
+    <p>Currently this link is supported on mobile devices only. Use the QR code to install the plugin on Grayjay.</p>
+    <img style="width: 10em;" src="qr.PNG" alt="QR Code to install Grayjay plugin">
+</body>
+
+</html>
\ No newline at end of file
diff --git a/build/qr.PNG b/build/qr.PNG
new file mode 100644
index 0000000000000000000000000000000000000000..8184c3c39b1288b17adb68a222f3e7aebf6dc836
Binary files /dev/null and b/build/qr.PNG differ
diff --git a/rollup.config.js b/rollup.config.js
index afcf059fb2c2e5c6e2afaccacd67a4d2c886cf8c..9b636730b2681ffcfa5ae9ebdbfa4829b567758b 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -4,13 +4,13 @@ const typescript = require('@rollup/plugin-typescript');
 const copy = require('rollup-plugin-copy');
 const del = require('rollup-plugin-delete');
 
-const dest = 'build'; // Change this to your output folder
+const dest = './build'; // Output folder
 
 module.exports = {
-  input: './src/DailymotionScript.ts', // Change this to your entry file
+  input: 'src/DailymotionScript.ts', // Entry file
   output: {
-    file: 'build/DailymotionScript.js',
-    format: 'cjs', // Use IIFE format to avoid using require
+    file: `${dest}/DailymotionScript.js`,
+    format: 'cjs', // Use IIFE format for browser compatibility
     sourcemap: false
   },
   plugins: [
@@ -20,10 +20,11 @@ module.exports = {
     typescript({ tsconfig: './tsconfig.json' }),
     copy({
       targets: [
-        { src: './DailymotionConfig.json', dest },
-        { src: './assets/dailymotion.png', dest},
-        { src: './assets/index.html', dest },
-        { src: './assets/qr.PNG', dest},
+        { src: 'DailymotionConfig.json', dest },
+        { src: 'assets/dailymotion.png', dest },
+        { src: 'assets/index.html', dest },
+        { src: 'assets/qr.PNG', dest },
+        { src: 'Readme.md', dest },
       ]
     })
   ]
diff --git a/sign.sh b/sign.sh
index 7cf0905297023e2357841fb3c93515a82d8c5ea6..b79928555cbf09b73b98ee056ab4e7925a70f115 100644
--- a/sign.sh
+++ b/sign.sh
@@ -16,14 +16,39 @@ fi
 
 # Generate signature for the provided JS file
 SIGNATURE=$(cat $JS_FILE_PATH | openssl dgst -sha512 -sign tmp_private_key.pem | base64 -w 0)
+if [ $? -ne 0 ]; then
+  echo "Failed to generate signature."
+  rm tmp_private_key.pem
+  exit 1
+fi
 
 # Extract public key from the temporary private key file
 PUBLIC_KEY=$(openssl rsa -pubout -outform DER -in tmp_private_key.pem 2>/dev/null | openssl pkey -pubin -inform DER -outform PEM | tail -n +2 | head -n -1 | tr -d '\n')
+if [ $? -ne 0 ]; then
+  echo "Failed to extract public key."
+  rm tmp_private_key.pem
+  exit 1
+fi
 
+echo "SIGNATURE: $SIGNATURE"
 echo "PUBLIC_KEY: $PUBLIC_KEY"
 
 # Remove temporary key files
 rm tmp_private_key.pem
 
 # Update "scriptSignature" and "scriptPublicKey" fields in Config JSON
-cat $CONFIG_FILE_PATH | jq --arg signature "$SIGNATURE" --arg publicKey "$PUBLIC_KEY" '. + {scriptSignature: $signature, scriptPublicKey: $publicKey}' > temp_config.json && mv temp_config.json $CONFIG_FILE_PATH
\ No newline at end of file
+jq --arg signature "$SIGNATURE" --arg publicKey "$PUBLIC_KEY" '. + {scriptSignature: $signature, scriptPublicKey: $publicKey}' "$CONFIG_FILE_PATH" > temp_config.json
+if [ $? -ne 0 ]; then
+  echo "Failed to update JSON file."
+  rm temp_config.json
+  exit 1
+fi
+
+mv temp_config.json "$CONFIG_FILE_PATH"
+if [ $? -ne 0 ]; then
+  echo "Failed to replace the original JSON file."
+  rm temp_config.json
+  exit 1
+fi
+
+echo "JSON file updated successfully."
diff --git a/tsconfig.json b/tsconfig.json
index f342065ac6408f47f18bfb1754273b57f28e66b3..c4f80feabb31c456d590ec394a593b3f85b20e0c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -53,7 +53,7 @@
     "sourceMap": true, /* Create source map files for emitted JavaScript files. */
     // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
     // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
-    "outDir": "./dist", /* Specify an output folder for all emitted files. */
+    "outDir": "./build", /* Specify an output folder for all emitted files. */
     "removeComments": false, /* Disable emitting comments. */
     "noEmit": true,                                   /* Disable emitting files from a compilation. */
     // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */