Sleazy Fork is available in English.

GCBT显示预览图和磁力链接

列表页显示全部预览图和磁力链接

// ==UserScript==
// @name         GCBT显示预览图和磁力链接
// @namespace    http://tampermonkey.net/
// @version      2025-1-6
// @description  列表页显示全部预览图和磁力链接
// @author       You
// @match        https://gcbt.net
// @match        https://gcbt.net/page/*
// @match        https://gcbt.net/?s=*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gcbt.net
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      www.rmdown.com
// @connect      bt.azvmw.com
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 预览大图
    GM_addStyle(`
.preview-box {
  z-index: 10000;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: rgba(0, 0, 0, 0.3);
  display: flex;
  flex-flow: column;
}
.preview-box * {
  user-select: none;
}
.preview-box.hide {
  display: none;
}
.preview-image {
  flex: auto;
  background: no-repeat center center;
  background-size: contain;
}
.preview-list {
  white-space: nowrap;
  overflow-x: auto;
  text-align: center;
  background-color: #000;
}
.preview-item {
  display: inline-block;
  height: 100px;
  width: auto;
  min-width: 50px;
  min-height: 50px;
  background-color: #eee;
  cursor: pointer;
  opacity: 0.8;
  margin: 0 1px;
}
.preview-item:hover {
  opacity: 1;
}
.preview-item.cur {
  opacity: 1;
  outline: solid 2px #f00;
  outline-offset: -2px;
}
.preview-button {
  display: inline-block;
  width: 40px;
  height: 40px;
  text-align: center;
  align-content: center;
  cursor: pointer;
  font-size: 24px;
  font-weight: bold;
  color: white;
  background-color: #000;
  opacity: 0.5;
}
.preview-button:hover {
  opacity: 1;
}
#preview-index {
  position: fixed;
  bottom: 110px;
  left: 50%;
  transform: translateX(-50%);
  background-color: rgba(0, 0, 0, 0.2);
  color: #fff;
  font-size: 24px;
  padding: 5px 10px;
}
#prev-image, #next-image {
  position: fixed;
  top: calc(50vh - 120px);
}
#prev-image {
  left: 0px;
}
#next-image {
  right: 0px;
}
#preview-close {
  background-color: #f00;
  position: fixed;
  top: 0px;
  right: 0px;
}
`);
    const preview = {
        isShown: false,
        $root: null,
        $image: null,
        $prevImage: null,
        $nextImage: null,
        $index: null,
        $list: null,
        $curItem: null,
        images: null,
        index: -1,
        init() {
            this.$root = document.createElement('div');
            this.$root.className = 'preview-box';
            this.$root.innerHTML = `
              <div class="preview-image">
                <div class="preview-button" id="prev-image">‹</div>
                <div class="preview-button" id="next-image">›</div>
                <div id="preview-index">0 / 0</div>
              </div>
              <div class="preview-list"></div>
              <div class="preview-button" id="preview-close">×</div>`;
            document.body.appendChild(this.$root);
            // 上一张/下一张
            this.$prevImage = this.$root.querySelector('#prev-image');
            this.$nextImage = this.$root.querySelector('#next-image');
            this.$prevImage.onclick = () => this.prev();
            this.$nextImage.onclick = () => this.next();
            // 页码
            this.$index = this.$root.querySelector('#preview-index');
            // 大预览图
            this.$image = this.$root.querySelector('.preview-image');
            // 预览图列表
            this.$list = this.$root.querySelector('.preview-list');
            this.$list.onclick = (e) => {
                if (e.target.classList.contains('preview-item')) {
                    this.slideTo(parseInt(e.target.dataset.index));
                }
            };
            // 关闭
            this.$root.querySelector('#preview-close').onclick = () => this.close();
            // 快捷键
            window.addEventListener('keydown', (e) => {
                if (!this.isShown || e.repeat) return;
                console.log(e.code);
                if (e.code === 'ArrowLeft') this.prev();
                else if (e.code === 'ArrowRight') this.next();
                else if (e.code === 'Escape') this.close();
            });
        },
        show(images, index) {
            this.isShown = true;
            document.body.style.overflow = 'hidden';
            if (!this.$root) this.init();
            else this.$root.classList.remove('hide');
            this.setData(images);
            this.slideTo(index);
        },
        close() {
            this.isShown = false;
            document.body.style.overflow = '';
            this.$root.classList.add('hide')
        },
        setData(images) {
            this.images = images;
            this.$list.innerHTML = images.map((src, i) => `<img src="${src}" data-index="${i}" class="preview-item">`).join('');
            this.$cueItem = null;
        },
        slideTo(index) {
            this.$list.children[this.index]?.classList.remove('cur');
            this.$list.children[index].classList.add('cur');
            this.$list.children[index].scrollIntoView({ behavior: "smooth" });
            this.index = index;
            this.$image.style.backgroundImage = `url(${this.images[index]})`;
            this.$index.textContent = `${index + 1} / ${this.images.length}`;
        },
        prev() {
            this.slideTo(this.index === 0 ? this.images.length - 1 : this.index - 1);
        },
        next() {
            this.slideTo(this.index === this.images.length - 1 ? 0 : this.index + 1);
        }
    };

    // 列表
    GM_addStyle(`
.entry-media {
  position: relative;
}
.image-count {
  position: absolute;
  top: 0;
  right: 0;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  padding: 0px 8px;
}
`);
    document.querySelectorAll('article').forEach(async ($item) => {
        const url = $item.querySelector('.entry-title a')?.href;
        if (!url) return;
        // 获取详情页
        const $document = await fetch(url).then((response) => response.text()).then((text) => (new DOMParser()).parseFromString(text, 'text/html'));
        const $content = $document.querySelector('.entry-content') || $document;

        // 详情页图片列表
        const images = [...$content.querySelectorAll('img')].map(($img) => $img.src);
        // 原图片处理
        const $media = $item.querySelector('.entry-media a');
        if (images.length > 0) {
            // 移除原图片点击事件
            $media.href = 'javascript:;';
            $media.removeAttribute('target');
            // 原图片替换为详情页第一张图片
            const $img = $media.querySelector('img');
            $img.classList.remove('lazyload');
            $img.src = images[0];
            // 点击原图片显示预览大图
            $media.style.cursor = 'pointer';
            $media.onclick = () => preview.show(images, 0);
            // 原图片右上角显示详情页图片数
            const $count = document.createElement('span');
            $count.className = 'image-count';
            $count.textContent = images.length;
            $media.appendChild($count);
        } else {
            $media.style.pointerEvents = 'none';
        }

        const $time = $item.querySelector('.entry-footer time');
        const addLink = (href, text) => ($time.innerHTML += `<a href="${href}" target="_blank" style="margin-left: 10px">${text}</a>`);
        const addMagnetLink = (magnet) => addLink(magnet, '磁力链接');
        const addHashLink = (hash) => addMagnetLink(`magnet:?xt=urn:btih:${hash}`);
        const addDownloadLink = (href) => addLink(href, '下载链接');

        // 有磁力哈希信息,使用磁力哈希
        const hash = $content.querySelector('p:first-child')?.textContent?.match(/[0-9a-z]{40,40}/ig)?.[0];
        if (hash) return addHashLink(hash);

        // 明文磁力链接
        const magnet = $content.innerHTML.match(/magnet:\?xt=urn:btih:[0-9a-z]{40,40}/ig)?.[0];
        if (magnet) return addMagnetLink(magnet);

        // 获取中转页面,提取磁力哈希
        const getHash = (url) => {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    url,
                    method: "GET",
                    onload: (xhr) => resolve(xhr.responseText.match(/[0-9a-z]{40,40}/ig)?.[0]),
                    onerror: () => resolve(''),
                });
            });
        }

        // rmdown
        const rmdown = $content.innerHTML.match(new RegExp('//www.rmdown.com/link.php\\?hash=[0-9a-z]+'), 'i')?.[0];
        if (rmdown) {
            const hash = await getHash('https:' + rmdown);
            if (hash) return addHashLink(hash);
            addDownloadLink(url);
        }

        // azvmw
        const azvmw = $content.innerHTML.match(new RegExp('//bt.azvmw.com/list.php\\?name=[0-9a-z]+'), 'i')?.[0];
        if (azvmw) {
            const hash = await getHash('https:' + azvmw);
            if (hash) return addHashLink(hash);
            addDownloadLink(url);
        }

        // 显示其他链接
        let links = [...$content.querySelectorAll('a')].map(($a) => $a.href);
        // 去除无效链接和分享链接
        links = links.filter((href) => href.trim().length > 0 && !/\?share=/.test(href));
        // 去重
        links = [...new Set(links)];
        links.forEach((href, i) => addLink(href, `链接${i + 1}`));
    })
})();