JM Shelf - UI Recs

首页推荐卡片展示 — JM Shelf 推荐脚本的模块库,通过 @require 被主脚本引用。

이 스크립트는 직접 설치하는 용도가 아닙니다. 다른 스크립트에서 메타 지시문 // @require https://update.sleazyfork.org/scripts/581111/1842612/JM%20Shelf%20-%20UI%20Recs.js을(를) 사용하여 포함하는 라이브러리입니다.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         JM Shelf - UI Recs
// @namespace    jmshelf-lib
// @version      1.0.0
// @author       Kesdi
// @description  首页推荐卡片展示 — JM Shelf 推荐脚本的模块库,通过 @require 被主脚本引用。
// @license      MIT
// ==/UserScript==
// 
// 此文件是 GreasyFork 库(library),不直接安装。
// 请安装主脚本: JM Shelf 给杂鱼的个性化推荐
//

// ═══ [13] UI RECOMMENDATIONS ═══ (on homepage)
  // ============================================================
  function injectRecommendations(recommendations) {
    const existing = document.getElementById('jms-recommendations');
    if (existing) existing.remove();
    if (!recommendations || recommendations.length === 0) return;

    const displayItems = recommendations.slice(0, CONFIG.DISPLAY_COUNT);

    const wrapper = document.createElement('div');
    wrapper.id = 'jms-recommendations';
    wrapper.className = 'col-lg-12 col-md-12';

    const headerRow = document.createElement('div');
    headerRow.className = 'row';
    headerRow.innerHTML = '<div class="pull-left"><h4 class="talk-title"><img class="img-circle" src="https://cdn-msp3.18comic.vip/templates/frontend/airav/img/title-png/title-circle.png" alt=""><span class="p-r-15 p-l-15 p-t-5 p-b-5" style="margin-left:5px">' + '\uD83C\uDFAF' + ' 为你推荐</span></h4></div><div class="pull-right m-t-10" style="cursor:pointer" title="点击刷新推荐"><span class="talk-more-btn"><span id="jms-refresh-btn" class="p-r-10 p-l-10 p-t-5 p-b-5" style="cursor:pointer;color:#fff;margin-right:3px" title="点击刷新推荐">刷新</span><img class="img-circle" src="https://cdn-msp3.18comic.vip/templates/frontend/airav/img/title-png/more-ms-jm.webp" alt=""></span></div>';
    wrapper.appendChild(headerRow);

    const carouselRow = document.createElement('div');
    carouselRow.className = 'row m-b-10';
    const carousel = document.createElement('div');
    carousel.className = 'owl-comic-block';
    carousel.style.cssText = 'display:flex;flex-direction:row;overflow-x:auto;overflow-y:hidden;padding-left:1.5rem;list-style:none';
    carouselRow.appendChild(carousel);
    wrapper.appendChild(carouselRow);

    const firstHeading = document.querySelector('.talk-title');
    if (firstHeading) {
      const sd = firstHeading.closest('.col-lg-12,.col-md-12') || firstHeading.closest('.row') ? firstHeading.closest('.row').parentElement : null;
      if (sd && sd.parentNode) sd.parentNode.insertBefore(wrapper, sd);
      else (document.querySelector('.container') || document.body).insertBefore(wrapper, (document.querySelector('.container') || document.body).firstChild);
    } else {
      (document.querySelector('.container, [class*="content"], main') || document.body).insertBefore(wrapper, (document.querySelector('.container, [class*="content"], main') || document.body).firstChild);
    }

    setTimeout(() => {
      const pr = wrapper.querySelector('.pull-right.m-t-10');
      if (pr) {
        pr.addEventListener('click', (e) => {
          _cachedReranked = lightRescore();
          _lastLightRescore = Date.now(); _lastCachedT = CONFIG.TEMPERATURE;
          if (_cachedReranked.length > 0) {
            injectRecommendations(_cachedReranked);
          }
          showNotification('🔄 已刷新 (T='+CONFIG.TEMPERATURE.toFixed(2)+')');
        });
      }
    }, 100);

    // Build one card
    function _b(item) {
      const al = State.getAlbumCache()[item.id];
      const ti = al ? al.title || item.title || '' : item.title || '';
      const ta = al ? al.tags || [] : [];
      const au = al ? al.authors || [] : [];
      const tt = al && al.typeTags ? al.typeTags[0] || '' : '';
      const st = al && al.typeTags ? al.typeTags[1] || ta[0] || '' : ta[0] || '';
      const lc = al ? al.likes || al.views || 0 : 0;
      const ls = lc >= 1000 ? (lc/1000).toFixed(0)+'K' : String(lc);
      const mp = Math.round(item.score * 100);
      let rl = '\uD83D\uDCCA 综合', rc = '#0f9';
      var wm = item._wm || '';
      if (wm === 'author+') { rl = '\u270F\uFE0F 作者'; rc = '#e91e63'; }
      else if (wm === 'tag+') { rl = '\uD83C\uDFF7\uFE0F 标签'; rc = '#2196f3'; }
      else if (wm === 'surprise') { rl = '\uD83C\uDFB2 新发现'; rc = '#ff5722'; }
      else if ((item.channels||[]).includes('author')) { rl = '\u270F\uFE0F 作者'; rc = '#e91e63'; }
      else if ((item.channels||[]).some(function(x){return x==='explore'||x==='discover'})) { rl = '\uD83C\uDFB2 探索'; rc = '#9c27b0'; }
      else if ((item.channels||[]).length >= 5) { rl = '\uD83D\uDD17 多源'; rc = '#ff9800'; }
      var tu = 'https://cdn-msp2.18comic.vip/media/albums/' + item.id + '_3x4.jpg';
      var cd = document.createElement('div');
      cd.className = 'owl-item';
      cd.style.cssText = 'width:180px;margin-right:8px;flex-shrink:0';
      cd.innerHTML = '<div class="p-b-15 p-l-5 p-r-5" style="padding-top:0;background:initial;box-shadow:none;margin:0"><div class="thumb-overlay-albums"><a href="/album/' + item.id + '/"><img src="' + tu + '" title="' + escapeHtml(ti) + '" alt="' + escapeHtml(ti) + '" class="img-responsive img-rounded" style="width:100%;aspect-ratio:3/4;object-fit:cover" loading="lazy" referrerpolicy="no-referrer" onerror="var s=this,t=s.src;t.onerror=function(){t.onerror=null;t.src=\'https://cdn-msp.18comic.vip/media/albums/' + item.id + '_3x4.jpg\'};t.src=s.indexOf(\'cdn-msp2\')>-1?\'https://cdn-msp3.18comic.vip/media/albums/' + item.id + '_3x4.jpg\':s.indexOf(\'cdn-msp3\')>-1?\'https://cdn-msp.18comic.vip/media/albums/' + item.id + '_3x4.jpg\':\'https://cdn-msp2.18comic.vip/media/albums/' + item.id + '_3x4.jpg\'"></a><div class="label-loveicon"><a><i class="glyphicon glyphicon-heart"></i><span class="text-white">' + ls + '</span></a></div><div class="label-star" style="cursor:pointer" data-jms-dismiss="' + item.id + '" title="' + '\u4E0D\u611F\u5174\u8DA3' + '"><i style="color:#f66" class="far fa-times-circle"></i></div><div class="label-latest"><div class="label-sub" style="background-color:' + rc + ';font-size:10px">' + rl + '</div><br><div class="label-sub" style="background:#2a2a4a;color:#ffd166;padding:1px 4px;border-radius:2px;margin-top:2px">' + mp + '%</div></div>' + (tt ? '<div class="category-icon"><div class="label-category">' + escapeHtml(tt) + '</div>' + (st ? '<div class="label-sub" style="background-color:#ff7a00">' + escapeHtml(st) + '</div>' : '') + '</div>' : '') + '</div><span class="video-title title-truncate-index m-t-5" style="display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;font-size:13px">' + escapeHtml(ti.substring(0, 80)) + '</span><div class="title-truncate-index hidden-xs" style="font-size:11px;color:#888">' + au.slice(0, 2).map(function(a){return '<a href="/search/photos?search_query=' + encodeURIComponent(a) + '&main_tag=2">' + escapeHtml(a) + '</a>'}).join(', ') + ' ' + ta.slice(0, 2).filter(function(t){return !getAllBlacklistedTags().includes(t)}).map(function(t){return '<a href="/search/photos?search_query=' + encodeURIComponent(t) + '">' + escapeHtml(t) + '</a>'}).join(' ') + '</div><div class="clearfix"></div></div>';
      var dis = cd.querySelector('[data-jms-dismiss]');
      if (dis) dis.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); dismissRecommendation(item.id); cd.remove(); });
      return cd;
    }

    var _i = 0, _B = 12;
    function _r() {
      var end = Math.min(_i + _B, displayItems.length);
      for (; _i < end; _i++) carousel.appendChild(_b(displayItems[_i]));
      if (_i < displayItems.length) { requestAnimationFrame(_r); }
    }
    requestAnimationFrame(_r);
  }

  function dismissRecommendation(id) {
    const bl = getBlacklist();
    bl.albums.push(id);
    setBlacklist(bl);
    const recs = State.getRecommendations();
    State.saveRecommendations(recs.filter(r => r.id !== id));
    showNotification('已隐藏,不再推荐此漫画');
  }

  function escapeHtml(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
  }