PixAI Hide Specific Users

PixAI 上の特定ユーザーの作品を非表示にします。@username でブロックすると GraphQL API で表示名を自動取得するため、ニックネーム変更後も機能し続けます。

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         PixAI Hide Specific Users
// @namespace    https://github.com/syumari/PixAI-Hide-Specific-Users
// @version      1.1.0
// @description  PixAI 上の特定ユーザーの作品を非表示にします。@username でブロックすると GraphQL API で表示名を自動取得するため、ニックネーム変更後も機能し続けます。
// @description:en Hide artworks from specific users on PixAI. Supports blocking by @username which automatically resolves to their current display name via GraphQL API.
// @description:zh-CN 隐藏 PixAI 上特定用户的作品。支持通过 @username 屏蔽,脚本会自动通过 GraphQL API 获取当前显示名,即使用户更改昵称也能持续生效。
// @description:zh-TW 隱藏 PixAI 上特定使用者的作品。支援透過 @username 封鎖,腳本會自動透過 GraphQL API 取得目前顯示名稱,即使使用者更改暱稱也能持續運作。
// @description:ko PixAI에서 특정 사용자의 작품을 숨깁니다. @username으로 차단하면 GraphQL API를 통해 현재 표시 이름을 자동으로 가져오므로 닉네임이 변경되어도 계속 작동합니다.
// @author       syumari
// @match        https://pixai.art/*
// @run-at       document-idle
// @grant        none
// @license      MIT
// ==/UserScript==

(async () => {
  // --- Configuration ---
  // Block by display name ("nickname") or by username ("@username").
  // If you use "@username", the script will automatically fetch their current display name.
  const blockedUsers = [
    "対象ユーザー表示名",
    "@target_user_id"
  ];
  // ---------------------

  const resolvedBlockedNames = new Set();
  const usernameCache = new Map(); // Cache to avoid duplicate API calls

  // Resolve @username to display name via PixAI GraphQL API
  async function resolveUsernameToDisplayName(username) {
    // Remove '@' if present
    const cleanUsername = username.startsWith('@') ? username.substring(1) : username;
    
    if (usernameCache.has(cleanUsername)) {
      return usernameCache.get(cleanUsername);
    }

    const query = `
      query getUserInfoByUsername($username: String!) {
        user(username: $username) {
          id
          displayName
          username
        }
      }
    `;

    try {
      const response = await fetch("https://api.pixai.art/graphql", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ query, variables: { username: cleanUsername } })
      });
      
      const data = await response.json();
      if (data?.data?.user?.displayName) {
        const displayName = data.data.user.displayName;
        usernameCache.set(cleanUsername, displayName);
        return displayName;
      }
    } catch (error) {
      console.error(`[PixAI Hide Specific Users] Failed to resolve username: ${username}`, error);
    }
    return null;
  }

  // Initialize the blocklist by resolving all @usernames
  async function initBlocklist() {
    for (const user of blockedUsers) {
      if (user.startsWith('@')) {
        const displayName = await resolveUsernameToDisplayName(user);
        if (displayName) {
          resolvedBlockedNames.add(displayName);
          console.log(`[PixAI Hide Specific Users] Resolved ${user} -> ${displayName}`);
        }
      } else {
        resolvedBlockedNames.add(user);
      }
    }
    // Run hide function once initialization is complete
    hideBlockedCards();
  }

  function isBlocked(text) {
    if (!text) return false;
    for (const name of resolvedBlockedNames) {
      if (text.includes(name)) return true;
    }
    return false;
  }

  function hideBlockedCards() {
    // We don't have resolved names yet
    if (resolvedBlockedNames.size === 0) return;

    // Check all links
    const links = document.querySelectorAll("a[href]");

    links.forEach(link => {
      const text = link.innerText || link.textContent || "";
      const href = link.getAttribute("href") || "";

      // We only care if the link text contains a blocked display name
      if (!isBlocked(text)) return;

      // Find the card container
      let card = link;

      for (let i = 0; i < 5; i++) {
        if (!card.parentElement) break;

        const parent = card.parentElement;
        const rect = parent.getBoundingClientRect();

        // Safety check to avoid hiding the whole page
        if (rect.width > window.innerWidth * 0.9) break;
        if (rect.height > window.innerHeight * 0.9) break;

        card = parent;
      }

      if (card.style.display !== "none") {
        card.style.display = "none";
        // console.log(`[PixAI Hide Specific Users] Hid card for blocked user`);
      }
    });
  }

  // Start initialization
  initBlocklist();

  // Observe DOM changes to hide new cards as they load
  const observer = new MutationObserver(() => {
    hideBlockedCards();
  });

  observer.observe(document.body, {
    childList: true,
    subtree: true
  });

  // Handle SPA navigation
  let lastUrl = location.href;
  setInterval(() => {
    if (location.href !== lastUrl) {
      lastUrl = location.href;
      setTimeout(hideBlockedCards, 500);
      setTimeout(hideBlockedCards, 1500);
    }
  }, 700);
})();