JM Shelf - UI Recs

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

Este script não deve ser instalado diretamente. É uma biblioteca destinada a ser incluída por outros scripts através da diretiva de metadados // @require https://update.sleazyfork.org/scripts/581111/1842612/JM%20Shelf%20-%20UI%20Recs.js

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==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;
  }