Mananelo/Mangakakalot/Manganato/Manga4life Bookmarks Export

Writes Mangakakalot, Manganelo, Manganato Bookmarks (name and visited number) to "manga_bookmarks.txt" on "Export Bookmarks" button click

目前为 2025-03-03 提交的版本。查看 最新版本

// ==UserScript==
// @name         Mananelo/Mangakakalot/Manganato/Manga4life Bookmarks Export
// @namespace    http://smoondev.com/
// @version      2.30
// @description  Writes Mangakakalot, Manganelo, Manganato Bookmarks (name and visited number) to "manga_bookmarks.txt" on "Export Bookmarks" button click
// @author       Shawn Moon
// @match        https://*.mangakakalot.gg/bookmark*
// @match        https://*.nelomanga.com/bookmark*
// @match        https://*.natomanga.com/bookmark*
// @match        https://*.manganato.gg/bookmark*
// @match        https://mangakakalot.fun/user/bookmarks
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js
// ==/UserScript==

(function () {
  'use strict'

    function addBookarkStyles(css) {
    const head = document.head || document.getElementsByTagName('head')[0]
    if (!head) return
    const style = document.createElement('style')
    style.type = 'text/css'
    style.innerHTML = css
    head.appendChild(style)
  }

  addBookarkStyles(`
#export_container_nato, #export_container_kakalot, #export_container_m4l {
  color: #000;
  cursor: pointer;
  float: right;
}

#export_container_fun {
  display: inline-flex;
  vertical-align: bottom;
  align-items: baseline;
  margin-top: 20px;
  text-align: right;
  float: right;
  margin-right: 20px;
}

#export_container_kakalot {
  margin-right: 10px;
}

#export_nato:hover, #export_kakalot:hover, #export_m4l:hover {
  background-color: #b6e4e3;
  color: #000;
  cursor: pointer;
}

#export_nato, #export_kakalot, #export_m4l {
  border-radius: 5px;
  text-decoration: none;
  color: #fff;
  background-color: #76cdcb;
  border: none;
  font-weight: 600;
}

#export_nato, #export_kakalot {
  padding: 4px 8px;
}

#export_m4l {
  padding: 1px 12px;
  font-size: 16.5px;
}

#export_fun {
  color: #f05759;
  background-color: #fff;
  border: 1px solid #f05759;
  display: inline-block;
  margin-bottom: 0;
  font-weight: 400;
  text-align: center;
  touch-action: manipulation;
  cursor: pointer;
  white-space: nowrap;
  padding: 6px 12px;
  border-radius: 0;
  user-select: none;
  transition: all .2s ease-in-out;
}

#export_fun:hover {
  color: #fff;
  background-color: #f05759;
}

#inclURL_nato, #inclURL_kakalot, #inclURL_fun, #inclURL_m4l {
  margin-left: 10px;
}

.inclURL_kakalot, .inclURL_m4l {
  color: #ffffff;
}

.inclURL_m4l {
  color: #ffffff;
  font-size: 15px;
  font-weight: 500;
}

.inclURL_fun {
  font-weight:normal;
}

#temp_data {
  position: absolute;
  top: -9999px;
  left: -9999px;
}
`)

  let pageI,
    bmTag,
    bmTitle,
    lastViewed,
    btnContainer,
    exportButtonID,
    inclURL,
    bookmarkedTitles = '',
    exportContainer,
    pageCount = 0,
    domain = window.location.hostname,
    tld = domain.replace('www.', ''),
    bmLabel = 'Bookmarks'

  let mangagakalotDomains = [
    'mangakakalot.gg',
    'nelomanga.com',
    'natomanga.com',
    'manganato.gg'
  ]

  if (mangagakalotDomains.includes(tld)) {
    pageI = '.group-page a'
    bmTag = '.user-bookmark-item-right'
    bmTitle = '.bm-title'
    lastViewed = 'span:nth-of-type(2) a'
    btnContainer = '.breadcrumbs p'
    inclURL = 'inclURL_kakalot'

    let pageElList = document.querySelectorAll(pageI)
    if (pageElList.length > 0) {
      let lastText = pageElList[pageElList.length - 1].textContent
      pageCount = parseInt(lastText.replace(/\D+/g, ''), 10) || 0
    }
    exportButtonID = 'export_kakalot'
    exportContainer = 'export_container_kakalot'
  } else if (domain.indexOf('mangakakalot.fun') !== -1) {
    bmTag = '.list-group-item'
    bmTitle = '.media-heading a'
    lastViewed = '.media-body p a'
    btnContainer = '.container-fluid:first-child div:last-child'
    inclURL = 'inclURL_fun'
    exportButtonID = 'export_fun'
    exportContainer = 'export_container_fun'
  }

  const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay))

  const saveFile = saveData => {
    const fileData = new Blob([saveData], { type: 'application/octet-stream' })
    saveAs(fileData, 'manga_bookmarks.txt')
    const btn = document.getElementById(exportButtonID)
    if (btn) {
      btn.innerHTML = `Export ${bmLabel}`
      btn.disabled = false
    }
  }

  const deleteTemp = () => {
    const tempData = document.getElementById('temp_data')
    if (tempData) {
      tempData.remove()
    }
  }

  const getCookie = (name = 'user_acc') => {
    let returnVal = false
    const value = `; ${document.cookie}`
    try {
      const parts = value.split(`; ${name}=`)
      if (parts.length === 2) {
        let user = parts.pop().split(';').shift()
        if (name === 'user_acc') {
          user = JSON.parse(decodeURIComponent(user))
          returnVal = user.user_data
        }
      }
    } catch (e) {
      returnVal = false
    }
    return returnVal
  }

  const getBMs = async (userCookie, currentPage = 1) => {
    const urlencoded = new URLSearchParams()
    urlencoded.append('out_type', 'json')
    urlencoded.append('bm_source', 'manganato')
    urlencoded.append('bm_page', currentPage)
    urlencoded.append('user_data', userCookie)

    try {
      const response = await fetch('https://user.mngusr.com/bookmark_get_list_full', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: urlencoded,
        redirect: 'follow'
      })
      return await response.json()
    } catch (error) {
      console.log('ExportError', error)
    }
  }

  const getFunBMs = url => {
    const bmItems = document.querySelectorAll(bmTag)
    for (let i = 0; i < bmItems.length; i++) {
      const titleElem = bmItems[i].querySelector(bmTitle)
      const title = titleElem ? titleElem.textContent : ''
      const lastViewedElem = bmItems[i].querySelector('.media-body p a')
      const lastViewedText = lastViewedElem ? lastViewedElem.textContent.trim() : 'None'
      const linkText = (url && lastViewedElem && lastViewedElem.href) ? `- ${lastViewedElem.href}` : ''
      bookmarkedTitles += `${title} || Viewed: ${lastViewedText} ${linkText} \n`
    }
    saveFile(bookmarkedTitles)
  }

  if (!document.getElementById(exportContainer)) {
    const btnContElem = document.querySelector(btnContainer)
    if (btnContElem) {
      btnContElem.insertAdjacentHTML(
        'beforeend',
        `<div id='${exportContainer}'>
           <button id='${exportButtonID}'>Export ${bmLabel}</button>
           <input type="checkbox" id="${inclURL}">
           <span style="margin-left: 5px;">
             <label for="${inclURL}" class='${inclURL}'>Add URL</label>
           </span>
         </div>`
      )
    }
  }

  const getBookmarks = (url, bookmarkHeader) => {
    deleteTemp()
    document.body.insertAdjacentHTML('beforeend', "<div id='temp_data'></div>")
    let bookmarkedContent = bookmarkHeader
    const tempData = document.getElementById('temp_data')
    const fetches = []
    for (let i = 0; i < pageCount; i++) {
      const pageId = `page${i + 1}`
      const pageDiv = document.createElement('div')
      pageDiv.id = pageId
      tempData.appendChild(pageDiv)
      const fetchPromise = fetch(`https://${domain}/bookmark?page=${i + 1}`)
        .then(response => response.text())
        .then(htmlText => {
          const parser = new DOMParser()
          const doc = parser.parseFromString(htmlText, 'text/html')
          const items = doc.querySelectorAll(bmTag)
          pageDiv.innerHTML = Array.from(items)
            .map(item => item.outerHTML)
            .join('')
        })
        .catch(error => console.error('ExportError', error))
      fetches.push(fetchPromise)
    }
    Promise.all(fetches).then(() => {
      const bmItems = document.querySelectorAll(`#temp_data ${bmTag}`)
      bmItems.forEach(item => {
        const titleElem = item.querySelector(bmTitle)
        if (titleElem && titleElem.textContent) {
          const lastViewedElem = item.querySelector(lastViewed)
          const viewedText = lastViewedElem
            ? lastViewedElem.textContent.trim()
            : 'Not Found'
          const linkPart =
            url && lastViewedElem && lastViewedElem.href
              ? `- ${lastViewedElem.href}`
              : ''
          bookmarkedContent += `${titleElem.textContent.trim()} ||  Viewed: ${viewedText} ${linkPart} \n`
        }
      })
      saveFile(bookmarkedContent)
      deleteTemp()
    })
  }

  const exportButton = document.getElementById(exportButtonID)
  if (exportButton) {
    exportButton.addEventListener('click', async function () {
      let bookmarkHeader = `===========================\n${domain} ${bmLabel}\n===========================\n`
      bookmarkedTitles = bookmarkHeader
      const inclURLCheck = document.getElementById(inclURL).checked

      if (mangagakalotDomains.includes(tld)) {
        exportButton.innerHTML = 'Generating File...'
        exportButton.disabled = true
        getBookmarks(inclURLCheck, bookmarkedTitles)
      } else if (domain === 'mangakakalot.fun') {
        getFunBMs(inclURLCheck)
      }
    })
  }
})()