Greasy Fork

来自缓存

Greasy Fork is available in English.

Emby Hide Media Configurable Tag

Add a "Hide Media" option to Emby context menu to tag all versions of selected media with a configurable tag

当前为 2025-06-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Emby Hide Media Configurable Tag
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  Add a "Hide Media" option to Emby context menu to tag all versions of selected media with a configurable tag
// @author       Baiganjia
// @match        http://127.0.0.1:8886/*
// @grant        none
// ==/UserScript==


(function() {
    'use strict';

    // Configuration: Change HIDE_TAG to your desired tag name
    const HIDE_TAG = '待批判'; // Modify this to any tag, e.g., '隐藏', '待删除'
    // Emby server URL and API key
    const EMBY_URL = 'http://127.0.0.1:8886';
    const API_KEY = 'c81ce450fe4c4b2db8ac0d592d6192ef';

    // Debounce utility to prevent excessive function calls
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // Function to add "Hide Media" option to context menu
    function addHideMediaOption() {
        const actionSheet = document.querySelector('.actionSheetScroller');
        if (!actionSheet || document.querySelector('#hideMedia')) return;

        const menuItem = document.createElement('button');
        menuItem.className = 'listItem listItem-autoactive itemAction listItemCursor listItem-hoverable actionSheetMenuItem actionSheetMenuItem-iconright';
        menuItem.id = 'hideMedia';
        menuItem.setAttribute('data-id', 'hideMedia');
        menuItem.setAttribute('data-action', 'custom');
        menuItem.innerHTML = `
            <div class="listItem-content listItem-content-bg listItemContent-touchzoom listItem-border">
                <div class="actionSheetItemImageContainer actionSheetItemImageContainer-customsize actionSheetItemImageContainer-transparent listItemImageContainer listItemImageContainer-margin listItemImageContainer-square defaultCardBackground" style="aspect-ratio:1">
                    <i class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent md-icon listItemIcon md-icon autortl">visibility_off</i>
                </div>
                <div class="actionsheetListItemBody actionsheetListItemBody-iconright listItemBody listItemBody-1-lines">
                    <div class="listItemBodyText actionSheetItemText listItemBodyText-nowrap listItemBodyText-lf">隐藏媒体</div>
                </div>
            </div>
        `;
        menuItem.addEventListener('click', hideSelectedMedia);
        actionSheet.querySelector('.actionsheetScrollSlider').appendChild(menuItem);
    }

    // Function to get TmdbId for a given mediaId
    async function getTmdbId(mediaId) {
        try {
            const response = await fetch(`${EMBY_URL}/Items/${mediaId}?Fields=ProviderIds&api_key=${API_KEY}`, {
                method: 'GET',
                headers: { 'Accept': 'application/json' }
            });
            if (!response.ok) throw new Error(`获取 TmdbId 失败: ${response.status}`);
            const data = await response.json();
            const tmdbId = data?.ProviderIds?.Tmdb;
            if (!tmdbId) throw new Error('TmdbId 未找到');
            console.log(`媒体 ${mediaId} 的 TmdbId: ${tmdbId}`);
            return tmdbId;
        } catch (error) {
            console.warn(`无法获取媒体 ${mediaId} 的 TmdbId:`, error);
            return null;
        }
    }

    // Function to get all ItemIds for a given TmdbId
    async function getItemIdsByTmdbId(tmdbId) {
        try {
            const response = await fetch(`${EMBY_URL}/Items?ProviderIds.Tmdb=${tmdbId}&IncludeItemTypes=Movie&api_key=${API_KEY}`, {
                method: 'GET',
                headers: { 'Accept': 'application/json' }
            });
            if (!response.ok) throw new Error(`查询 TmdbId ${tmdbId} 的版本失败: ${response.status}`);
            const data = await response.json();
            const itemIds = data?.Items?.map(item => item.Id) || [];
            console.log(`TmdbId ${tmdbId} 对应的 ItemIds: ${itemIds.join(', ')}`);
            return itemIds;
        } catch (error) {
            console.warn(`无法查询 TmdbId ${tmdbId} 的版本:`, error);
            return [];
        }
    }

    // Function to add configurable tag to a media item
    async function addTagToMedia(mediaId) {
        try {
            const response = await fetch(`${EMBY_URL}/Items/${mediaId}/Tags/Add?api_key=${API_KEY}`, {
                method: 'POST',
                headers: {
                    'Accept': '*/*',
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ Tags: [{ Name: HIDE_TAG }] })
            });
            if (!response.ok) throw new Error(`添加标签失败 (Tags format): ${response.status}`);
            console.log(`媒体 ${mediaId} 通过 Tags 格式成功添加“${HIDE_TAG}”标签`);
            return true;
        } catch (error) {
            console.warn(`为媒体 ${mediaId} 使用 Tags 格式添加标签失败:`, error);
            // Fallback to TagItems format
            try {
                const fallbackResponse = await fetch(`${EMBY_URL}/Items/${mediaId}/Tags/Add?api_key=${API_KEY}`, {
                    method: 'POST',
                    headers: {
                        'Accept': '*/*',
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ TagItems: [HIDE_TAG] })
                });
                if (!fallbackResponse.ok) throw new Error(`添加标签失败 (TagItems format): ${fallbackResponse.status}`);
                console.log(`媒体 ${mediaId} 通过 TagItems 格式成功添加“${HIDE_TAG}”标签`);
                return true;
            } catch (fallbackError) {
                console.error(`为媒体 ${mediaId} 添加标签失败:`, fallbackError);
                return false;
            }
        }
    }

    // Function to handle "Hide Media" action
    async function hideSelectedMedia() {
        // Try multiple selectors to find selected items
        let selectedItems = document.querySelectorAll('.selectionCommandsPanel input[type=checkbox]:checked');
        let context = 'multi-select';
        if (selectedItems.length === 0) {
            // Fallback for single selection or context menu
            selectedItems = document.querySelectorAll('.card[data-id].selected, .card[data-id]:has(input[type=checkbox]:checked), .card[data-id][data-context]');
            context = 'single-select';
        }

        if (selectedItems.length === 0) {
            console.warn('未找到选中的媒体项目');
            alert('请先选择至少一个媒体项目!');
            return;
        }

        console.log(`选中的项目 (${context}):`, selectedItems.length);
        let successCount = 0;
        let failureCount = 0;

        for (const item of selectedItems) {
            // Get data-id from card or parent
            const card = item.closest('.card') || item;
            const mediaId = card.getAttribute('data-id');
            if (!mediaId) {
                console.warn('无法获取媒体ID:', card);
                failureCount++;
                continue;
            }

            console.log(`处理媒体ID: ${mediaId}`);
            // Get TmdbId for the media
            const tmdbId = await getTmdbId(mediaId);
            let itemIds = [mediaId]; // Fallback to single mediaId if TmdbId fails

            // If TmdbId is available, get all related ItemIds
            if (tmdbId) {
                const relatedItemIds = await getItemIdsByTmdbId(tmdbId);
                if (relatedItemIds.length > 0) {
                    itemIds = relatedItemIds;
                }
            }

            // Add tag to each ItemId
            for (const id of itemIds) {
                console.log(`为版本 ItemId ${id} 添加标签`);
                const success = await addTagToMedia(id);
                if (success) {
                    successCount++;
                } else {
                    failureCount++;
                }
            }
        }

        // Show completion message and trigger page refresh
        alert(`操作完成!成功为 ${successCount} 个媒体版本添加“${HIDE_TAG}”标签,${failureCount} 个失败。页面将自动刷新以应用隐藏效果。`);
        setTimeout(() => {
            location.reload();
        }, 1000); // Delay refresh by 1 second to allow user to read message

        // Close the action sheet
        const actionSheet = document.querySelector('.actionSheet');
        if (actionSheet) actionSheet.remove();
    }

    // Debounced function to add menu item
    const debouncedAddHideMediaOption = debounce(addHideMediaOption, 100);

    // Observe actionSheet container
    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            if (mutation.addedNodes.length) {
                const actionSheet = document.querySelector('.actionSheet');
                if (actionSheet) {
                    debouncedAddHideMediaOption();
                }
            }
        }
    });

    // Start observing with limited scope
    observer.observe(document.body, { childList: true, subtree: false });

    // Ensure menu is added when actionSheet appears
    document.addEventListener('click', () => {
        if (document.querySelector('.actionSheet')) {
            debouncedAddHideMediaOption();
        }
    });
})();