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.

  1. // ==UserScript==
  2. // @name Asurascans Bookmarking System
  3. // @version 3.4
  4. // @author longkidkoolstar
  5. // @namespace https://github.com/longkidkoolstar
  6. // @icon https://images.vexels.com/media/users/3/223055/isolated/preview/eb3fcec56c95c2eb7ded9201e51550a2-bookmark-icon-flat-by-vexels.png
  7. // @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.
  8. // @require https://greasyfork.org/scripts/468394-itsnotlupus-tiny-utilities/code/utils.js
  9. // @match https://www.asurascans.com/*
  10. // @match https://asura.gg/*
  11. // @match https://asuracomics.gg/*
  12. // @match https://asura.nacm.xyz/*
  13. // @match https://asuracomics.com/*
  14. // @match https://asuratoon.com/*
  15. // @match https://asuracomic.net/*
  16. // @match https://void-scans.com/*
  17. // @match https://lunarscan.org/*
  18. // @match https://flamescans.org/*
  19. // @match https://luminousscans.com/*
  20. // @match https://luminousscans.net/*
  21. // @match https://cosmic-scans.com/*
  22. // @match https://nightscans.org/*
  23. // @match https://nightscans.net/*
  24. // @match https://freakscans.com/*
  25. // @match https://reaperscans.fr/*
  26. // @match https://manhwafreak.com/*
  27. // @match https://manhwa-freak.com/*
  28. // @match https://manhwafreak-fr.com/*
  29. // @match https://realmscans.to/*
  30. // @match https://mangastream.themesia.com/*
  31. // @match https://manga-scans.com/*
  32. // @match https://mangakakalot.so/*
  33. // @match https://reaperscans.com/*
  34. // @match https://flamecomics.com/*
  35. // @match https://hivetoon.com/*
  36. // @match https://night-scans.com/*
  37. // @license MIT
  38. // @grant GM.setValue
  39. // @grant GM.getValue
  40. // @grant GM.xmlHttpRequest
  41. // ==/UserScript==
  42.  
  43. console.log('User script started');
  44.  
  45. const scriptVersion = '3.4';
  46.  
  47. // Check if user is logged in
  48. (async () => {
  49. const username = await GM.getValue('username');
  50. const password = await GM.getValue('password');
  51.  
  52. if (username && password) {
  53. console.log('User is logged in');
  54. window.addEventListener('load', saveDeviceType);
  55. } else {
  56. console.log('User is not logged in');
  57. // Prompt user to enter username and password
  58. const username = prompt('Enter your username:');
  59. const password = prompt('Enter your password:');
  60. // Save username and password to Tampermonkey
  61. await GM.setValue('username', username);
  62. await GM.setValue('password', password);
  63. // Save username and password to JSONBin
  64. saveUserCredentialsToJSONBin(username, password);
  65. }
  66. })();
  67.  
  68.  
  69. // Function to get a standardized hostname for Asura-related domains
  70. function getStandardizedHostname(hostname) {
  71. if (hostname.includes('asura') || hostname.includes('asuratoon')) {
  72. return 'asurascans.com';
  73. }
  74. return hostname;
  75. }
  76.  
  77. // Function to get the current Asura domain
  78. function getCurrentAsuraDomain() {
  79. const hostname = window.location.hostname;
  80. if (hostname.includes('asura') || hostname.includes('asuratoon')) {
  81. return hostname;
  82. }
  83. return null;
  84. }
  85.  
  86. // Below is to delete the new ads that Asura has been implementing.
  87. //--------------
  88. (function() {
  89. // Function to delete the element
  90. function deleteElement() {
  91. var element = document.getElementById('noktaplayercontainer');
  92. if (element) {
  93. element.remove();
  94. }
  95. }
  96.  
  97. // DOM listener to check for element existence
  98. var observer = new MutationObserver(function(mutations) {
  99. mutations.forEach(function(mutation) {
  100. if (mutation.type === 'childList' || mutation.type === 'subtree') {
  101. deleteElement();
  102. }
  103. });
  104. });
  105.  
  106. // Start observing the document body for changes
  107. observer.observe(document.body, { childList: true, subtree: true });
  108. })();
  109.  
  110. // Function to detect and save the device type to JSONBin and local storage
  111. async function saveDeviceType() {
  112. const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  113.  
  114. // Get the saved device type from local storage
  115. const savedDeviceType = localStorage.getItem('deviceType');
  116.  
  117. if (savedDeviceType !== isMobile.toString()) {
  118. // Device type has changed, update JSONBin and local storage
  119. const username = await GM.getValue('username');
  120. const password = await GM.getValue('password');
  121. const deviceData = { deviceType: isMobile.toString() };
  122.  
  123. // Save device data to JSONBin
  124. GM.xmlHttpRequest({
  125. method: 'GET',
  126. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  127. headers: {},
  128. onload: function (response) {
  129. const existingData = JSON.parse(response.responseText);
  130. if (existingData.users[username].password === password) {
  131. existingData.users[username].variables.deviceType = isMobile.toString();
  132. GM.xmlHttpRequest({
  133. method: 'PUT',
  134. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  135. headers: {
  136. 'Content-Type': 'application/json',
  137. },
  138. data: JSON.stringify(existingData),
  139. });
  140. }
  141. }
  142. });
  143.  
  144. // Save device data to local storage
  145. localStorage.setItem('deviceType', isMobile.toString());
  146.  
  147. // Update variables in local storage
  148. const jsonBinData = getJSONBinData();
  149. if (jsonBinData) {
  150. saveVariablesFromJSONBin(jsonBinData);
  151. updateLocalStorageVariables(jsonBinData);
  152. }
  153. }
  154.  
  155. // Log the device type to the console
  156. console.log('Device Type:', isMobile ? 'Mobile' : 'Computer');
  157. }
  158.  
  159. //------------------------------------------------------------------------ Uncomment if Asura changes Domain name again.
  160.  
  161. function updateLastChaptersDomain() {
  162. const oldDomain = "https://asuracomics.com/";
  163. const newDomain = "https://asuracomics.gg/";
  164.  
  165. const lastChapters = JSON.parse(localStorage.getItem("last-chapter"));
  166. if (lastChapters) {
  167. for (const mangaId in lastChapters) {
  168. if (lastChapters.hasOwnProperty(mangaId)) {
  169. const oldUrl = lastChapters[mangaId];
  170. if (oldUrl.startsWith(oldDomain)) {
  171. const newUrl = oldUrl.replace(oldDomain, newDomain);
  172. lastChapters[mangaId] = newUrl;
  173. }
  174. }
  175. }
  176. localStorage.setItem("last-chapter", JSON.stringify(lastChapters));
  177. console.log("Last chapters updated to the new domain.");
  178. } else {
  179. console.log("No last chapters found.");
  180. }
  181. }
  182.  
  183. // Call the function to update the URLs
  184. updateLastChaptersDomain();
  185.  
  186. function updateLastChaptersDomainif() {
  187. const oldDomain = "https://asuracomics.gg/";
  188. const newDomain = "https://asuratoon.com/";
  189.  
  190. const lastChapters = JSON.parse(localStorage.getItem("last-chapter"));
  191. if (lastChapters) {
  192. for (const mangaId in lastChapters) {
  193. if (lastChapters.hasOwnProperty(mangaId)) {
  194. const oldUrl = lastChapters[mangaId];
  195. if (oldUrl.startsWith(oldDomain)) {
  196. const newUrl = oldUrl.replace(oldDomain, newDomain);
  197. lastChapters[mangaId] = newUrl;
  198. }
  199. }
  200. }
  201. localStorage.setItem("last-chapter", JSON.stringify(lastChapters));
  202. console.log("Last chapters updated to the new domain.");
  203. } else {
  204. console.log("No last chapters found.");
  205. }
  206. }
  207.  
  208. // Call the function to update the URLs
  209. updateLastChaptersDomainif();
  210. //-----------------------------------------------------------------------------
  211.  
  212. // Wait for the page to load
  213. window.addEventListener('load', async function () {
  214. // Fetch JSONBin data and save variables
  215. const jsonBinData = getJSONBinData();
  216. if (jsonBinData) {
  217. saveVariablesFromJSONBin(jsonBinData);
  218. }
  219.  
  220. // Check if it's a manga page and display the last read chapter if available
  221. if (window.location.pathname.startsWith('/manga/')) {
  222. displayLastReadChapter();
  223. }
  224. });
  225.  
  226. // Function to fetch JSONBin data
  227. function getJSONBinData() {
  228. const request = new XMLHttpRequest();
  229. 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);
  230.  
  231. request.send(null);
  232. if (request.status === 200) {
  233. return JSON.parse(request.responseText);
  234. }
  235. return null;
  236. }
  237.  
  238. // Save variables from JSONBin to GM storage
  239. async function saveVariablesFromJSONBin(data) {
  240. const userData = data.users[await GM.getValue('username')];
  241. if (userData) {
  242. const variables = userData.variables;
  243. if (variables) {
  244. for (const key in variables) {
  245. if (variables.hasOwnProperty(key)) {
  246. await GM.setValue(key, variables[key]);
  247. }
  248. }
  249. }
  250. }
  251. }
  252.  
  253. // Modify the saveVariablesToJSONBin function
  254. async function saveVariablesToJSONBin() {
  255. const username = await GM.getValue('username');
  256. const password = await GM.getValue('password');
  257. const originalWebsite = window.location.hostname;
  258. const standardizedWebsite = getStandardizedHostname(originalWebsite);
  259. const newVariables = {};
  260.  
  261. // Add specific variables to the newVariables object
  262. const requiredVariables = ['last-chapter', 'bookmark', 'deviceType', 'scriptVersion'];
  263.  
  264. for (const key of requiredVariables) {
  265. const value = localStorage.getItem(key);
  266. if (value !== null) {
  267. newVariables[key] = value;
  268. }
  269. }
  270.  
  271. // Fetch existing data from JSONBin
  272. GM.xmlHttpRequest({
  273. method: 'GET',
  274. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  275. headers: {},
  276. onload: function (response) {
  277. const responseData = JSON.parse(response.responseText);
  278. const userData = responseData.users[username];
  279. if (userData && userData.password === password) {
  280. // Merge newVariables with the existing variables for the standardized website
  281. userData.variables[standardizedWebsite] = {
  282. ...(userData.variables[standardizedWebsite] || {}),
  283. ...newVariables
  284. };
  285.  
  286. // Save the merged data back to JSONBin
  287. saveUserDataToJSONBin(userData);
  288. } else {
  289. console.log('Invalid credentials or user data not found.');
  290. }
  291. }
  292. });
  293. }
  294.  
  295.  
  296. function checkAndUpdateScriptVersion() {
  297. const storedScriptVersion = localStorage.getItem('scriptVersion');
  298. if (storedScriptVersion !== scriptVersion) {
  299. // Script version has changed, update the stored version and save variables to JSONBin
  300. localStorage.setItem('scriptVersion', scriptVersion);
  301. }
  302. }
  303.  
  304. // Call the function on page load
  305. checkAndUpdateScriptVersion();
  306.  
  307. // Save user data to JSONBin
  308. function saveUserDataToJSONBin(userData) {
  309. GM.xmlHttpRequest({
  310. method: 'GET',
  311. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  312. headers: {},
  313. onload: function (response) {
  314. const existingData = JSON.parse(response.responseText);
  315.  
  316. // Create an empty users object if it doesn't exist
  317. existingData.users = existingData.users || {};
  318.  
  319. // Store the user data within the users object using the username as the key
  320. existingData.users[userData.username] = userData;
  321.  
  322. GM.xmlHttpRequest({
  323. method: 'PUT',
  324. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  325. headers: {
  326. 'Content-Type': 'application/json',
  327. },
  328. data: JSON.stringify(existingData),
  329. });
  330. }
  331. });
  332. }
  333.  
  334. // Save variables from the local website storage to Tampermonkey storage
  335. async function saveVariablesToTampermonkeyStorage() {
  336. const variables = await GM.getValue('variables') || {};
  337.  
  338. let hasChanges = false;
  339. for (const key in localStorage) {
  340. if (localStorage.hasOwnProperty(key)) {
  341. if (!variables.hasOwnProperty(key) || variables[key] !== localStorage[key]) {
  342. variables[key] = localStorage[key];
  343. hasChanges = true;
  344. }
  345. }
  346. }
  347. if (hasChanges) {
  348. await GM.setValue('variables', variables);
  349. saveVariablesToJSONBin();
  350. }
  351. }
  352.  
  353. // Add event listener to the bookmark <div> elements
  354. const bookmarkDivs = document.querySelectorAll('div.bookmark');
  355. bookmarkDivs.forEach(function (bookmarkDiv) {
  356. bookmarkDiv.addEventListener('click', function () {
  357. setTimeout(saveVariablesToTampermonkeyStorage, 1000); // Delay execution by 1000 milliseconds (1 second)
  358. });
  359. });
  360.  
  361. //----------------------------------------------------------------------------------------------------------------------------------------------------All the Buttons
  362.  
  363. // Check for element before changing the z index to 0
  364. const bgelement = document.querySelector("#post-225070 > div.bixbox.animefull > div.bigcontent.nobigcover");
  365. if (bgelement) {
  366. bgelement.style.zIndex = "0";
  367. }
  368.  
  369. // Autosaving functionality
  370. let isAutosaveEnabled = GM.getValue('isAutosaveEnabled', false);
  371. let autosaveInterval = GM.getValue('autosaveInterval', null);
  372.  
  373. // Function to toggle autosaving with persistence across webpages
  374. async function toggleAutosave() {
  375. const button = document.getElementById('autosaveButton');
  376. if (isAutosaveEnabled) {
  377. clearInterval(autosaveInterval);
  378. isAutosaveEnabled = false;
  379. await GM.setValue('isAutosaveEnabled', false);
  380. await GM.setValue('autosaveInterval', null); // Clear the autosaveInterval on disable
  381. button.textContent = 'Toggle Autosave (Off)';
  382. console.log('Autosaving disabled.');
  383. } else {
  384. await GM.setValue('autosaveInterval', autosaveInterval);
  385. const currentTime = new Date().getTime();
  386. localStorage.setItem('autosaveStartTime', currentTime);
  387. autosaveInterval = setInterval(async function() {
  388. const startTime = parseInt(localStorage.getItem('autosaveStartTime'));
  389. const elapsedTime = (new Date().getTime() - startTime) / 1000;
  390. const remainingTime = 180 - elapsedTime; // Change autosave interval to 3 minutes
  391. localStorage.setItem('autosaveCountdown', remainingTime);
  392. if (remainingTime <= 0) {
  393. saveVariablesToJSONBin();
  394. saveVariablesToTampermonkeyStorage();
  395. localStorage.removeItem('autosaveStartTime');
  396. localStorage.removeItem('autosaveCountdown');
  397. localStorage.setItem('autosaveStartTime', new Date().getTime()); // Start another countdown from the last autosave
  398. console.log('Autosaved at: ' + new Date().toLocaleString()); // Log the time of the last save
  399. }
  400. }, 1000); // Update countdown every second
  401. isAutosaveEnabled = true;
  402. await GM.setValue('isAutosaveEnabled', true); // Save the state to Tampermonkey storage
  403. button.textContent = 'Toggle Autosave (On)';
  404. console.log('Autosaving enabled.');
  405. }
  406. }
  407.  
  408. // Create a container for the buttons and style it
  409. const menuContainer = document.createElement('div');
  410. menuContainer.id = 'menuContainer';
  411. menuContainer.classList.add('menu-container'); // Add a CSS class for styling
  412. // Function to check if autosave is enabled and return a string for button text
  413. async function checkAutosaveStatus() {
  414. const isAutosaveEnabled = await GM.getValue('isAutosaveEnabled', false);
  415. return isAutosaveEnabled ? 'Toggle Autosave (On)' : 'Toggle Autosave (Off)';
  416. }
  417.  
  418. // Create "Save" button
  419. const saveButton = document.createElement('button');
  420. saveButton.id = 'saveButton';
  421. saveButton.textContent = 'Save';
  422. saveButton.classList.add('normal-button');
  423. saveButton.addEventListener('click', handleSaveButtonClick);
  424. saveButton.addEventListener('mouseover', function() {
  425. saveButton.style.opacity = '0.5'; // Dim the button when hovered over
  426. });
  427. saveButton.addEventListener('mouseout', function() {
  428. saveButton.style.opacity = '1'; // Restore the button's normal opacity
  429. });
  430. menuContainer.appendChild(saveButton);
  431.  
  432. // Create "Get Bookmarks" button
  433. const getBookmarksButton = document.createElement('button');
  434. getBookmarksButton.id = 'getBookmarksButton';
  435. getBookmarksButton.textContent = 'Get Bookmarks';
  436. getBookmarksButton.classList.add('normal-button');
  437. getBookmarksButton.addEventListener('click', handleGetBookmarksButtonClick);
  438. getBookmarksButton.addEventListener('mouseover', function() {
  439. getBookmarksButton.style.opacity = '0.5'; // Dim the button when hovered over
  440. });
  441. getBookmarksButton.addEventListener('mouseout', function() {
  442. getBookmarksButton.style.opacity = '1'; // Restore the button's normal opacity
  443. });
  444. menuContainer.appendChild(getBookmarksButton);
  445.  
  446. // Create "Autosave" button
  447. const autosaveButton = document.createElement('button');
  448. autosaveButton.id = 'autosaveButton';
  449. (async () => {
  450. autosaveButton.textContent = await checkAutosaveStatus();
  451. })();
  452. autosaveButton.classList.add('normal-button');
  453. autosaveButton.addEventListener('click', toggleAutosave);
  454. autosaveButton.addEventListener('mouseover', function() {
  455. autosaveButton.style.opacity = '0.5'; // Dim the button when hovered over
  456. });
  457. autosaveButton.addEventListener('mouseout', function() {
  458. autosaveButton.style.opacity = '1'; // Restore the button's normal opacity
  459. });
  460. menuContainer.appendChild(autosaveButton);
  461. // Append the menu container to the document body
  462. document.body.appendChild(menuContainer);
  463.  
  464. // Create a dropdown button
  465. const dropdownButton = document.createElement('button');
  466. dropdownButton.id = 'dropdownButton';
  467. dropdownButton.textContent = 'Menu ▼'; // ▼ is a down arrow character
  468. dropdownButton.classList.add('menu-button', 'fixed-button'); // Add CSS classes for styling
  469.  
  470. // Append the dropdown button to the menu container
  471. menuContainer.appendChild(dropdownButton);
  472.  
  473. // Toggle the visibility of the menu when the dropdown button is clicked
  474. dropdownButton.addEventListener('click', function () {
  475. if (menuContainer.style.display === 'none') {
  476. menuContainer.style.display = 'block';
  477. } else {
  478. menuContainer.style.display = 'none';
  479. }
  480. });
  481.  
  482. // Position the menu container in the bottom right corner
  483. menuContainer.style.position = 'fixed';
  484. menuContainer.style.bottom = '20px';
  485. menuContainer.style.right = '20px';
  486.  
  487. const logoutButton = document.createElement('button');
  488. logoutButton.id = 'logoutButton';
  489. logoutButton.textContent = 'Logout';
  490. 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;';
  491. logoutButton.classList.add('normal-button');
  492. logoutButton.addEventListener('click', logout);
  493. logoutButton.addEventListener('mouseover', () => logoutButton.style.opacity = '0.5');
  494. logoutButton.addEventListener('mouseout', () => logoutButton.style.opacity = '1');
  495.  
  496. // Append the logout button to the menu container
  497. menuContainer.appendChild(logoutButton);
  498.  
  499. // Append the dropdown button to the document body
  500. document.body.appendChild(dropdownButton);
  501.  
  502. // Apply CSS styles to the buttons and container
  503. const style = document.createElement('style');
  504. style.textContent = `
  505. .menu-container {
  506. position: fixed;
  507. bottom: 20px;
  508. left: 20px;
  509. display: none;
  510. flex-direction: column;
  511. align-items: flex-start;
  512. }
  513.  
  514. .menu-button {
  515. background-color: rgb(145, 63, 226);
  516. border: 1px solid #ccc;
  517. border-radius: 3px;
  518. color: #fff;
  519. opacity: 0.33;
  520. cursor: pointer;
  521. font-family: 'Open Sans, sans-serif';
  522. font-size: 9.15px;
  523. font-weight: bold;
  524. padding: 5px;
  525. margin: 5px 0;
  526. }
  527.  
  528. .normal-button {
  529. background-color: rgb(145, 63, 226);
  530. border: 1px solid #ccc;
  531. border-radius: 3px;
  532. color: #fff;
  533. cursor: pointer;
  534. font-family: 'Open Sans, sans-serif';
  535. font-size: 12px;
  536. font-weight: bold;
  537. padding: 5px;
  538. margin: 5px 0;
  539. transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
  540. }
  541.  
  542. .menu-button:hover {
  543. opacity: 0.8;
  544. }
  545. .fixed-button {
  546. position: fixed;
  547. top: 20px; /* Adjust top position as needed */
  548. left: 20px; /* Adjust left position as needed */
  549. }
  550.  
  551. /* Additional styles for the fixed button */
  552. .fixed-button {
  553. z-index: 999; /* Ensure the button is on top of other elements */
  554. }
  555. `;
  556.  
  557. document.head.appendChild(style);
  558.  
  559. // Event handler for "Save" button
  560. async function handleSaveButtonClick() {
  561. const username = await GM.getValue('username');
  562. const password = await GM.getValue('password');
  563.  
  564. if (username && password) {
  565. // Save the script version along with other variables
  566. localStorage.setItem('scriptVersion', scriptVersion);
  567. saveVariablesToTampermonkeyStorage();
  568. saveVariablesToJSONBin(username, password);
  569. } else {
  570. console.log('User is not logged in.');
  571. }
  572. }
  573.  
  574. async function handleGetBookmarksButtonClick() {
  575. const jsonBinData = getJSONBinData();
  576. if (jsonBinData) {
  577. const originalWebsite = window.location.hostname;
  578. const standardizedWebsite = getStandardizedHostname(originalWebsite);
  579. const userData = jsonBinData.users[await GM.getValue('username')];
  580. if (userData && userData.variables && userData.variables[standardizedWebsite]) {
  581. const variables = userData.variables[standardizedWebsite];
  582. updateLocalStorageVariables(variables);
  583. }
  584. }
  585. }
  586.  
  587. function updateLocalStorageVariables(variables) {
  588. for (const key in variables) {
  589. if (variables.hasOwnProperty(key)) {
  590. // Check if the value is an object and convert it to a string
  591. const value = typeof variables[key] === 'object' ? JSON.stringify(variables[key]) : variables[key];
  592. localStorage.setItem(key, value);
  593. }
  594. }
  595. }
  596.  
  597. function displayLastReadChapter() {
  598. const mangaNameElement = document.querySelector('h1.entry-title');
  599. if (!mangaNameElement) {
  600. console.log('Manga name element not found.');
  601. return;
  602. }
  603.  
  604. const displayedMangaName = mangaNameElement.innerText.trim().toLowerCase().replace(/[^a-z0-9]/g, '');
  605. const lastChapter = JSON.parse(localStorage.getItem('last-chapter'));
  606. const currentAsuraDomain = getCurrentAsuraDomain();
  607.  
  608. if (lastChapter && currentAsuraDomain) {
  609. for (const mangaId in lastChapter) {
  610. let storedChapterUrl = lastChapter[mangaId];
  611. const storedMangaNameMatch = storedChapterUrl.match(/\/(\d+-)?([^/]+)-chapter-\d+\//);
  612. if (storedMangaNameMatch) {
  613. const storedMangaName = storedMangaNameMatch[2].replace(/-/g, '').toLowerCase();
  614.  
  615. console.log('Displayed Manga Name:', displayedMangaName);
  616. console.log('Stored Manga Name:', storedMangaName);
  617.  
  618. if (displayedMangaName === storedMangaName || isCloseMatch(displayedMangaName, storedMangaName)) {
  619. console.log(`Match found: Displayed Manga Name - ${displayedMangaName}, Stored Manga Name - ${storedMangaName}`);
  620. // Update the stored URL with the current Asura domain
  621. const storedUrlObj = new URL(storedChapterUrl);
  622. storedUrlObj.hostname = currentAsuraDomain;
  623. storedChapterUrl = storedUrlObj.toString();
  624.  
  625. const lastChapterElement = document.createElement('div');
  626. lastChapterElement.textContent = 'Last Read Chapter: ';
  627. const lastChapterLink = document.createElement('a');
  628. lastChapterLink.href = storedChapterUrl;
  629. lastChapterLink.textContent = 'Chapter ' + storedChapterUrl.split('/').pop().replace(/[^0-9]/g, '');
  630. lastChapterElement.appendChild(lastChapterLink);
  631. lastChapterElement.style.position = 'fixed';
  632. lastChapterElement.style.top = '50%';
  633. lastChapterElement.style.right = '10px';
  634. lastChapterElement.style.transform = 'translateY(-50%)';
  635. lastChapterElement.style.backgroundColor = 'rgb(145,63,226)';
  636. lastChapterElement.style.color = '#fff';
  637. lastChapterElement.style.padding = '5px';
  638. lastChapterElement.style.fontFamily = 'Open Sans, sans-serif';
  639. lastChapterElement.style.fontSize = '12px';
  640. lastChapterElement.style.fontWeight = 'bold';
  641. document.body.appendChild(lastChapterElement);
  642.  
  643. return; // Stop looping after finding a match
  644. }
  645. }
  646. }
  647. }
  648. console.log('No last read chapter found for the manga.');
  649. }
  650.  
  651.  
  652. function levenshteinDistance(a, b) {
  653. const matrix = [];
  654.  
  655. let i;
  656. for (i = 0; i <= b.length; i++) {
  657. matrix[i] = [i];
  658. }
  659.  
  660. let j;
  661. for (j = 0; j <= a.length; j++) {
  662. matrix[0][j] = j;
  663. }
  664.  
  665. for (i = 1; i <= b.length; i++) {
  666. for (j = 1; j <= a.length; j++) {
  667. if (b.charAt(i - 1) === a.charAt(j - 1)) {
  668. matrix[i][j] = matrix[i - 1][j - 1];
  669. } else {
  670. matrix[i][j] = Math.min(
  671. matrix[i - 1][j - 1] + 1,
  672. Math.min(matrix[i][j - 1] + 1, matrix[i - 1][j] + 1)
  673. );
  674. }
  675. }
  676. }
  677.  
  678. return matrix[b.length][a.length];
  679. }
  680.  
  681. function isCloseMatch(displayedMangaName, storedMangaName) {
  682. const similarityThreshold = 5; // Adjust this threshold as needed
  683. const distance = levenshteinDistance(displayedMangaName.toLowerCase(), storedMangaName.toLowerCase());
  684.  
  685. return distance <= similarityThreshold;
  686. }
  687.  
  688. // Test the function
  689. const displayedName = "One Piece";
  690. const storedName = "One Pece";
  691. console.log(isCloseMatch(displayedName, storedName)); // Should return true or false based on the threshold
  692.  
  693. async function logout() {
  694. // Clear Tampermonkey storage
  695. await GM.setValue('username', '');
  696. await GM.setValue('password', '');
  697. await GM.setValue('variables', {});
  698.  
  699. // Clear website's local storage
  700. localStorage.clear();
  701.  
  702. // Refresh the page to log out
  703. location.reload();
  704. }
  705.  
  706. function saveUserCredentialsToJSONBin(username, password) {
  707. // Validate that both username and password are not null or empty
  708. if (!username || !password) {
  709. console.log('Username and password cannot be empty.');
  710. alert('Username and Password cannot be empty. Please refresh the page or click the logout button to try again.');
  711. return; // Exit the function if either is empty
  712. }
  713.  
  714. GM.xmlHttpRequest({
  715. method: 'GET',
  716. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  717. headers: {},
  718. onload: async function (response) {
  719. const existingData = JSON.parse(response.responseText);
  720. let isUsernameFound = false;
  721. let isPasswordCorrect = false;
  722. let isUsernameTaken = false;
  723. for (const user in existingData.users) {
  724. if (existingData.users.hasOwnProperty(user)) {
  725. if (existingData.users[user].username === username) {
  726. isUsernameFound = true;
  727. if (existingData.users[user].password === password) {
  728. isPasswordCorrect = true;
  729. }
  730. break;
  731. }
  732. }
  733. }
  734. if (isUsernameFound && isPasswordCorrect) {
  735. console.log('User logged in successfully.');
  736.  
  737. // Replace variables in local storage with the ones from JSONBin
  738. const jsonBinData = getJSONBinData();
  739. if (jsonBinData) {
  740. const website = window.location.hostname;
  741. const userData = jsonBinData.users[await GM.getValue('username')];
  742. if (userData && userData.variables && userData.variables[website]) {
  743. const variables = userData.variables[website];
  744. updateLocalStorageVariables(variables);
  745.  
  746. //-------------
  747. // Select the element you want to refresh
  748. const elementToRefresh = document.querySelector("#bookmark-pool");
  749.  
  750. // Create a new XMLHttpRequest object
  751. const xhr = new XMLHttpRequest();
  752.  
  753. // Set up the AJAX request
  754. xhr.open("GET", "https://asuratoon.com/bookmark/ #bookmark-pool", true);
  755.  
  756. // Define the callback function to handle the AJAX response
  757. xhr.onload = function () {
  758. if (xhr.status === 200) {
  759. // Create a temporary element to hold the response
  760. const tempElement = document.createElement("div");
  761. tempElement.innerHTML = xhr.responseText;
  762.  
  763. // Find the specific part of the response
  764. const refreshedElement = tempElement.querySelector("#bookmark-pool");
  765.  
  766. // Replace the content of the element with the refreshed content and its children
  767. elementToRefresh.innerHTML = refreshedElement.innerHTML;
  768.  
  769. // Append the refreshed children to the element
  770. while (refreshedElement.firstChild) {
  771. elementToRefresh.appendChild(refreshedElement.firstChild);
  772. }
  773. }
  774. };
  775.  
  776. // Send the AJAX request
  777. xhr.send();
  778. //------------
  779. }
  780. }
  781. } else if (!isUsernameFound || !isPasswordCorrect) {
  782. for (const user in existingData.users) {
  783. if (existingData.users.hasOwnProperty(user)) {
  784. if (existingData.users[user].username === username) {
  785. isUsernameTaken = true;
  786. break;
  787. }
  788. }
  789. }
  790. if (!isUsernameTaken) {
  791. const userData = {
  792. username: username,
  793. password: password,
  794. variables: {} // Initialize variables object
  795. };
  796. const mergedData = {
  797. ...existingData,
  798. users: {
  799. ...existingData.users,
  800. [username]: userData
  801. }
  802. };
  803. GM.xmlHttpRequest({
  804. method: 'PUT',
  805. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  806. headers: {
  807. 'Content-Type': 'application/json',
  808. },
  809. data: JSON.stringify(mergedData),
  810. onload: async function (response) {
  811. // Update Tampermonkey storage with the variables from the local website storage
  812. const variables = {};
  813. for (const key in localStorage) {
  814. if (localStorage.hasOwnProperty(key)) {
  815. variables[key] = localStorage[key];
  816. }
  817. }
  818. await GM.setValue('username', username);
  819. await GM.setValue('password', password);
  820. await GM.setValue('variables', variables);
  821. saveVariablesToJSONBin();
  822. }
  823. });
  824. } else {
  825. const newUsername = prompt('That username is already taken. Please enter a new username:');
  826. saveUserCredentialsToJSONBin(newUsername, password);
  827. }
  828. }
  829. }
  830. });
  831. }
  832.  
  833. function checkUserCredentialsInJSONBin(username, password) {
  834. GM.xmlHttpRequest({
  835. method: 'GET',
  836. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  837. headers: {},
  838. onload: function (response) {
  839. const existingData = JSON.parse(response.responseText);
  840. let isUserFound = false;
  841. let isPasswordCorrect = false;
  842. let userData = {};
  843. for (const user in existingData.users) {
  844. if (existingData.users[user].username === username) {
  845. isUserFound = true;
  846. userData = existingData.users[user];
  847. if (userData.password === password) {
  848. isPasswordCorrect = true;
  849. }
  850. break;
  851. }
  852. }
  853. if (isUserFound && isPasswordCorrect) {
  854. console.log('User logged in successfully.');
  855. // Insert your login code here
  856. } else if (isUserFound && !isPasswordCorrect) {
  857. const newPassword = prompt('Incorrect password. Please enter a new password:');
  858. checkUserCredentialsInJSONBin(username, newPassword);
  859. } else {
  860. const newUsername = prompt('That username is not found. Please enter a new username:');
  861. checkUserCredentialsInJSONBin(newUsername, password);
  862. }
  863. }
  864. });
  865. }
  866.  
  867. function saveBookmarkToJSONBin(username, password, title) {
  868. GM.xmlHttpRequest({
  869. method: 'GET',
  870. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  871. headers: {},
  872. onload: function (response) {
  873. const existingData = JSON.parse(response.responseText);
  874. const userData = existingData.users[username];
  875. if (userData && userData.password === password) {
  876. // Add the bookmark to the user's bookmarks
  877. if (!userData.bookmarks) {
  878. userData.bookmarks = [];
  879. }
  880. userData.bookmarks.push(title);
  881. const mergedData = {
  882. ...existingData,
  883. users: {
  884. ...existingData.users,
  885. [username]: userData
  886. }
  887. };
  888. GM.xmlHttpRequest({
  889. method: 'PUT',
  890. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  891. headers: {
  892. 'Content-Type': 'application/json',
  893. },
  894. data: JSON.stringify(mergedData),
  895.  
  896. });
  897. } else {
  898. console.log('Invalid credentials or user data not found.');
  899. }
  900. }
  901. });
  902. }
  903.  
  904. function removeBookmarkFromJSONBin(username, password, title) {
  905. GM.xmlHttpRequest({
  906. method: 'GET',
  907. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  908. headers: {},
  909. onload: function (response) {
  910. const existingData = JSON.parse(response.responseText);
  911. const userData = existingData.users[username];
  912. if (userData.password === password) {
  913. // Remove the bookmark from the user's bookmarks
  914. userData.bookmarks = userData.bookmarks.filter(bookmark => bookmark !== title);
  915. const mergedData = {
  916. ...existingData,
  917. users: {
  918. ...existingData.users,
  919. [username]: userData
  920. }
  921. };
  922. GM.xmlHttpRequest({
  923. method: 'PUT',
  924. url: `https://api.jsonstorage.net/v1/json/d206ce58-9543-48db-a5e4-997cfc745ef3/b4e88aaa-780e-4e6f-9500-c7fbf655aa67?apiKey=a5e587f8-a940-42e4-bc68-cc17f8f0468c`,
  925. headers: {
  926. 'Content-Type': 'application/json',
  927. },
  928. data: JSON.stringify(mergedData),
  929.  
  930. });
  931. }
  932. }
  933. });
  934. }
  935.  
  936. function handleClick() {
  937. const navElement = document.getElementById('main-menu');
  938.  
  939. if (navElement.style.display === 'none') {
  940. navElement.style.display = 'block';
  941. } else {
  942. navElement.style.display = 'none';
  943. }
  944. }
  945.  
  946. const iconElement = document.querySelector('.fa-bars');
  947.  
  948. // Add your own click event listener with useCapture set to true
  949. iconElement.addEventListener('click', function() {
  950. if (iconElement.style.display === 'none') {
  951. handleClick();
  952. }
  953. }, true);
  954.  
  955. // 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.
  956.  
  957. addStyles(`
  958. /* remove ads and blank space between images were ads would have been */
  959. [class^="ai-viewport"], .code-block, .blox, .kln, [id^="teaser"] {
  960. display: none !important;
  961. }
  962.  
  963. /* hide various header and footer content. */
  964. .socialts, .chdesc, .chaptertags, .postarea >#comments, .postbody>article>#comments {
  965. display: none;
  966. }
  967.  
  968. /* style a custom button to expand collapsed footer areas */
  969. button.expand {
  970. float: right;
  971. border: 0;
  972. border-radius: 20px;
  973. padding: 2px 15px;
  974. font-size: 13px;
  975. line-height: 25px;
  976. background: #333;
  977. color: #888;
  978. font-weight: bold;
  979. cursor: pointer;
  980. }
  981. button.expand:hover {
  982. background: #444;
  983. }
  984.  
  985. /* disable builtin drag behavior to allow drag scrolling */
  986. * {
  987. user-select: none;
  988. -webkit-user-drag: none;
  989. }
  990. body.drag {
  991. cursor: grabbing;
  992. }
  993.  
  994. /* add a badge on bookmark items showing the number of unread chapters */
  995. .unread-badge {
  996. position: absolute;
  997. top: 0;
  998. right: 0;
  999. z-index: 9999;
  1000. display: block;
  1001. padding: 2px;
  1002. margin: 5px;
  1003. border: 1px solid #0005b1;
  1004. border-radius: 12px;
  1005. background: #ffc700;
  1006. color: #0005b1;
  1007. font-weight: bold;
  1008. font-family: cursive;
  1009. transform: rotate(10deg);
  1010. width: 24px;
  1011. height: 24px;
  1012. line-height: 18px;
  1013. text-align: center;
  1014. }
  1015. .soralist .unread-badge {
  1016. position: initial;
  1017. display: inline-block;
  1018. zoom: 0.8;
  1019. }
  1020. `);
  1021.  
  1022. function makeCollapsedFooter({ label, section }) {
  1023. const elt = crel('div', {
  1024. className: 'bixbox',
  1025. style: 'padding: 8px 15px'
  1026. }, crel('button', {
  1027. className: 'expand',
  1028. textContent: label,
  1029. onclick() {
  1030. section.style.display = 'block';
  1031. elt.style.display = 'none';
  1032. }
  1033. }));
  1034. section.parentElement.insertBefore(elt, section);
  1035. }
  1036. // 1. collapse related series.
  1037. const related = $$$("//span[text()='Related Series']/../../..")[0];
  1038. if (related) {
  1039. makeCollapsedFooter({label: 'Show Related Series', section: related});
  1040. related.style.display = 'none';
  1041. }
  1042. // 2. collapse comments.
  1043. const comments = $`#comments`;
  1044. if (comments) makeCollapsedFooter({label: 'Show Comments', section: comments});
  1045.  
  1046. //------------- What I wrote resumes Here
  1047.  
  1048. (function () {
  1049. 'use strict';
  1050.  
  1051. // Function to store chapter data
  1052. function storeChapterData() {
  1053. var chapter = window.location.href.split('/').pop().split('-').pop();
  1054. var chapter_id = document.querySelector('link[rel="shortlink"]').href.split('=').pop();
  1055. var manga_id = JSON.parse(localStorage.getItem('bm_history'))[chapter_id].manga_ID;
  1056. var last_chapter = JSON.parse(localStorage.getItem('last-chapter')) || {};
  1057. // Store the standardized URL
  1058. const standardizedUrl = new URL(window.location.href);
  1059. standardizedUrl.hostname = getStandardizedHostname(standardizedUrl.hostname);
  1060. last_chapter[manga_id] = standardizedUrl.toString();
  1061. localStorage.setItem('last-chapter', JSON.stringify(last_chapter));
  1062. }
  1063. // Return if url is not a chapter
  1064. if (
  1065. window.location.href === 'https://asuracomics.com//bookmarks/' ||
  1066. window.location.href.startsWith('https://asuracomics.com//manga/')
  1067. ) {
  1068. return;
  1069. }
  1070.  
  1071. // Create a mutation observer to watch for changes in the URL
  1072. const observer = new MutationObserver(() => {
  1073. storeChapterData();
  1074. });
  1075.  
  1076. // Observe changes in the URL
  1077. observer.observe(document.documentElement, { childList: true, subtree: true });
  1078.  
  1079. // Call the function on page load
  1080. storeChapterData();
  1081. })();