Asurascans Bookmarking System

A bookmarking system for Manga reading sites with signup/login functionality to save your info(Bookmarks e.t.c) Across ALL Devices. Keep Track of your BookMarks in AsuraScans amongst other websites.

// ==UserScript==
// @name        Asurascans Bookmarking System
// @version     3.4
// @author      longkidkoolstar
// @namespace   https://github.com/longkidkoolstar
// @icon        https://images.vexels.com/media/users/3/223055/isolated/preview/eb3fcec56c95c2eb7ded9201e51550a2-bookmark-icon-flat-by-vexels.png
// @description A bookmarking system for Manga reading sites with signup/login functionality to save your info(Bookmarks e.t.c) Across ALL Devices. Keep Track of your BookMarks in AsuraScans amongst other websites.
// @require     https://greasyfork.org/scripts/468394-itsnotlupus-tiny-utilities/code/utils.js
// @match       https://www.asurascans.com/*
// @match       https://asura.gg/*
// @match       https://asuracomics.gg/*
// @match       https://asura.nacm.xyz/*
// @match       https://asuracomics.com/*
// @match       https://asuratoon.com/*
// @match       https://asuracomic.net/*
// @match       https://void-scans.com/*
// @match       https://lunarscan.org/*
// @match       https://flamescans.org/*
// @match       https://luminousscans.com/*
// @match       https://luminousscans.net/*
// @match       https://cosmic-scans.com/*
// @match       https://nightscans.org/*
// @match       https://nightscans.net/*
// @match       https://freakscans.com/*
// @match       https://reaperscans.fr/*
// @match       https://manhwafreak.com/*
// @match       https://manhwa-freak.com/*
// @match       https://manhwafreak-fr.com/*
// @match       https://realmscans.to/*
// @match       https://mangastream.themesia.com/*
// @match       https://manga-scans.com/*
// @match       https://mangakakalot.so/*
// @match       https://reaperscans.com/*
// @match       https://flamecomics.com/*
// @match       https://hivetoon.com/*
// @match       https://night-scans.com/*
// @license     MIT
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.xmlHttpRequest
// ==/UserScript==

console.log('User script started');

const scriptVersion = '3.4';

// Check if user is logged in
(async () => {
  const username = await GM.getValue('username');
  const password = await GM.getValue('password');

  if (username && password) {
    console.log('User is logged in');
    window.addEventListener('load', saveDeviceType);
  } else {
    console.log('User is not logged in');
    // Prompt user to enter username and password
    const username = prompt('Enter your username:');
    const password = prompt('Enter your password:');
    // Save username and password to Tampermonkey
    await GM.setValue('username', username);
    await GM.setValue('password', password);
    // Save username and password to JSONBin
    saveUserCredentialsToJSONBin(username, password);
  }
})();


// Function to get a standardized hostname for Asura-related domains
function getStandardizedHostname(hostname) {
  if (hostname.includes('asura') || hostname.includes('asuratoon')) {
    return 'asurascans.com';
  }
  return hostname;
}

// Function to get the current Asura domain
function getCurrentAsuraDomain() {
  const hostname = window.location.hostname;
  if (hostname.includes('asura') || hostname.includes('asuratoon')) {
    return hostname;
  }
  return null;
}

// Below is to delete the new ads that Asura has been implementing.
//--------------
(function() {
  // Function to delete the element
  function deleteElement() {
    var element = document.getElementById('noktaplayercontainer');
    if (element) {
      element.remove();
    }
  }

  // DOM listener to check for element existence
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      if (mutation.type === 'childList' || mutation.type === 'subtree') {
        deleteElement();
      }
    });
  });

  // Start observing the document body for changes
  observer.observe(document.body, { childList: true, subtree: true });
})();

// Function to detect and save the device type to JSONBin and local storage
async function saveDeviceType() {
  const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

  // Get the saved device type from local storage
  const savedDeviceType = localStorage.getItem('deviceType');

  if (savedDeviceType !== isMobile.toString()) {
    // Device type has changed, update JSONBin and local storage
    const username = await GM.getValue('username');
    const password = await GM.getValue('password');
    const deviceData = { deviceType: isMobile.toString() };

    // Save device data to JSONBin
    GM.xmlHttpRequest({
      method: 'GET',
      url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
      headers: {},
      onload: function (response) {
        const existingData = JSON.parse(response.responseText);
        if (existingData.users[username].password === password) {
          existingData.users[username].variables.deviceType = isMobile.toString();
          GM.xmlHttpRequest({
            method: 'PUT',
            url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
            headers: {
              'Content-Type': 'application/json',
            },
            data: JSON.stringify(existingData),
          });
        }
      }
    });

    // Save device data to local storage
    localStorage.setItem('deviceType', isMobile.toString());

    // Update variables in local storage
    const jsonBinData = getJSONBinData();
    if (jsonBinData) {
      saveVariablesFromJSONBin(jsonBinData);
      updateLocalStorageVariables(jsonBinData);
    }
  }

  // Log the device type to the console
  console.log('Device Type:', isMobile ? 'Mobile' : 'Computer');
}

//------------------------------------------------------------------------ Uncomment if Asura changes Domain name again.

function updateLastChaptersDomain() {
    const oldDomain = "https://asuracomics.com/";
    const newDomain = "https://asuracomics.gg/";

    const lastChapters = JSON.parse(localStorage.getItem("last-chapter"));
    if (lastChapters) {
        for (const mangaId in lastChapters) {
           if (lastChapters.hasOwnProperty(mangaId)) {
                const oldUrl = lastChapters[mangaId];
               if (oldUrl.startsWith(oldDomain)) {
                    const newUrl = oldUrl.replace(oldDomain, newDomain);
                    lastChapters[mangaId] = newUrl;
                }
            }
        }
       localStorage.setItem("last-chapter", JSON.stringify(lastChapters));
        console.log("Last chapters updated to the new domain.");
    } else {
        console.log("No last chapters found.");
    }
}

// Call the function to update the URLs
updateLastChaptersDomain();

function updateLastChaptersDomainif() {
    const oldDomain = "https://asuracomics.gg/";
    const newDomain = "https://asuratoon.com/";

    const lastChapters = JSON.parse(localStorage.getItem("last-chapter"));
    if (lastChapters) {
        for (const mangaId in lastChapters) {
           if (lastChapters.hasOwnProperty(mangaId)) {
                const oldUrl = lastChapters[mangaId];
               if (oldUrl.startsWith(oldDomain)) {
                    const newUrl = oldUrl.replace(oldDomain, newDomain);
                    lastChapters[mangaId] = newUrl;
                }
            }
        }
       localStorage.setItem("last-chapter", JSON.stringify(lastChapters));
        console.log("Last chapters updated to the new domain.");
    } else {
        console.log("No last chapters found.");
    }
}

// Call the function to update the URLs
updateLastChaptersDomainif();
//-----------------------------------------------------------------------------

// Wait for the page to load
window.addEventListener('load', async function () {
  // Fetch JSONBin data and save variables
  const jsonBinData = getJSONBinData();
  if (jsonBinData) {
    saveVariablesFromJSONBin(jsonBinData);
  }

  // Check if it's a manga page and display the last read chapter if available
  if (window.location.pathname.startsWith('/manga/')) {
    displayLastReadChapter();
  }
});

// Function to fetch JSONBin data
function getJSONBinData() {
  const request = new XMLHttpRequest();
  request.open('GET', `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`, false);

  request.send(null);
  if (request.status === 200) {
    return JSON.parse(request.responseText);
  }
  return null;
}

// Save variables from JSONBin to GM storage
async function saveVariablesFromJSONBin(data) {
  const userData = data.users[await GM.getValue('username')];
  if (userData) {
    const variables = userData.variables;
    if (variables) {
      for (const key in variables) {
        if (variables.hasOwnProperty(key)) {
          await GM.setValue(key, variables[key]);
        }
      }
    }
  }
}

// Modify the saveVariablesToJSONBin function
async function saveVariablesToJSONBin() {
  const username = await GM.getValue('username');
  const password = await GM.getValue('password');
  const originalWebsite = window.location.hostname;
  const standardizedWebsite = getStandardizedHostname(originalWebsite);
  const newVariables = {};

  // Add specific variables to the newVariables object
  const requiredVariables = ['last-chapter', 'bookmark', 'deviceType', 'scriptVersion'];

  for (const key of requiredVariables) {
    const value = localStorage.getItem(key);
    if (value !== null) {
      newVariables[key] = value;
    }
  }

  // Fetch existing data from JSONBin
  GM.xmlHttpRequest({
    method: 'GET',
    url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
    headers: {},
    onload: function (response) {
      const responseData = JSON.parse(response.responseText);
      const userData = responseData.users[username];
      if (userData && userData.password === password) {
        // Merge newVariables with the existing variables for the standardized website
        userData.variables[standardizedWebsite] = {
          ...(userData.variables[standardizedWebsite] || {}),
          ...newVariables
        };

        // Save the merged data back to JSONBin
        saveUserDataToJSONBin(userData);
      } else {
        console.log('Invalid credentials or user data not found.');
      }
    }
  });
}


function checkAndUpdateScriptVersion() {
  const storedScriptVersion = localStorage.getItem('scriptVersion');
  if (storedScriptVersion !== scriptVersion) {
    // Script version has changed, update the stored version and save variables to JSONBin
    localStorage.setItem('scriptVersion', scriptVersion);
  }
}

// Call the function on page load
checkAndUpdateScriptVersion();

// Save user data to JSONBin
function saveUserDataToJSONBin(userData) {
  GM.xmlHttpRequest({
    method: 'GET',
    url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
    headers: {},
    onload: function (response) {
      const existingData = JSON.parse(response.responseText);

      // Create an empty users object if it doesn't exist
      existingData.users = existingData.users || {};

      // Store the user data within the users object using the username as the key
      existingData.users[userData.username] = userData;

      GM.xmlHttpRequest({
        method: 'PUT',
        url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
        headers: {
          'Content-Type': 'application/json',
        },
        data: JSON.stringify(existingData),
      });
    }
  });
}

// Save variables from the local website storage to Tampermonkey storage
async function saveVariablesToTampermonkeyStorage() {
  const variables = await GM.getValue('variables') || {};

  let hasChanges = false;
  for (const key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      if (!variables.hasOwnProperty(key) || variables[key] !== localStorage[key]) {
        variables[key] = localStorage[key];
        hasChanges = true;
      }
    }
  }
  if (hasChanges) {
    await GM.setValue('variables', variables);
    saveVariablesToJSONBin();
  }
}

// Add event listener to the bookmark <div> elements
const bookmarkDivs = document.querySelectorAll('div.bookmark');
bookmarkDivs.forEach(function (bookmarkDiv) {
  bookmarkDiv.addEventListener('click', function () {
    setTimeout(saveVariablesToTampermonkeyStorage, 1000); // Delay execution by 1000 milliseconds (1 second)
  });
});

//----------------------------------------------------------------------------------------------------------------------------------------------------All the Buttons

// Check for element before changing the z index to 0
const bgelement = document.querySelector("#post-225070 > div.bixbox.animefull > div.bigcontent.nobigcover");
if (bgelement) {
  bgelement.style.zIndex = "0";
}

// Autosaving functionality
let isAutosaveEnabled = GM.getValue('isAutosaveEnabled', false);
let autosaveInterval = GM.getValue('autosaveInterval', null);

// Function to toggle autosaving with persistence across webpages
async function toggleAutosave() {
  const button = document.getElementById('autosaveButton');
  if (isAutosaveEnabled) {
    clearInterval(autosaveInterval);
    isAutosaveEnabled = false;
    await GM.setValue('isAutosaveEnabled', false);
    await GM.setValue('autosaveInterval', null); // Clear the autosaveInterval on disable
    button.textContent = 'Toggle Autosave (Off)';
    console.log('Autosaving disabled.');
  } else {
    await GM.setValue('autosaveInterval', autosaveInterval);
    const currentTime = new Date().getTime();
    localStorage.setItem('autosaveStartTime', currentTime);
    autosaveInterval = setInterval(async function() {
      const startTime = parseInt(localStorage.getItem('autosaveStartTime'));
      const elapsedTime = (new Date().getTime() - startTime) / 1000;
      const remainingTime = 180 - elapsedTime; // Change autosave interval to 3 minutes
      localStorage.setItem('autosaveCountdown', remainingTime);
      if (remainingTime <= 0) {
        saveVariablesToJSONBin();
        saveVariablesToTampermonkeyStorage();
        localStorage.removeItem('autosaveStartTime');
        localStorage.removeItem('autosaveCountdown');
        localStorage.setItem('autosaveStartTime', new Date().getTime()); // Start another countdown from the last autosave
        console.log('Autosaved at: ' + new Date().toLocaleString()); // Log the time of the last save
      }
    }, 1000); // Update countdown every second
    isAutosaveEnabled = true;
    await GM.setValue('isAutosaveEnabled', true); // Save the state to Tampermonkey storage
    button.textContent = 'Toggle Autosave (On)';
    console.log('Autosaving enabled.');
  }
}

// Create a container for the buttons and style it
const menuContainer = document.createElement('div');
menuContainer.id = 'menuContainer';
menuContainer.classList.add('menu-container'); // Add a CSS class for styling
// Function to check if autosave is enabled and return a string for button text
async function checkAutosaveStatus() {
  const isAutosaveEnabled = await GM.getValue('isAutosaveEnabled', false);
  return isAutosaveEnabled ? 'Toggle Autosave (On)' : 'Toggle Autosave (Off)';
}

// Create "Save" button
const saveButton = document.createElement('button');
saveButton.id = 'saveButton';
saveButton.textContent = 'Save';
saveButton.classList.add('normal-button');
saveButton.addEventListener('click', handleSaveButtonClick);
saveButton.addEventListener('mouseover', function() {
  saveButton.style.opacity = '0.5'; // Dim the button when hovered over
});
saveButton.addEventListener('mouseout', function() {
  saveButton.style.opacity = '1'; // Restore the button's normal opacity
});
menuContainer.appendChild(saveButton);

// Create "Get Bookmarks" button
const getBookmarksButton = document.createElement('button');
getBookmarksButton.id = 'getBookmarksButton';
getBookmarksButton.textContent = 'Get Bookmarks';
getBookmarksButton.classList.add('normal-button');
getBookmarksButton.addEventListener('click', handleGetBookmarksButtonClick);
getBookmarksButton.addEventListener('mouseover', function() {
  getBookmarksButton.style.opacity = '0.5'; // Dim the button when hovered over
});
getBookmarksButton.addEventListener('mouseout', function() {
  getBookmarksButton.style.opacity = '1'; // Restore the button's normal opacity
});
menuContainer.appendChild(getBookmarksButton);

// Create "Autosave" button
const autosaveButton = document.createElement('button');
autosaveButton.id = 'autosaveButton';
(async () => {
  autosaveButton.textContent = await checkAutosaveStatus();
})();
autosaveButton.classList.add('normal-button');
autosaveButton.addEventListener('click', toggleAutosave);
autosaveButton.addEventListener('mouseover', function() {
  autosaveButton.style.opacity = '0.5'; // Dim the button when hovered over
});
autosaveButton.addEventListener('mouseout', function() {
  autosaveButton.style.opacity = '1'; // Restore the button's normal opacity
});
menuContainer.appendChild(autosaveButton);
// Append the menu container to the document body
document.body.appendChild(menuContainer);

// Create a dropdown button
const dropdownButton = document.createElement('button');
dropdownButton.id = 'dropdownButton';
dropdownButton.textContent = 'Menu ▼'; // ▼ is a down arrow character
dropdownButton.classList.add('menu-button', 'fixed-button'); // Add CSS classes for styling

// Append the dropdown button to the menu container
menuContainer.appendChild(dropdownButton);

// Toggle the visibility of the menu when the dropdown button is clicked
dropdownButton.addEventListener('click', function () {
  if (menuContainer.style.display === 'none') {
    menuContainer.style.display = 'block';
  } else {
    menuContainer.style.display = 'none';
  }
});

// Position the menu container in the bottom right corner
menuContainer.style.position = 'fixed';
menuContainer.style.bottom = '20px';
menuContainer.style.right = '20px';

const logoutButton = document.createElement('button');
logoutButton.id = 'logoutButton';
logoutButton.textContent = 'Logout';
logoutButton.style.cssText = 'background-color: rgb(145,63,226); border: 1px solid #ccc; border-radius: 3px; color: #fff; cursor: pointer; font-size: 12px; font-weight: bold; padding: 5px; position: fixed; bottom: 10px; right: 10px;';
logoutButton.classList.add('normal-button');
logoutButton.addEventListener('click', logout);
logoutButton.addEventListener('mouseover', () => logoutButton.style.opacity = '0.5');
logoutButton.addEventListener('mouseout', () => logoutButton.style.opacity = '1');

// Append the logout button to the menu container
menuContainer.appendChild(logoutButton);

// Append the dropdown button to the document body
document.body.appendChild(dropdownButton);

// Apply CSS styles to the buttons and container
const style = document.createElement('style');
style.textContent = `
  .menu-container {
    position: fixed;
    bottom: 20px;
    left: 20px;
    display: none;
    flex-direction: column;
    align-items: flex-start;
  }

  .menu-button {
    background-color: rgb(145, 63, 226);
    border: 1px solid #ccc;
    border-radius: 3px;
    color: #fff;
    opacity: 0.33;
    cursor: pointer;
    font-family: 'Open Sans, sans-serif';
    font-size: 9.15px;
    font-weight: bold;
    padding: 5px;
    margin: 5px 0;
  }

.normal-button {
  background-color: rgb(145, 63, 226);
    border: 1px solid #ccc;
    border-radius: 3px;
    color: #fff;
    cursor: pointer;
    font-family: 'Open Sans, sans-serif';
    font-size: 12px;
    font-weight: bold;
    padding: 5px;
    margin: 5px 0;
  transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}

  .menu-button:hover {
    opacity: 0.8;
  }
  .fixed-button {
    position: fixed;
    top: 20px; /* Adjust top position as needed */
    left: 20px; /* Adjust left position as needed */
  }

  /* Additional styles for the fixed button */
  .fixed-button {
    z-index: 999; /* Ensure the button is on top of other elements */
  }
`;

document.head.appendChild(style);

// Event handler for "Save" button
async function handleSaveButtonClick() {
  const username = await GM.getValue('username');
  const password = await GM.getValue('password');

  if (username && password) {
    // Save the script version along with other variables
    localStorage.setItem('scriptVersion', scriptVersion);
    saveVariablesToTampermonkeyStorage();
    saveVariablesToJSONBin(username, password);
  } else {
    console.log('User is not logged in.');
  }
}

async function handleGetBookmarksButtonClick() {
  const jsonBinData = getJSONBinData();
  if (jsonBinData) {
    const originalWebsite = window.location.hostname;
    const standardizedWebsite = getStandardizedHostname(originalWebsite);
    const userData = jsonBinData.users[await GM.getValue('username')];
    if (userData && userData.variables && userData.variables[standardizedWebsite]) {
      const variables = userData.variables[standardizedWebsite];
      updateLocalStorageVariables(variables);
    }
  }
}

function updateLocalStorageVariables(variables) {
  for (const key in variables) {
    if (variables.hasOwnProperty(key)) {
      // Check if the value is an object and convert it to a string
      const value = typeof variables[key] === 'object' ? JSON.stringify(variables[key]) : variables[key];
      localStorage.setItem(key, value);
    }
  }
}

function displayLastReadChapter() {
  const mangaNameElement = document.querySelector('h1.entry-title');
  if (!mangaNameElement) {
    console.log('Manga name element not found.');
    return;
  }

  const displayedMangaName = mangaNameElement.innerText.trim().toLowerCase().replace(/[^a-z0-9]/g, '');
  const lastChapter = JSON.parse(localStorage.getItem('last-chapter'));
  const currentAsuraDomain = getCurrentAsuraDomain();

  if (lastChapter && currentAsuraDomain) {
    for (const mangaId in lastChapter) {
      let storedChapterUrl = lastChapter[mangaId];
      const storedMangaNameMatch = storedChapterUrl.match(/\/(\d+-)?([^/]+)-chapter-\d+\//);
      if (storedMangaNameMatch) {
        const storedMangaName = storedMangaNameMatch[2].replace(/-/g, '').toLowerCase();

        console.log('Displayed Manga Name:', displayedMangaName);
        console.log('Stored Manga Name:', storedMangaName);

        if (displayedMangaName === storedMangaName || isCloseMatch(displayedMangaName, storedMangaName)) {
          console.log(`Match found: Displayed Manga Name - ${displayedMangaName}, Stored Manga Name - ${storedMangaName}`);
          
          // Update the stored URL with the current Asura domain
          const storedUrlObj = new URL(storedChapterUrl);
          storedUrlObj.hostname = currentAsuraDomain;
          storedChapterUrl = storedUrlObj.toString();

          const lastChapterElement = document.createElement('div');
          lastChapterElement.textContent = 'Last Read Chapter: ';
          const lastChapterLink = document.createElement('a');
          lastChapterLink.href = storedChapterUrl;
          lastChapterLink.textContent = 'Chapter ' + storedChapterUrl.split('/').pop().replace(/[^0-9]/g, '');
          lastChapterElement.appendChild(lastChapterLink);
          lastChapterElement.style.position = 'fixed';
          lastChapterElement.style.top = '50%';
          lastChapterElement.style.right = '10px';
          lastChapterElement.style.transform = 'translateY(-50%)';
          lastChapterElement.style.backgroundColor = 'rgb(145,63,226)';
          lastChapterElement.style.color = '#fff';
          lastChapterElement.style.padding = '5px';
          lastChapterElement.style.fontFamily = 'Open Sans, sans-serif';
          lastChapterElement.style.fontSize = '12px';
          lastChapterElement.style.fontWeight = 'bold';
          document.body.appendChild(lastChapterElement);

          return; // Stop looping after finding a match
        }
      }
    }
  }
  console.log('No last read chapter found for the manga.');
}


function levenshteinDistance(a, b) {
  const matrix = [];

  let i;
  for (i = 0; i <= b.length; i++) {
    matrix[i] = [i];
  }

  let j;
  for (j = 0; j <= a.length; j++) {
    matrix[0][j] = j;
  }

  for (i = 1; i <= b.length; i++) {
    for (j = 1; j <= a.length; j++) {
      if (b.charAt(i - 1) === a.charAt(j - 1)) {
        matrix[i][j] = matrix[i - 1][j - 1];
      } else {
        matrix[i][j] = Math.min(
          matrix[i - 1][j - 1] + 1,
          Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)
        );
      }
    }
  }

  return matrix[b.length][a.length];
}

function isCloseMatch(displayedMangaName, storedMangaName) {
  const similarityThreshold = 5; // Adjust this threshold as needed
  const distance = levenshteinDistance(displayedMangaName.toLowerCase(), storedMangaName.toLowerCase());

  return distance <= similarityThreshold;
}

// Test the function
const displayedName = "One Piece";
const storedName = "One Pece";
console.log(isCloseMatch(displayedName, storedName)); // Should return true or false based on the threshold

async function logout() {
  // Clear Tampermonkey storage
  await GM.setValue('username', '');
  await GM.setValue('password', '');
  await GM.setValue('variables', {});

  // Clear website's local storage
  localStorage.clear();

  // Refresh the page to log out
  location.reload();
}

function saveUserCredentialsToJSONBin(username, password) {
  // Validate that both username and password are not null or empty
  if (!username || !password) {
    console.log('Username and password cannot be empty.');
    alert('Username and Password cannot be empty. Please refresh the page or click the logout button to try again.');
    return; // Exit the function if either is empty
  }

  GM.xmlHttpRequest({
    method: 'GET',
    url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
    headers: {},
    onload: async function (response) {
      const existingData = JSON.parse(response.responseText);
      let isUsernameFound = false;
      let isPasswordCorrect = false;
      let isUsernameTaken = false;
      for (const user in existingData.users) {
        if (existingData.users.hasOwnProperty(user)) {
          if (existingData.users[user].username === username) {
            isUsernameFound = true;
            if (existingData.users[user].password === password) {
              isPasswordCorrect = true;
            }
            break;
          }
        }
      }
      if (isUsernameFound && isPasswordCorrect) {
        console.log('User logged in successfully.');

        // Replace variables in local storage with the ones from JSONBin
        const jsonBinData = getJSONBinData();
        if (jsonBinData) {
          const website = window.location.hostname;
          const userData = jsonBinData.users[await GM.getValue('username')];
          if (userData && userData.variables && userData.variables[website]) {
            const variables = userData.variables[website];
            updateLocalStorageVariables(variables);

            //-------------
            // Select the element you want to refresh
            const elementToRefresh = document.querySelector("#bookmark-pool");

            // Create a new XMLHttpRequest object
            const xhr = new XMLHttpRequest();

            // Set up the AJAX request
            xhr.open("GET", "https://asuratoon.com/bookmark/ #bookmark-pool", true);

            // Define the callback function to handle the AJAX response
            xhr.onload = function () {
              if (xhr.status === 200) {
                // Create a temporary element to hold the response
                const tempElement = document.createElement("div");
                tempElement.innerHTML = xhr.responseText;

                // Find the specific part of the response
                const refreshedElement = tempElement.querySelector("#bookmark-pool");

                // Replace the content of the element with the refreshed content and its children
                elementToRefresh.innerHTML = refreshedElement.innerHTML;

                // Append the refreshed children to the element
                while (refreshedElement.firstChild) {
                  elementToRefresh.appendChild(refreshedElement.firstChild);
                }
              }
            };

            // Send the AJAX request
            xhr.send();
            //------------
          }
        }
      } else if (!isUsernameFound || !isPasswordCorrect) {
        for (const user in existingData.users) {
          if (existingData.users.hasOwnProperty(user)) {
            if (existingData.users[user].username === username) {
              isUsernameTaken = true;
              break;
            }
          }
        }
        if (!isUsernameTaken) {
          const userData = {
            username: username,
            password: password,
            variables: {} // Initialize variables object
          };
          const mergedData = {
            ...existingData,
            users: {
              ...existingData.users,
              [username]: userData
            }
          };
          GM.xmlHttpRequest({
            method: 'PUT',
            url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
            headers: {
              'Content-Type': 'application/json',
            },
            data: JSON.stringify(mergedData),
            onload: async function (response) {
              // Update Tampermonkey storage with the variables from the local website storage
              const variables = {};
              for (const key in localStorage) {
                if (localStorage.hasOwnProperty(key)) {
                  variables[key] = localStorage[key];
                }
              }
              await GM.setValue('username', username);
              await GM.setValue('password', password);
              await GM.setValue('variables', variables);
              saveVariablesToJSONBin();
            }
          });
        } else {
          const newUsername = prompt('That username is already taken. Please enter a new username:');
          saveUserCredentialsToJSONBin(newUsername, password);
        }
      }
    }
  });
}

function checkUserCredentialsInJSONBin(username, password) {
  GM.xmlHttpRequest({
    method: 'GET',
    url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
    headers: {},
    onload: function (response) {
      const existingData = JSON.parse(response.responseText);
      let isUserFound = false;
      let isPasswordCorrect = false;
      let userData = {};
      for (const user in existingData.users) {
        if (existingData.users[user].username === username) {
          isUserFound = true;
          userData = existingData.users[user];
          if (userData.password === password) {
            isPasswordCorrect = true;
          }
          break;
        }
      }
      if (isUserFound && isPasswordCorrect) {
        console.log('User logged in successfully.');
        // Insert your login code here
      } else if (isUserFound && !isPasswordCorrect) {
        const newPassword = prompt('Incorrect password. Please enter a new password:');
        checkUserCredentialsInJSONBin(username, newPassword);
      } else {
        const newUsername = prompt('That username is not found. Please enter a new username:');
        checkUserCredentialsInJSONBin(newUsername, password);
      }
    }
  });
}

function saveBookmarkToJSONBin(username, password, title) {
  GM.xmlHttpRequest({
    method: 'GET',
    url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
    headers: {},
    onload: function (response) {
      const existingData = JSON.parse(response.responseText);
      const userData = existingData.users[username];
      if (userData && userData.password === password) {
        // Add the bookmark to the user's bookmarks
        if (!userData.bookmarks) {
          userData.bookmarks = [];
        }
        userData.bookmarks.push(title);
        const mergedData = {
          ...existingData,
          users: {
            ...existingData.users,
            [username]: userData
          }
        };
        GM.xmlHttpRequest({
          method: 'PUT',
          url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
          headers: {
            'Content-Type': 'application/json',
          },
          data: JSON.stringify(mergedData),

        });
      } else {
        console.log('Invalid credentials or user data not found.');
      }
    }
  });
}

function removeBookmarkFromJSONBin(username, password, title) {
  GM.xmlHttpRequest({
    method: 'GET',
    url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
    headers: {},
    onload: function (response) {
      const existingData = JSON.parse(response.responseText);
      const userData = existingData.users[username];
      if (userData.password === password) {
        // Remove the bookmark from the user's bookmarks
        userData.bookmarks = userData.bookmarks.filter(bookmark => bookmark !== title);
        const mergedData = {
          ...existingData,
          users: {
            ...existingData.users,
            [username]: userData
          }
        };
        GM.xmlHttpRequest({
          method: 'PUT',
          url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
          headers: {
            'Content-Type': 'application/json',
          },
          data: JSON.stringify(mergedData),

        });
      }
    }
  });
}

function handleClick() {
  const navElement = document.getElementById('main-menu');

  if (navElement.style.display === 'none') {
    navElement.style.display = 'block';
  } else {
    navElement.style.display = 'none';
  }
}

const iconElement = document.querySelector('.fa-bars');

// Add your own click event listener with useCapture set to true
iconElement.addEventListener('click', function() {
  if (iconElement.style.display === 'none') {
    handleClick();
  }
}, true);

// Code below is derived from 'Itsnotlupus' as they are using the MIT license I am allowed to appropriate their code into my system. Thank you for understanding this and if you have any issue with this email me at 'kidkoolstar@GMail.com' or comment on the script.

addStyles(`
/* remove ads and blank space between images were ads would have been */
[class^="ai-viewport"], .code-block, .blox, .kln, [id^="teaser"] {
  display: none !important;
}

/* hide various header and footer content. */
.socialts, .chdesc, .chaptertags, .postarea >#comments, .postbody>article>#comments {
  display: none;
}

/* style a custom button to expand collapsed footer areas */
button.expand {
  float: right;
  border: 0;
  border-radius: 20px;
  padding: 2px 15px;
  font-size: 13px;
  line-height: 25px;
  background: #333;
  color: #888;
  font-weight: bold;
  cursor: pointer;
}
button.expand:hover {
  background: #444;
}

/* disable builtin drag behavior to allow drag scrolling */
* {
  user-select: none;
  -webkit-user-drag: none;
}
body.drag {
  cursor: grabbing;
}

/* add a badge on bookmark items showing the number of unread chapters */
.unread-badge {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 9999;
  display: block;
  padding: 2px;
  margin: 5px;
  border: 1px solid #0005b1;
  border-radius: 12px;
  background: #ffc700;
  color: #0005b1;
  font-weight: bold;
  font-family: cursive;
  transform: rotate(10deg);
  width: 24px;
  height: 24px;
  line-height: 18px;
  text-align: center;
}
.soralist .unread-badge {
  position: initial;
  display: inline-block;
  zoom: 0.8;
}
`);

function makeCollapsedFooter({ label, section }) {
  const elt = crel('div', {
    className: 'bixbox',
    style: 'padding: 8px 15px'
  }, crel('button', {
    className: 'expand',
    textContent: label,
    onclick() {
      section.style.display = 'block';
      elt.style.display = 'none';
    }
  }));
  section.parentElement.insertBefore(elt, section);
}
// 1. collapse related series.
const related = $$$("//span[text()='Related Series']/../../..")[0];
if (related) {
  makeCollapsedFooter({label: 'Show Related Series', section: related});
  related.style.display = 'none';
}
// 2. collapse comments.
const comments = $`#comments`;
if (comments) makeCollapsedFooter({label: 'Show Comments', section: comments});

//------------- What I wrote resumes Here

(function () {
    'use strict';

    // Function to store chapter data
      function storeChapterData() {
        var chapter = window.location.href.split('/').pop().split('-').pop();
        var chapter_id = document.querySelector('link[rel="shortlink"]').href.split('=').pop();
        var manga_id = JSON.parse(localStorage.getItem('bm_history'))[chapter_id].manga_ID;
      
        var last_chapter = JSON.parse(localStorage.getItem('last-chapter')) || {};
        
        // Store the standardized URL
        const standardizedUrl = new URL(window.location.href);
        standardizedUrl.hostname = getStandardizedHostname(standardizedUrl.hostname);
        last_chapter[manga_id] = standardizedUrl.toString();
        
        localStorage.setItem('last-chapter', JSON.stringify(last_chapter));
      }
    // Return if url is not a chapter
    if (
        window.location.href === 'https://asuracomics.com//bookmarks/' ||
        window.location.href.startsWith('https://asuracomics.com//manga/')
    ) {
        return;
    }

    // Create a mutation observer to watch for changes in the URL
    const observer = new MutationObserver(() => {
        storeChapterData();
    });

    // Observe changes in the URL
    observer.observe(document.documentElement, { childList: true, subtree: true });

    // Call the function on page load
    storeChapterData();
})();