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.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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 '[email protected]' 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();
})();