BGM 角色图廊

在角色主页面插入“图廊”Tab;在上传图片页显示图廊画廊.

当前为 2025-07-11 提交的版本,查看 最新版本

// ==UserScript==
// @name         BGM 角色图廊
// @namespace    https://github.com/stay206
// @version      1.0
// @description  在角色主页面插入“图廊”Tab;在上传图片页显示图廊画廊.
// @match        https://bgm.tv/character/*
// @match        https://bangumi.tv/character/*
// @match        https://chii.in/character/*
// @match        https://bgm.tv/character/*/upload_img
// @match        https://bangumi.tv/character/*/upload_img
// @match        https://chii.in/character/*/upload_img
// @grant        GM_xmlhttpRequest
// @connect      bangumi.tv
// @connect      api.github.com
// @connect      raw.githubusercontent.com
// @connect      raw.fastgit.org
// @connect      ghproxy.com
// @license MIT
// ==/UserScript==

;(async function() {
  'use strict';

  const path = location.pathname;

  // —— 主页面:插入“图廊”Tab ——
  const mainMatch = path.match(/^\/character\/(\d+)(?:\/?)$/);
  if (mainMatch) {
    const charId = mainMatch[1];
    const nav = document.querySelector('ul.navTabs');
    if (nav && !nav.querySelector(`a[href="/character/${charId}/upload_img"]`)) {
      const li = document.createElement('li');
      const a  = document.createElement('a');
      a.href        = `/character/${charId}/upload_img`;
      a.textContent = '图廊';
      a.classList.add('bve-processed');
      if (path.endsWith('/upload_img')) a.classList.add('focus');
      li.appendChild(a);
      const relateLi = nav.querySelector('li.relate-button');
      if (relateLi) relateLi.insertAdjacentElement('afterend', li);
      else nav.appendChild(li);
    }
  }

  // —— 上传页:渲染图廊 ——
  const uploadMatch = path.match(/^\/character\/(\d+)\/upload_img/);
  if (!uploadMatch) return;
  const charId = uploadMatch[1];

  // 2. 在左侧首部插入容器和空列表
  const container = document.getElementById('columnInSubjectA');
  if (!container) return;
  container.insertAdjacentHTML('afterbegin', `
<br>
<h2>已经提供的图片</h2>
<div class="clearit">
  <ul class="photoList"></ul>
</div>`);
  const listEl = container.querySelector('ul.photoList');
  if (!listEl) return;

  // 3. 封面
  function fetchCover() {
    return new Promise(resolve => {
      GM_xmlhttpRequest({
        method: 'GET',
        url: `https://bangumi.tv/character/${charId}`,
        onload(res) {
          if (res.status !== 200) return resolve(null);
          const doc = new DOMParser().parseFromString(res.responseText, 'text/html');
          const img = doc.querySelector('div[align="center"] img.cover');
          resolve(img?.src || null);
        },
        onerror() { resolve(null); }
      });
    });
  }
  const coverUrl = await fetchCover();

  // 4. 寻找条目肖像贡献者
  let groupHref = '', groupName = '';
  const allLi = Array.from(document.querySelectorAll('ul.groupsLine li'));
  let entryLi = allLi.find(li => li.querySelector('span.comment.tip')?.textContent.trim() === '新肖像');
  if (!entryLi) {
    entryLi = allLi.find(li => li.querySelector('span.comment.tip')?.textContent.trim() === '新条目');
  }
  if (entryLi) {
    const a = entryLi.querySelector('a.l.bve-processed');
    if (a && a.href) {
      groupHref = a.href;
      groupName = a.textContent.trim();
    }
  }

  // 5. 角色本身封面
if (coverUrl) {
    const li = document.createElement('li');
    let inner = `
  <a href="${coverUrl}" class="grid thickbox bve-processed" target="_blank">
    <img src="${coverUrl}" width="100" class="grid">`;
    // 只有当 href 和 name 均存在时,才插入贡献者信息
    if (groupHref && groupName) {
      inner += `
    <p>
      <span class="tip_j">
        by <a href="${groupHref}" class="l bve-processed" target="_blank">${groupName}</a>
      </span>
    </p>`;
    }
    inner += `
  </a>`;
    li.innerHTML = inner;
    listEl.insertAdjacentElement('afterbegin', li);
  }

  // 6. 读取GitHub对应ID图片
  async function listFiles() {
    try {
      const apiUrl =
        `https://api.github.com/repos/stay206/bangumi-character/contents/${charId}?ref=main`;
      const res = await fetch(apiUrl, { headers: { 'Accept': 'application/vnd.github.v3+json' } });
      if (res.ok) {
        const js = await res.json();
        if (Array.isArray(js)) {
          const names = js.filter(f => f.type === 'file' && /\.(?:jpe?g|png|gif)$/i.test(f.name))
                          .map(f => f.name);
          if (names.length) return names;
        }
      }
    } catch {}
    try {
      const url = `https://github.com/stay206/bangumi-character/tree/main/${charId}`;
      const res = await fetch(url);
      if (res.ok) {
        const txt = await res.text();
        const re  = /<a[^>]+class="js-navigation-open"[^>]+title="([^"]+\.(?:jpe?g|png|gif))"/gi;
        const names = [];
        let m;
        while ((m = re.exec(txt))) names.push(m[1]);
        if (names.length) return names;
      }
    } catch {}
    return [];
  }

  // 7. 获取最新GitHub图片贡献者
  async function fetchAuthor(fileName) {
    try {
      const commitsApi =
        `https://api.github.com/repos/stay206/bangumi-character/commits`
        + `?path=${encodeURIComponent(`${charId}/${fileName}`)}&per_page=1`;
      const res = await fetch(commitsApi);
      if (!res.ok) throw new Error();
      const js = await res.json();
      const c  = Array.isArray(js) ? js[0] : js;
      const real = c?.commit?.author?.name || '';
      const usr  = c?.author;
      const url  = usr?.html_url || (usr?.login ? `https://github.com/${usr.login}` : '#');
      return { name: real || usr?.login || 'unknown', url };
    } catch {
      return { name: 'unknown', url: '#' };
    }
  }

  // 8. 渲染所有仓库图片
  const files = await listFiles();
  for (const name of files) {
    const author = await fetchAuthor(name);
    const cdnUrls = [
      `https://cdn.jsdelivr.net/gh/stay206/bangumi-character@main/${charId}/${encodeURIComponent(name)}`,
      `https://raw.fastgit.org/stay206/bangumi-character/main/${charId}/${encodeURIComponent(name)}`,
      `https://raw.githubusercontent.com/stay206/bangumi-character/main/${charId}/${encodeURIComponent(name)}`,
      `https://ghproxy.com/https://raw.githubusercontent.com/stay206/bangumi-character/main/${charId}/${encodeURIComponent(name)}`
    ];
    let idx = 0;
    const img = document.createElement('img');
    img.width     = 100;
    img.className = 'grid';
    img.src       = cdnUrls[idx];
    img.onerror   = () => { if (++idx < cdnUrls.length) img.src = cdnUrls[idx]; };

    const a = document.createElement('a');
    a.href        = cdnUrls[0];
    a.className   = 'grid thickbox bve-processed';
    a.target      = '_blank';
    a.appendChild(img);

    const p = document.createElement('p');
    p.innerHTML = `
      <span class="tip_j">
        by <a href="${author.url}" class="l bve-processed" target="_blank">${author.name}</a>
      </span>`;
    a.appendChild(p);

    const li = document.createElement('li');
    li.appendChild(a);
    listEl.appendChild(li);
  }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址