分享几个EMBY美化插件和外挂播放器

分享几个EMBY美化插件和实用小工具

1、emby crx

Emby 增强/美化 插件 (适用于 Chrome 内核浏览器)

github链接 https://github.com/Nolovenodie/emby-crx

效果图:

图片[1]-分享几个EMBY美化插件和外挂播放器-杨公子的博客
美化效果

视频演示效果:

2、embyExternalUrl

直接调用外部播放器油猴脚本

油猴脚本链接:https://greasyfork.org/en/scripts/459297-embylaunchpotplayer

emby调用外部播放器服务端脚本:

通过nginx的njs模块运行js脚本,在emby视频的外部链接处添加调用外部播放器链接,所有emby官方客户端可用

效果如下:

图片[2]-分享几个EMBY美化插件和外挂播放器-杨公子的博客
调用外部播放器

脚本备份:

// ==用户脚本==
// @name embyLaunchPotplayer
// @name:enembyLaunchPotplayer
// @name:zh embyLaunchPotplayer
// @name:zh-CN embyLaunchPotplayer
// @命名空间 http://tampermonkey.net/
// @版本1.1.0
// @description emby 启动外部播放器
// @description:zh-cn emby调用外部播放器
// @description:en 嵌入到外部播放器
// @license麻省理工学院
// @作者 @bpking
// @github https://github.com/bpking1/embyExternalUrl
// @include */web/index.html
// ==/用户脚本==
(功能(){  
    '严格使用' ;
    函数初始化(){
        让playBtns =文档. getElementById ( "ExternalPlayersBtns" );
        如果(播放Btns ){
            播放按钮。删除();
        }
        让mainDetailButtons =文档. querySelector ( "div[is='emby-scroller']:not(.hide) .mainDetailButtons" );
        让buttonhtml =`< div id =“ ExternalPlayersBtns ”类=“ detailButtons flexalign - items - flex - start flex -wrap -wrap ” >
            <按钮 id = "embyPot" type = "button" class = "detailButton emby-button emby-button-backdropfilter raise-backdropfilterDetailButton-primary" title = "Potplayer" > < div class = "detailButton-content" > < iclass = "md-icondetailButton-iconbutton-iconbutton-icon-lefticon-PotPlayer" > </i> <span class="button-text">锅</span> </div> < /按钮>     
            <按钮 id = "embyVlc" type = "button" class = "detailButton emby-button emby-button-backdropfilter raise-backdropfilterDetailButton-primary" title = "VLC" > < div class = "detailButton-content" > < i类= "md-icondetailButton-iconbutton-iconbutton-icon-lefticon-VLC" > </i> <span class="button-text">VLC </span> </div> < /按钮>     
            <按钮 id = "embyIINA" type = "button" class = "detailButton emby-button emby-button-backdropfilter raise-backdropfilterDetailButton-primary" title = "IINA" > < div class = "detailButton-content" > < i类= "md-icondetailButton-iconbutton-iconbutton-icon-lefticon-IINA" > </i> <span class="button-text">IINA</ span > </div> < /按钮>     
            <按钮 id = "embyNPlayer" type = "button" class = "detailButton emby-button emby-button-backdropfilter raise-backdropfilterDetailButton-primary" title = "NPlayer" > < div class = "detailButton-content" > < i类= "md-icondetailButton-iconbutton-iconbutton-icon-lefticon-NPlayer" > </i> <span class="button-text">NPlayer</ span > </div> < /按钮>     
            <按钮 id = "embyMX" type = "button" class = "detailButton emby-button emby-button-backdropfilter raise-backdropfilterDetailButton-primary" title = "MXPlayer" > < div class = "detailButton-content" > < i类= "md-icondetailButton-iconbutton-iconbutton-icon-lefticon-MXPlayer" > </i> <span class="button-text">MX</ span > </div> < /按钮>     
            <按钮 id = "embyInfuse" type = "button" class = "detailButton emby-button emby-button-backdropfilter raise-backdropfilterDetailButton-primary" title = "InfusePlayer" > < div class = "detailButton-content" > < i类= "md-icondetailButton-iconbutton-iconbutton-icon-lefticon-infuse" ></i> <spanclass="button-text">注入</span> </div> < /按钮>   
            < button id = "embyStellarPlayer" type = "button" class = "detailButton emby-button emby-button-backdropfilter raise-backdropfilterDetailButton-primary" title = "测量播放器" > < div class = "detailButton-content" > < i class = "md-icondetailButton-iconbutton-iconbutton-icon-lefticon-StellarPlayer" > </i> <span class="button-text"> 测量</ span > </div> < /按钮>     
            <按钮 id = "embyMPV" type = "button" class = "detailButton emby-button emby-button-backdropfilter raise-backdropfilterDetailButton-primary" title = "MPV" > < div class = "detailButton-content" > < i类= "md-icondetailButton-iconbutton-iconbutton-icon-lefticon-MPV" > </i> <span class="button-text">MPV</ span > </div> < /按钮>     
            < button id = "embyCopyUrl" type = "button" class = "detailButton emby-button emby-button-backdropfilter raise-backdropfilterDetailButton-primary" title = "复制串流地址" > < div class = "detailButton-content" > < i class = "md-icondetailButton-iconbutton-iconbutton-icon-lefticon-Copy" > </i> <spanclass="button-text">复制链接</ span > </div> < /按钮>     
            </div> ` _
        主要细节按钮。insertAdjacentHTML ('afterend' ,buttonhtml )
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) #embyPot" ). onclick = embyPot ;
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) #embyIINA" ). onclick = embyIINA ;
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) #embyNPlayer" ). onclick = embyNPlayer ;
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) #embyMX" ). onclick = embyMX ;
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) #embyCopyUrl" ). onclick = embyCopyUrl ;
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) #embyVlc" ). onclick = embyVlc ;
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) #embyInfuse" ). onclick = embyInfuse ;
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) #embyStellarPlayer" ). onclick = embyStellarPlayer ;
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) #embyMPV" ). onclick = embyMPV ;
        //添加图标
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) .icon-PotPlayer" ). 风格。cssText += '背景: url(https://fastly.jsdelivr.net/gh/ bpking1/embyExternalUrl@0.0.5/embyWebAddExternalUrl/icons/icon-PotPlayer.webp)无重复;背景大小:100% 100%;字体尺寸:1.4em' ; 
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) .icon-IINA" ). 风格。cssText += '背景: url(https://fastly.jsdelivr.net/gh/ bpking1/embyExternalUrl@0.0.5/embyWebAddExternalUrl/icons/icon-IINA.webp)无重复;大小:100% 100%;字体尺寸:1.4em' ; 
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) .icon-MXPlayer" ). 风格。cssText += '背景: url(https://fastly.jsdelivr.net/gh/ bpking1/embyExternalUrl@0.0.5/embyWebAddExternalUrl/icons/icon-MXPlayer.webp)无重复;大小:100% 100%;字体尺寸:1.4em' ; 
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) .icon-infuse" ). 风格。cssText += '背景: url(https://fastly.jsdelivr.net/gh/ bpking1/embyExternalUrl@0.0.5/embyWebAddExternalUrl/icons/icon-infuse.webp)无背景重复;大小:100% 100%;字体尺寸:1.4em' ; 
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) .icon-VLC" ). 风格。cssText += '背景: url(https://fastly.jsdelivr.net/gh/ bpking1/embyExternalUrl@0.0.5/embyWebAddExternalUrl/icons/icon-VLC.webp)无重复;大小:100% 100%;字体尺寸:1.3em' ; 
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) .icon-NPlayer" ). 风格。cssText += '背景: url(https://fastly.jsdelivr.net/gh/ bpking1/embyExternalUrl@0.0.5/embyWebAddExternalUrl/icons/icon-NPlayer.webp)无重复;大小:100% 100%;字体尺寸:1.3em' ; 
        文档. querySelector ( "div[is='emby-scroller']:not(.hide) .icon-StellarPlayer" ). 风格。cssText += '背景: url(https://fastly.jsdelivr.net/gh/ bpking1/embyExternalUrl@0.0.5/embyWebAddExternalUrl/icons/icon-StellarPlayer.webp)无重复;背景大小:100% 100%;字体尺寸:1.4em' ; 
        文档. querySelector ( "div[is='emby-scroller']:not(.hide).icon-MPV" ). 风格。cssText += '背景:url(https://fastly.jsdelivr.net/gh/ bpking1/embyExternalUrl@0.0.5/embyWebAddExternalUrl/icons/icon-MPV.webp)无重复;背景大小:100% 100%;字体尺寸:1.4em' ; 
        文档. querySelector ( "div[is='emby-scroller']:not(.hide).icon-Copy" ). 风格。cssText += '背景:url(https://fastly.jsdelivr.net/gh/ bpking1/embyExternalUrl@0.0.5/embyWebAddExternalUrl/icons/icon-Copy.webp)无重复;背景大小:100% 100%;字体尺寸:1.4em' ; 
    }
    函数showFlag () { 
        让mainDetailButtons =文档. querySelector ( "div[is='emby-scroller']:not(.hide) .mainDetailButtons" );
        if (! mainDetailButtons ) {  
            返回假;
        }
        让videoElement =文档. querySelector ( "div[is='emby-scroller']:not(.hide) .selectVideoContainer" );
        if ( videoElement && videoElement . classList . contains ( "隐藏" )) {  
            返回假;
        }
        让audioElement =文档. querySelector ( "div[is='emby-scroller']:not(.hide) .selectAudioContainer" );
        return !( audioElement && audioElement . classList . contains ( "隐藏" )); 
    }
    异步函数getItemInfo () { 
        让userId = ApiClient 。_serverInfo 。用户ID ; 
        让itemId = /\?id=(\d*)/ 。exec (窗口.位置.哈希)[ 1 ]; 
        让响应=等待ApiClient 。getItem (用户 ID ,项目 ID );
        //继续播放当前的下集
        if ( response.Type == "系列" ) {    
            让seriesNextUpItems =等待ApiClient 。getNextUpEpisodes ({ SeriesId : itemId , UserId : userId });  
            控制台。log ( “ nextUpItemId :” + seriesNextUpItems.Items [ 0 ] .Id ) ; 
            返回等待ApiClient 。getItem ( userId , seriesNextUpItems.Items [ 0 ] .Id ) ; _ 
        }
        //播放当前季季的第一集
        if ( response.Type == "季节" ) {    
            让seasonItems =等待ApiClient 。getItems ( userId , { parentId : itemId });  
            控制台。log ( " seasonItemId:" + seasonItems.Items [ 0 ] .Id ) ; 
            返回等待ApiClient 。getItem ( userId , seasonItems . Items [ 0 ] . Id ); 
        }
        //播放当前集或电影
        控制台。日志(“项目ID:” +项目ID ); 
        返回响应;
    }
    函数getSeek (位置) { 
        让价格波动=位置* 10000 ;    
        让部分= []  
            ,小时=到达/ 36e9 ;     
        (小时= Math . Floor (小时)) && parts . 推(小时);   
        让=分钟(到达-= 36e9 *小时) / 6e8 ;        
        入口线-= 6e8 * (分钟= Math . Floor (分钟)),      
            分钟< 10 &&小时&& (分钟= "0" +分钟),          
            部分.推(分钟);
        令秒=实际值/ 1e7 ;    
        return (秒= Math.floor (秒)) < 10 && (秒= " 0" +秒) ,           
            部分.推(秒),
            部分。加入(“:” )
    }
    函数getSubPath ( mediaSource ) { 
        让selectSubtitles =文档. querySelector ( "div[is='emby-scroller']:not(.hide) select.selectSubtitles" );
        让subTitlePath = '' ; 
        //返回选中的外挂字幕
        if ( selectSubtitles && selectSubtitles . value > 0 ) {   
            让SubIndex = mediaSource 。媒体流。findIndex ( m => m . Index == selectSubtitles . value && m . IsExternal );   
            if (子索引> - 1 ) {    
                让subtitleCodec = mediaSource 。媒体流[子索引]。编解码器;
                subTitlePath = `/ $ {媒体源. Id }/字幕/ $ { selectSubtitles . 值}/流。$ {字幕编解码器}`; 
            }
        }
        另外{ 
            //默认尝试返回第一个外挂中文字幕
            让chiSubIndex = mediaSource 。媒体流。findIndex ( m => m . Language == "chi" && m . IsExternal );   
            if ( chiSubIndex > - 1 ) {   
                让subtitleCodec = mediaSource 。媒体流[ chiSubIndex ]。编解码器;
                subTitlePath = `/ $ {媒体源. Id }/字幕/ $ { chiSubIndex }/流. $ {字幕编解码器}`; 
            }另外{  
                //尝试返回第一个外挂字幕
                让externalSubIndex = mediaSource 。媒体流。findIndex ( m = > m .IsExternal );
                if (外部子索引> - 1 ) {   
                    让subtitleCodec = mediaSource 。媒体流[ externalSubIndex ]。编解码器;
                    subTitlePath = `/ $ {媒体源. Id }/字幕/ $ { externalSubIndex }/流。$ {字幕编解码器}`; 
                }
            }
        }
        返回子标题路径;
    }
    异步函数getEmbyMediaInfo () { 
        让itemInfo =等待getItemInfo (); 
        让mediaSourceId = itemInfo 。媒体源[ 0 ]。身份证号;
        让selectSource =文档. querySelector ( "div[is='emby-scroller']:not(.hide) select.selectSource" );
        if ( selectSource && selectSource . value . length > 0 ) {   
            媒体源 ID =选择源。值;
        }
        //let selectAudio = document.querySelector("div[is='emby-scroller']:not(.hide) select.selectAudio");
        让mediaSource = itemInfo 。媒体源。find ( m => m .Id == mediaSourceId ) ; 
        让域= ` $ { ApiClient . _serverAddress }/ emby / videos / $ { itemInfo . ID }`;  
        让subPath = getSubPath ( mediaSource );
        让subUrl = subPath 。长度> 0 ?  `${domain}${subPath}?api_key=${ApiClient.accessToken()}` : '';
        让streamUrl = ` $ { domain }/ stream 。$ {媒体源. 容器}?api_key = $ { ApiClient . accessToken ()}& Static = true & MediaSourceId = $ { mediaSourceId }`; 
        让位置= parseInt ( itemInfo . UserData . PlaybackPositionTicks / 10000 );   
        让意图=等待getIntent (媒体源,位置);
        控制台。log ( streamUrl , subUrl ,解释); 
        返回{ 
            流网址:流网址, 
            子网址:子网址, 
            意图:意图,
        }
    }
    异步函数getIntent ( mediaSource ,位置) {  
        让标题=媒体源。路径。分割('/' )。弹出();
        让externalSubs = mediaSource 。媒体流。过滤器(m => m 。IsExternal == true );  
        让subs = '' ;  //要求是android.net.uri[] ?
        让subs_name = '' ; 
        让subs_filename = '' ; 
        让subs_enable = '' ; 
        如果(外部子){
            订阅者名称=外部订阅者. 地图(s => s 。DisplayTitle );
            subs_文件名= externalSubs 。map ( s => s . Path . split ( '/' ). pop ());
        }
        返回{ 
            标题:标题, 
            位置:位置, 
            非常:非常,
            子名称:子名称,
            子文件名:子文件名,
            子启用:子启用
        };
    }
    异步函数embyPot () { 
        让mediaInfo =等待getEmbyMediaInfo (); 
        让意图= mediaInfo 。意图;
        让poturl = ` potplayer : //${encodeURI(mediaInfo.streamUrl)} /sub=${encodeURI(mediaInfo.subUrl)} /current /title="${intent.title}" /seek=${getSeek(intent) ) .位置)}`; 
        控制台。日志(poturl );
        窗口。打开(poturl ,“_blank” ); 
    }
    //https://wiki.videolan.org/Android_Player_Intents/
    异步函数embyVlc () { 
        让mediaInfo =等待getEmbyMediaInfo (); 
        让意图= mediaInfo 。意图;
        //android字幕:https://code.videolan.org/videolan/vlc-android/-/issues/1903
        letvlcUrl = `意图:$ { encodeURI ( mediaInfo .streamUrl )}# Intent ; 包=组织. 视频网络。可视化控制器;类型=视频/*;S.subtitles_location=${encodeURI(mediaInfo.subUrl)};S.title= ${encodeURI(intent.title)};i.position=${intent.position};end`; 
        if (getOS() == "windows") {
            // 桌面端需要额外设置,参考这个项目:https://github.com/stefansundin/vlc-protocol
            vlcUrl = `vlc://${encodeURI(mediaInfo.streamUrl)}`;
        }
        if (getOS() == 'ios') {
            //https://code.videolan.org/videolan/vlc-ios/-/commit/55e27ed69e2fce7d87c47c9342f8889fda356aa9
            vlcUrl = `vlc-x-callback://x-callback-url/stream?url=${encodeURIComponent(mediaInfo.streamUrl)}&sub=${encodeURIComponent(mediaInfo.subUrl)}`;
        }
        控制台.log(vlcUrl);
        window.open(vlcUrl, "_blank");
    }
    //https://github.com/iina/iina/issues/1991
    异步函数 embyIINA() {
        让 mediaInfo = 等待 getEmbyMediaInfo();
        让 iinaUrl = `iina://weblink?url=${encodeURIComponent(mediaInfo.streamUrl)}&new_window=1`;
        console.log(`iinaUrl= ${iinaUrl}`);
        window.open(iinaUrl, "_blank");
    }
    //https://sites.google.com/site/mxvpen/api
    异步函数 embyMX() {
        让 mediaInfo = 等待 getEmbyMediaInfo();
        让意图= mediaInfo.intent;
        //mxPlayer免费
        让 mxUrl = `intent:${encodeURI(mediaInfo.streamUrl)}#Intent;package=com.mxtech.videoplayer.ad;S.title=${encodeURI(intent.title)};i.position=${intent.位置};结束`;
        //mxPlayer专业版
        //let mxUrl = `intent:${encodeURI(mediaInfo.streamUrl)}#Intent;package=com.mxtech.videoplayer.pro;S.title=${encodeURI(intent.title)};i.position=${目的.位置};结束`;
        控制台.log(mxUrl);
        window.open(mxUrl, "_blank");
    }
    异步函数 embyNPlayer() {
        让 mediaInfo = 等待 getEmbyMediaInfo();
        让 nUrl = getOS() == 'macOS' ?`nplayer-mac://weblink?url=${encodeURIComponent(mediaInfo.streamUrl)}&new_window=1` : `nplayer-${encodeURI(mediaInfo.streamUrl)}`;
        控制台.log(nUrl);
        window.open(nUrl, "_blank");
    }
    //注入
    判断函数 embyInfuse() {
        让 mediaInfo = 等待 getEmbyMediaInfo();
        让 infuseUrl = `infuse://x-callback-url/play?url=${encodeURIComponent(mediaInfo.streamUrl)}`;
        console.log(`infuseUrl= ${infuseUrl}`);
        window.open(infuseUrl, "_blank");
    }
    // 概念播放器
    异步函数 embyStellarPlayer() {
        让 mediaInfo = 等待 getEmbyMediaInfo();
        让 stellarPlayerUrl = `stellar://play/${encodeURI(mediaInfo.streamUrl)}`;
        console.log(`stellarPlayerUrl= ${stellarPlayerUrl}`);
        window.open(stellarPlayerUrl, "_blank");
    }
    //MPV
    异步函数 embyMPV() {
        让 mediaInfo = 等待 getEmbyMediaInfo();
        //桌面端需要额外设置,使用本项目:https://github.com/akiirui/mpv-handler
        让streamUrl64 = btoa(mediaInfo.streamUrl).replace(/\//g, "_").replace(/\+/g, "-").replace(/\=/g, "");
        让 MPVUrl = `mpv://play/${streamUrl64}`;
        if (mediaInfo.subUrl.length > 0) {
            让 subUrl64 = btoa(mediaInfo.subUrl).replace(/\//g, "_").replace(/\+/g, "-").replace(/\=/g, "");
            MPVUrl = `mpv://play/${streamUrl64}/?subfile=${subUrl64}`;
        }
        if (getOS() == "ios" || getOS() == "android") {
            MPVUrl = `mpv://${encodeURI(mediaInfo.streamUrl)}`;
        }
        控制台.log(MPVUrl);
        window.open(MPVUrl, "_blank");
    }
    异步函数 embyCopyUrl() {
        让 mediaInfo = 等待 getEmbyMediaInfo();
        让 textarea = document.createElement('textarea');
        document.body.appendChild(textarea);
        textarea.style.position = '绝对';
        textarea.style.clip = '几何(0 0 0 0)';
        textarea.value = mediaInfo.streamUrl;
        文本区域.select();
        if (document.execCommand('复制', true)) {
            console.log(`copyUrl = ${mediaInfo.streamUrl}`);
            this.innerText = '复制成功';
        }
        //需要https
        // if (navigator.clipboard) {
        // navigator.clipboard.writeText(mediaInfo.streamUrl).then(() => {
        // console.log(`copyUrl = ${mediaInfo.streamUrl}`);
        // this.innerText = '复制成功';
        // })
        // }
    }
    函数 getOS() {
        让 u = navigator.userAgent
        if (!!u.match(/兼容/i) || u.match(/Windows/i)) {
            返回“窗口”
        } else if (!!u.match(/Macintosh/i) || u.match(/MacIntel/i)) {
            返回“macOS”
        } else if (!!u.match(/iphone/i) || u.match(/Ipad/i)) {
            返回“ios”
        } 否则 if (u.match(/android/i)) {
            返回“安卓”
        } else if (u.match(/Ubuntu/i)) {
            返回“Ubuntu”
        }另外{
            返回“其他”
        }
    }
    // dom监听变化
    document.addEventListener("viewbeforeshow", function (e) {
        if (e.detail.contextPath.startsWith("/item?id=") ) {
            const 突变 = new MutationObserver(function() {
                如果(showFlag()){
                    在里面();
                    削弱.disconnect();
                }
            })
            突变.observe(文档.body, {
                子列表:真实,
                字符数据:真实,
                子树:真,
            })
        }
    });
})();

3、embyDouban

emby 里增加:豆瓣 Bangumi bgm.tv 评分

油猴脚本链接:https://greasyfork.org/zh-CN/scripts/449894-embydouban

效果图下:

图片[3]-分享几个EMBY美化插件和外挂播放器-杨公子的博客
豆瓣插件

插件备份:

// ==UserScript==
// @name         embyDouban
// @name:zh-CN   embyDouban
// @name:en      embyDouban
// @namespace    https://github.com/kjtsune/embyToLocalPlayer/tree/main/embyDouban
// @version      0.1.9
// @description  emby 里展示: 豆瓣 Bangumi bgm.tv 评分 链接 (豆瓣评论可关)
// @description:zh-CN emby 里展示: 豆瓣 Bangumi bgm.tv 评分 链接 (豆瓣评论可关)
// @description:en  show douban Bangumi ratings in emby
// @author       Kjtsune
// @match        *://*/web/index.html*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=emby.media
// @grant        GM.xmlHttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @connect      api.bgm.tv
// @connect      api.douban.com
// @connect      movie.douban.com
// @license MIT
// ==/UserScript==
'use strict';
 
setModeSwitchMenu('enableDoubanComment', '豆瓣评论已经', '', '开启')
let enableDoubanComment = (localStorage.getItem('enableDoubanComment') === 'false') ? false : true;
 
let config = {
    logLevel: 2,
};
let logger = {
    error: function (...args) {
        if (config.logLevel >= 1) {
            console.log('%cerror', 'color: yellow; font-style: italic; background-color: blue;', ...args);
        }
    },
    info: function (...args) {
        if (config.logLevel >= 2) {
            console.log('%cinfo', 'color: yellow; font-style: italic; background-color: blue;', ...args);
        }
    },
    debug: function (...args) {
        if (config.logLevel >= 3) {
            console.log('%cdebug', 'color: yellow; font-style: italic; background-color: blue;', ...args);
        }
    },
}
 
async function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
 
function switchLocalStorage(key, defaultValue = 'false', trueValue = 'true', falseValue = 'false') {
    if (key in localStorage) {
        let value = (localStorage.getItem(key) === trueValue) ? falseValue : trueValue;
        localStorage.setItem(key, value);
    } else {
        localStorage.setItem(key, defaultValue)
    }
    console.log('switchLocalStorage ', key, ' to ', localStorage.getItem(key))
}
 
function setModeSwitchMenu(storageKey, menuStart = '', menuEnd = '', defaultValue = '关闭', trueValue = '开启', falseValue = '关闭') {
    let switchNameMap = { 'true': trueValue, 'false': falseValue, null: defaultValue };
    let menuId = GM_registerMenuCommand(menuStart + switchNameMap[localStorage.getItem(storageKey)] + menuEnd, clickMenu);
 
    function clickMenu() {
        GM_unregisterMenuCommand(menuId);
        switchLocalStorage(storageKey)
        menuId = GM_registerMenuCommand(menuStart + switchNameMap[localStorage.getItem(storageKey)] + menuEnd, clickMenu);
    }
 
}
 
function isHidden(el) {
    return (el.offsetParent === null);
}
 
function isEmpty(s) {
    return !s || s === 'N/A' || s === 'undefined';
}
 
function getVisibleElement(elList) {
    if (!elList) { return; }
    if (NodeList.prototype.isPrototypeOf(elList)) {
        for (let i = 0; i < elList.length; i++) {
            if (!isHidden(elList[i])) {
                return elList[i];
            }
        }
    } else {
        console.log('%c%s', 'color: orange;', 'return raw ', elList);
        return elList;
    }
 
}
 
function cleanLocalStorage() {
    let count = 0
    for (i in localStorage) {
        if (i.search(/^tt/) != -1 || i.search(/^\d{7}/) != -1) {
            console.log(i);
            count++;
            localStorage.removeItem(i);
        }
    }
    console.log(`remove done, count=${count}`)
}
 
function getURL_GM(url, data = null) {
    let method = (data) ? 'POST' : 'GET'
    return new Promise(resolve => GM.xmlHttpRequest({
        method: method,
        url: url,
        data: data,
        onload: function (response) {
            if (response.status >= 200 && response.status < 400) {
                resolve(response.responseText);
            } else {
                console.error(`Error ${method} ${url}:`, response.status, response.statusText, response.responseText);
                resolve();
            }
        },
        onerror: function (response) {
            console.error(`Error during GM.xmlHttpRequest to ${url}:`, response.statusText);
            resolve();
        }
    }));
}
 
async function getJSON_GM(url, data = null) {
    const res = await getURL_GM(url, data);
    if (res) {
        return JSON.parse(res);
    }
}
 
// async function getJSONP_GM(url) {
//     const data = await getURL_GM(url);
//     if (data) {
//         const end = data.lastIndexOf(')');
//         const [, json] = data.substring(0, end).split('(', 2);
//         return JSON.parse(json);
//     }
// }
 
async function getJSON(url) {
    try {
        const response = await fetch(url);
        if (response.status >= 200 && response.status < 400)
            return await response.json();
        console.error(`Error fetching ${url}:`, response.status, response.statusText, await response.text());
    }
    catch (e) {
        console.error(`Error fetching ${url}:`, e);
    }
}
 
async function getDoubanInfo(id) {
    // TODO: Remove this API completely if it doesn't come back.
    // const data = await getJSON_GM(`https://api.douban.com/v2/movie/imdb/${id}?apikey=123456`);
    // if (data) {
    //     if (isEmpty(data.alt))
    //         return;
    //     const url = data.alt.replace('/movie/', '/subject/') + '/';
    //     return { url, rating: data.rating };
    // }
    // Fallback to search.
    const search = await getJSON_GM(`https://movie.douban.com/j/subject_suggest?q=${id}`);
    if (search && search.length > 0 && search[0].id) {
        const abstract = await getJSON_GM(`https://movie.douban.com/j/subject_abstract?subject_id=${search[0].id}`);
        const average = abstract && abstract.subject && abstract.subject.rate ? abstract.subject.rate : '?';
        const comment = abstract && abstract.subject && abstract.subject.short_comment && abstract.subject.short_comment.content;
        return {
            id: search[0].id,
            comment: comment,
            // url: `https://movie.douban.com/subject/${search[0].id}/`,
            rating: { numRaters: '', max: 10, average },
            title: search[0].title,
            sub_title: search[0].sub_title,
        };
    }
}
 
function insertDoubanComment(doubanId, doubanComment) {
    console.log('%c%o%s', "color:orange;", 'start add comment ', doubanId)
    if (!enableDoubanComment) { return; }
    let commentKey = `${doubanId}Comment`;
    doubanComment = doubanComment || localStorage.getItem(commentKey);
    let el = getVisibleElement(document.querySelectorAll('div#doubanComment'));
    if (el || isEmpty(doubanComment)) {
        console.log('%c%s', 'color: orange', 'skip add doubanComment', el, doubanComment);
        return;
    }
    let embyComment = getVisibleElement(document.querySelectorAll('div.overview-text'));
    if (embyComment) {
        let parentNode = (ApiClient._serverVersion.startsWith('4.6')
        ) ? embyComment.parentNode : embyComment.parentNode.parentNode
        parentNode.insertAdjacentHTML('afterend', `<div id="doubanComment"><li>douban comment
        </li>${doubanComment}</li></div>`);
        console.log('%c%s', 'color: orange;', 'insert doubanComment ', doubanId, doubanComment);
    }
}
 
function insertDoubanScore(doubanId, rating) {
    rating = rating || localStorage.getItem(doubanId);
    console.log('%c%s', 'color: orange;', 'start ', doubanId, rating);
    let el = getVisibleElement(document.querySelectorAll('a#doubanScore'));
    if (el || !rating) {
        console.log('%c%s', 'color: orange', 'skip add score', el, rating);
        return;
    }
    let yearDiv = getVisibleElement(document.querySelectorAll('div[class="mediaInfoItem"]'));
    if (yearDiv) {
        let doubanIco = `<img style="width:16px;" src="data:image/x-icon;base64,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AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP+RwIn////////////4+/j/GnwK/xF3AP8RdwD/EXcA/xF3AP8afAr/+Pv4////////////kcCJ/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/9Xn0v///////////77auf8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP++2rn////////////V59L/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8rhhz/////////////////erNw/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/3qzcP////////////////8rhhz/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/26sZP////////////////81jCf/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/NYwn/////////////////26sZP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/vdm4////////////5PDi/y+IIP8viCD/L4gg/y+IIP8viCD/L4gg/y+IIP8viCD/5PDi////////////vdm4/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/0+bQ/////////////////////////////////////////////////////////////////////////////////////////////////9Pm0P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP/T5tD/////////////////////////////////////////////////////////////////////////////////////////////////0+bQ/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/9Pm0P///////////9vq2P+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP/b6tj////////////T5tD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/0+bQ////////////tdWw/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/7XVsP///////////9Pm0P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP/T5tD///////////+11bD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/tdWw////////////0+bQ/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/9Pm0P///////////7XVsP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP+11bD////////////T5tD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/0+bQ////////////tdWw/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/7XVsP///////////9Pm0P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP/T5tD////////////b6tj/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/iLuA/4i7gP+Iu4D/2+rY////////////0+bQ/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/9Pm0P/////////////////////////////////////////////////////////////////////////////////////////////////T5tD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/0+bQ/////////////////////////////////////////////////////////////////////////////////////////////////9Pm0P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/+Lu4P//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////4u7g/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/4u7g///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////i7uD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP/i7uD//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+Lu4P8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AOYRdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwDmEXcASRF3AOYRdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA/xF3AP8RdwD/EXcA5hF3AEkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==">`
        yearDiv.insertAdjacentHTML('beforebegin', `<div class="starRatingContainer mediaInfoItem">${doubanIco}<a id="doubanScore">${rating}</a></div>`);
        console.log('%c%s', 'color: orange;', 'insert score ', doubanId, rating);
    }
    console.log('%c%s', 'color: orange;', 'finish ', doubanId, rating);
}
 
async function insertDoubanMain(linkZone) {
    if (isEmpty(linkZone)) { return; }
    let doubanButton = linkZone.querySelector('a[href*="douban.com"]');
    let imdbButton = linkZone.querySelector('a[href^="https://www.imdb"]');
    if (doubanButton || !imdbButton) { return; }
    let imdbId = imdbButton.href.match(/tt\d+/);
    if (imdbId in localStorage) {
        var doubanId = localStorage.getItem(imdbId);
    } else {
        await getDoubanInfo(imdbId).then(function (data) {
            if (!isEmpty(data)) {
                let doubanId = data.id;
                localStorage.setItem(imdbId, doubanId);
                if (data.rating && !isEmpty(data.rating.average)) {
                    insertDoubanScore(doubanId, data.rating.average);
                    localStorage.setItem(doubanId, data.rating.average);
                    localStorage.setItem(doubanId + 'Info', JSON.stringify(data));
                }
                if (enableDoubanComment) {
                    insertDoubanComment(doubanId, data.comment);
                    localStorage.setItem(doubanId + 'Comment', data.comment);
                }
            }
            console.log('%c%o%s', 'background:yellow;', data, ' result and send a requests')
        });
        var doubanId = localStorage.getItem(imdbId);
    }
    console.log('%c%o%s', "color:orange;", 'douban id ', doubanId, String(imdbId));
    if (!doubanId) {
        localStorage.setItem(imdbId, '');
        return;
    }
    let doubanString = `<a is="emby-linkbutton" class="raised item-tag-button nobackdropfilter emby-button" href="https://movie.douban.com/subject/${doubanId}/" target="_blank"><i class="md-icon button-icon button-icon-left">link</i>Douban</a>`;
    imdbButton.insertAdjacentHTML('beforebegin', doubanString);
    insertDoubanScore(doubanId);
    insertDoubanComment(doubanId);
}
 
function insertBangumiByPath(idNode) {
    let el = getVisibleElement(document.querySelectorAll('a#bangumibutton'));
    if (el) { return; }
    let id = idNode.textContent.match(/(?<=bgm\=)\d+/);
    let bgmHtml = `<a id="bangumibutton" is="emby-linkbutton" class="raised item-tag-button nobackdropfilter emby-button" href="https://bgm.tv/subject/${id}" target="_blank"><i class="md-icon button-icon button-icon-left">link</i>Bangumi</a>`
    idNode.insertAdjacentHTML('beforebegin', bgmHtml);
}
 
function insertBangumiScore(bgmObj, infoTable, linkZone) {
    if (!bgmObj) return;
    let bgmRate = infoTable.querySelector('a#bgmScore');
    if (bgmRate) return;
 
    let yearDiv = infoTable.querySelector('div[class="mediaInfoItem"]');
    if (yearDiv && bgmObj.trust) {
        let bgmIco = `<img style="width:16px;" src="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALJu+f//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsm75ELJu+cCybvn/sm75/7Ju+f+ybvn//////7Ju+f+ybvn/sm75/7Ju+f+ybvn/sm75/7Ju+f+ybvnAsm75ELJu+cCybvn/sm75/7Ju+f+ybvn/sm75////////////sm75/7Ju+f+ybvn/sm75/7Ju+f+ybvn/sm75/7Ju+cCwaPn/sGj5/9iz/P///////////////////////////////////////////////////////////9iz/P+waPn/rF/6/6xf+v//////////////////////////////////////////////////////////////////////rF/6/6lW+/+pVvv/////////////////////////////////zXn2/////////////////////////////////6lW+/+lTfz/pU38///////Nefb/zXn2/8159v//////zXn2///////Nefb//////8159v/Nefb/zXn2//////+lTfz/okT8/6JE/P//////////////////////2bb8/8159v/Nefb/zXn2/9m2/P//////////////////////okT8/546/f+eOv3//////8159v/Nefb/zXn2////////////////////////////zXn2/8159v/Nefb//////546/f+bMf7/mzH+//////////////////////////////////////////////////////////////////////+bMf7/lyj+wJco/v/Mk/7////////////////////////////////////////////////////////////Mk///lyj+wJQf/xCUH//AlB///5Qf//+UH///lB///5Qf//+aP///mj///5o///+UH///lB///5Qf//+UH///lB//wJQf/xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/5o////Nefb/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAM159v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzXn2/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADNefb/AAAAAAAAAAAAAAAA+f8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/j8AAP3fAAD77wAA9/cAAA==">`
        yearDiv.insertAdjacentHTML('beforebegin', `<div class="starRatingContainer mediaInfoItem">${bgmIco} <a id="bgmScore">${bgmObj.score}</a></div>`);
        console.log('%c%s', 'color: orange;', 'insert bgmScore ', bgmObj.score);
    }
    let imdbButton = linkZone.querySelector('a[href^="https://www.imdb"]');
    let bgmButton = linkZone.querySelector('a[href^="https://bgm.tv"]');
    if (bgmButton) return;
    let bgmString = `<a is="emby-linkbutton" class="raised item-tag-button nobackdropfilter emby-button" href="https://bgm.tv/subject/${bgmObj.id}" target="_blank"><i class="md-icon button-icon button-icon-left">link</i>Bangumi</a>`;
    imdbButton.insertAdjacentHTML('beforebegin', bgmString);
}
 
function textSimilarity(text1, text2) {
    const len1 = text1.length;
    const len2 = text2.length;
    let count = 0;
    for (let i = 0; i < len1; i++) {
        if (text2.indexOf(text1[i]) != -1) {
            count++;
        }
    }
    const similarity = count / Math.min(len1, len2);
    return similarity;
}
 
function checkIsExpire(key, expireDay = 1) {
    let timestamp = localStorage.getItem(key);
    if (!timestamp) return true;
    let expireMs = expireDay * 864E5;
    if (Number(timestamp) + expireMs < Date.now()) {
        localStorage.removeItem(key)
        logger.info(key, "IsExpire, old", timestamp, "now", Date.now());
        return true;
    } else {
        return false;
    }
 
}
 
async function insertBangumiMain(infoTable, linkZone) {
    if (!infoTable || !linkZone) return;
    let mediaInfoItems = infoTable.querySelectorAll('div[class="mediaInfoItem"] > a');
    let isAnime = 0;
    mediaInfoItems.forEach(tagItem => {
        if (tagItem.textContent && tagItem.textContent.search(/动画|Anim/) != -1) { isAnime++ }
    });
    if (isAnime == 0) return;
 
    let bgmRate = infoTable.querySelector('a#bgmScore');
    if (bgmRate) return;
 
    let imdbButton = linkZone.querySelector('a[href^="https://www.imdb"]');
    if (!imdbButton) return;
    let imdbId = imdbButton.href.match(/tt\d+/);
 
    let imdbExpireKey = imdbId + 'expire'
    let year = infoTable.querySelector('div[class="mediaInfoItem"]').textContent.match(/^\d{4}/);
    let expireDay = (Number(year) < new Date().getFullYear() && new Date().getMonth() + 1 != 1) ? 30 : 3
    let needUpdate = false;
    if (imdbExpireKey in localStorage) {
        if (checkIsExpire(imdbExpireKey, expireDay)) {
            needUpdate = true;
            localStorage.setItem(imdbExpireKey, JSON.stringify(Date.now()));
        }
    } else {
        localStorage.setItem(imdbExpireKey, JSON.stringify(Date.now()));
    }
 
 
    let imdbBgmKey = imdbId + 'bgm';
    let bgmObj = localStorage.getItem(imdbBgmKey);
    if (bgmObj && !needUpdate) {
        bgmObj = JSON.parse(bgmObj)
        insertBangumiScore(bgmObj, infoTable, linkZone);
        return;
    }
 
    let imdbNotBgmKey = imdbId + 'NotBgm';
    if (!checkIsExpire(imdbNotBgmKey)) {
        return;
    }
    let userId = ApiClient._serverInfo.UserId;
    let itemId = /\?id=(\d*)/.exec(window.location.hash)[1];
    let itemInfo = await ApiClient.getItems(userId, {
        'Ids': itemId,
        'Fields': 'OriginalTitle,PremiereDate'
    })
    itemInfo = itemInfo['Items'][0]
    let title = itemInfo.Name;
    let originalTitle = itemInfo.OriginalTitle;
 
    let splitRe = /[/\/]/;
    if (splitRe.test(originalTitle)) { //纸片人
        logger.info(originalTitle);
        let zprTitle = originalTitle.split(splitRe);
        for (let _i in zprTitle) {
            let _t = zprTitle[_i];
            if (/[あいうえおかきくけこさしすせそたちつてとなにぬねのひふへほまみむめもやゆよらりるれろわをんー]/.test(_t)) {
                originalTitle = _t;
                break
            } else {
                originalTitle = zprTitle[0];
            }
        }
    }
 
    let premiereDate = new Date(itemInfo.PremiereDate);
    premiereDate.setDate(premiereDate.getDate() - 2);
    let startDate = premiereDate.toISOString().slice(0, 10);
    premiereDate.setDate(premiereDate.getDate() + 4);
    let endDate = premiereDate.toISOString().slice(0, 10);
 
    logger.info('bgm ->', originalTitle, startDate, endDate);
    let bgmInfo = await getJSON_GM(`https://api.bgm.tv/v0/search/subjects?limit=10`, JSON.stringify({
        "keyword": originalTitle,
        // "keyword": 'titletitletitletitletitletitletitle',
        "filter": {
            "type": [
                2
            ],
            "air_date": [
                `>=${startDate}`,
                `<${endDate}`
            ],
            "nsfw": true
        }
    }))
    logger.info('bgmInfo', bgmInfo['data'])
    bgmInfo = (bgmInfo['data']) ? bgmInfo['data'][0] : null;
    if (!bgmInfo) {
        localStorage.setItem(imdbNotBgmKey, JSON.stringify(Date.now()));
        logger.error('getJSON_GM not bgmInfo return');
        return;
    };
 
    let trust = false;
    if (textSimilarity(originalTitle, bgmInfo['name']) < 0.4 && (textSimilarity(title, bgmInfo['name_cn'])) < 0.4
        && (textSimilarity(title, bgmInfo['name'])) < 0.4) {
        localStorage.setItem(imdbNotBgmKey, JSON.stringify(Date.now()));
        logger.error('not bgmObj and title not Similarity, skip');
    } else {
        trust = true
    }
    logger.info(bgmInfo)
    bgmObj = {
        id: bgmInfo['id'],
        score: bgmInfo['score'],
        name: bgmInfo['name'],
        name_cn: bgmInfo['name_cn'],
        trust: trust,
    }
    localStorage.setItem(imdbBgmKey, JSON.stringify(bgmObj));
    insertBangumiScore(bgmObj, infoTable, linkZone);
}
 
function cleanDoubanError() {
    let expireKey = 'doubanErrorExpireKey';
    let needClean = false;
    if (expireKey in localStorage) {
        if (checkIsExpire(expireKey, 3)) {
            needClean = true
            localStorage.setItem(expireKey, JSON.stringify(Date.now()));
        }
    } else {
        localStorage.setItem(expireKey, JSON.stringify(Date.now()));
    }
    if (!needClean) return;
 
    let count = 0
    for (let i in localStorage) {
        if (i.search(/^tt\d+/) != -1 && localStorage.getItem(i) === '') {
            console.log(i);
            count++;
            localStorage.removeItem(i);
        }
    }
    logger.info(`cleanDoubanError done, count=${count}`);
}
 
var runLimit = 50;
 
async function main() {
    let linkZone = getVisibleElement(document.querySelectorAll('div[class="verticalSection linksSection verticalSection-extrabottompadding"]'));
    let infoTable = getVisibleElement(document.querySelectorAll('div[class="flex-grow detailTextContainer details-largefont"]'));
    if (infoTable && linkZone) {
        if (!infoTable.querySelector('h3.itemName-secondary')) { // not eps page
            insertDoubanMain(linkZone);
            await insertBangumiMain(infoTable, linkZone)
        } else {
            let bgmIdNode = document.evaluate('//div[contains(text(), "[bgm=")]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            if (bgmIdNode) { insertBangumiByPath(bgmIdNode) };
        }
    }
    if (runLimit > 50) {
        cleanDoubanError();
        runLimit = 0
    }
}
 
(function loop() {
    setTimeout(async function () {
        // if (runLimit > 5) return;
        await main();
        loop();
        runLimit += 1
    }, 700);
})();

4、Emby.Plugin.TelegramNotification

Emby 服务器插件,用于向 Telegram 机器人发送通知。

github项目地址:https://github.com/bjoerns1983/Emby.Plugin.TelegramNotification

怎么用的话作者github的readme里有 后台长这样子,装好后填写bot token和user id然后重启下就行了

效果如下:

图片[4]-分享几个EMBY美化插件和外挂播放器-杨公子的博客
启用插件
图片[5]-分享几个EMBY美化插件和外挂播放器-杨公子的博客
tg的bot的话通知长这样,自己库里更新了,很快就能通知了

5、Emby.MeiamSub.DevTool

emby的插件,支持 迅雷看看  射手影音 字幕下载 Hash匹配

github项目地址:https://github.com/91270/MeiamSubtitles

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片