Bangumi 用户悬浮面板

https://bgm.tv/dev/app/953 的修改版,鼠标悬浮在用户链接上方时出现悬浮框

当前为 2023-05-04 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Bangumi User Hover Panel
// @name:zh-CN   Bangumi 用户悬浮面板
// @namespace    https://github.com/CryoVit/jioben/tree/master/bangumi/
// @version      0.5.0
// @description  fork of https://bgm.tv/dev/app/953. Display a hover panel when mouse hover on user link.
// @description:zh-CN  https://bgm.tv/dev/app/953 的修改版,鼠标悬浮在用户链接上方时出现悬浮框
// @author       cureDovahkiin + CryoVit
// @match        https://bangumi.tv/*
// @match        https://bgm.tv/*
// @match        https://chii.in/*
// @icon         https://bgm.tv/img/favicon.ico
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    let locker = false
    $('[href*="/user/"],[href*="/user/"].l,[href*="/user/"].avatar,#pm_sidebar a[onclick^="AddMSG"]').each(function () {
        let timer = null
        $(this).hover(function () {
            timer = setTimeout(() => {
                if (locker) return false
                if (this.text == "查看好友列表" || $(this).find('.avatarSize75').length > 0) return false
                locker = true
                const layout = document.createElement('div')
                let timer = null
                $(layout).addClass('user-hover')
                if ($(this).hasClass('avatar')) {
                    $(layout).addClass('fix-avatar-hover')
                }
                layout.innerHTML = `<div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>`
                const userData = {}
                if (this.onclick) {
                    userData.id = this.onclick.toString().split("'")[1]
                } else {
                    let urlSplit = /.*\/user\/([^\/]*)\/?(.*)/.exec(this.href)
                    if (urlSplit[2]) return
                    userData.id = urlSplit[1]
                }
                userData.href = '/user/' + userData.id
                const req = {
                    req1: null,
                    req2: null
                }
                Promise.all([
                    new Promise((r, j) => {
                        req.req1 = $.ajax({
                            url: userData.href,
                            dataType: 'text',
                            success: e => {
                                userData.self = /<a class="avatar" href="([^"]*)">/.exec(e)[1].split('/').pop()
                                if (userData.self != userData.id) {
                                    userData.sinkuro = /mall class="hot">\/([^<]*)<\/small>/.exec(e)[1]
                                    userData.sinkuroritsu = /<span class="percent" style="width:([^"]*)">/.exec(e)[1]
                                    userData.addFriend = /<a href="([^"']*)" id="connectFrd" class="chiiBtn">/.exec(e)
                                    userData.addFriend = userData.addFriend ? userData.addFriend[1] : false
                                }
                                userData.joinDate = /Bangumi<\/span> <span class="tip">([^<]*)<\/span>/.exec(e)[1]
                                userData.lastEvent = /<small class="time">([^<]*)<\/small><\/li>/.exec(e)
                                userData.watch = Array.from(e.match(/<a href="\/anime\/list[^>=]*>([0-9]{1,4}[^<]*)/g) || [], el => />([0-9]{1,5}.*)/.exec(el)[1])
                                userData.watch = userData.watch.map(el => el.split('部'))
                                userData.stats = /<div class="gridStats">([\s\S]*)<\/div>/.exec(e)[1]
                                userData.stats = Array.from(userData.stats.match(/<div[^>]*>([\s\S]*?)<\/div>/g).slice(0, 6), el => /<div[^>]*>([\s\S]*?)<\/div>/.exec(el)[1])
                                userData.stats = userData.stats.map(el => Array.from(el.match(/<span[^>]*>([\s\S]*?)<\/span>/g), el => /<span[^>]*>([\s\S]*?)<\/span>/.exec(el)[1]))
                                // console.log(userData)
                                r()
                            },
                            error: () => {
                                j()
                            }
                        })
                    }),
                    new Promise((r, j) => {
                        req.req2 = $.ajax({
                            url: 'https://api.bgm.tv/user/' + userData.id,
                            dataType: 'json',
                            success: e => {
                                userData.name = e.nickname
                                userData.avatar = e.avatar.large.replace(/https?/, 'https')
                                userData.sign = e.sign
                                userData.url = e.url
                                userData.message = `https://bgm.tv/pm/compose/${e.id}.chii`
                                r()
                            },
                            error: () => {
                                j()
                            }
                        })
                    })
                ]).then(() => {
                    layout.innerHTML = `
                        <img class='avater' src="${userData.avatar}"/>
                        <div class='user-info'>
                            <p class='user-name'><a href="${userData.href}" target="_blank">${userData.name}</a></p>
                            <p class='user-joindate'>${userData.joinDate}</p><span class='user-id'>@${userData.id}</span>
                            <p class='user-sign'>${userData.sign}</p>
                        </div>
                        ${
                        userData.sinkuro ? `
                            <div class="shinkuro">
                            <div style="width:${userData.sinkuroritsu}" class="shinkuroritsu"></div>
                            <div class="shinkuro-text">
                                <span>${userData.sinkuro}</span> 
                                <span>同步率:${userData.sinkuroritsu}</span> 
                            </div>                                      
                            </div>
                            `: ''
                        }                
                        <div class='user-stats'>
                            ${(function () {
                                const watchStates = ['在看', '看过', '想看', '搁置', '抛弃'];
                                let idx = 0;
                                let html = '<div class="stats-odd">'
                                for (let i = 0; i < 5; i++) {
                                    if (idx >= userData.watch.length || userData.watch[idx][1] != watchStates[i]) {
                                        html += `<span>${watchStates[i]} <strong>0</strong></span>`
                                    } else {
                                        html += `<span>${watchStates[i]} <strong>${userData.watch[idx][0]}</strong></span>`
                                        idx++
                                    }
                                }
                                html += '</div><div class="stats-even">'
                                for (let i = 0; i < 6; i++) {
                                    if (i == 2) {
                                        continue
                                    }
                                    html += `<span>${userData.stats[i][1]} <strong>${userData.stats[i][0]}</strong></span>`
                                }
                                return html + '</div>'
                            })()}
                        </div>
                        <span class='user-lastevent'>Last @ ${userData.lastEvent ? userData.lastEvent[1] : ''}</span>
                        <a class = 'hover-panel-btn' href="${userData.message}" target="_blank">发送短信</a>
                        <span id="panel-friend">
                        ${ userData.addFriend ? `
                                <a class='hover-panel-btn' href="${userData.addFriend}" id='PanelconnectFrd' href="javascript:void(0)">添加好友</a>                    
                            `: `
                        ${ userData.id == userData.self ? '' : `<span class = 'my-friend' >我的好友</span>`}
                            `}
                        </span>
                        `

                    $(layout).addClass('dataready')
                    $('#PanelconnectFrd').click(function () {
                        $('#panel-friend').html(`<span class='my-friend'>正在添加...</span>`)
                        $("#robot").fadeIn(500)
                        $("#robot_balloon").html(AJAXtip['wait'] + AJAXtip['addingFrd'])
                        $.ajax({
                            type: "GET",
                            url: this + '&ajax=1',
                            success: function (html) {
                                $('#PanelconnectFrd').hide()
                                $('#panel-friend').html(`<span class = 'my-friend' >我的好友</span>`)
                                $("#robot_balloon").html(AJAXtip['addFrd'])
                                $("#robot").animate({
                                    opacity: 1
                                }, 1000).fadeOut(500)
                                localStorage.removeItem('bgmFriends')
                            },
                            error: function (html) {
                                $("#robot_balloon").html(AJAXtip['error'])
                                $("#robot").animate({
                                    opacity: 1
                                }, 1000).fadeOut(500)
                                $('#panel-friend').html(`<span class='my-friend-fail'>添加失败,稍后再试</span>`)
                            }
                        })
                        return false
                    })
                }).catch(() => {
                    layout.innerHTML = `
                        <p style='font-size:16px; margin:25px 30px'>
                        <img style="height:15px;width:16px" src='/img/smiles/tv/15.gif'/><br/>
                        请求失败,请稍后再试。<br/><br/>或者使用<a href='https://bgm.tv'>bgm.tv</a>域名,</p>`
                    $(layout).addClass('dataready')
                })
                function removeLayout () {
                    setTimeout(() => {
                        $(layout).remove()
                        locker = false
                        req.req1.abort()
                        req.req2.abort()
                    }, 200);
                }
                $(this).after(layout).mouseout(function () {
                    timer = setTimeout(() => {
                        removeLayout()
                    }, 300);
                })
                $(layout).hover(function () {
                    clearTimeout(timer)
                }, function () {
                    removeLayout()
                })
                return false
            }, 500)
        },
            function () {
                clearTimeout(timer)
            }
        )
    })

    const style = document.createElement("style");
    const heads = document.getElementsByTagName("head");
    style.setAttribute("type", "text/css");
    style.innerHTML = `
        :root {
            --bg-color: #fff;
            --text-color: #010101;
            --bg-pink: #fce9e9;
            --bg-sky: #c2e1fc;
            --box-shadow: #ddd;
            --text-gray: #666;
        }
        [data-theme='dark'] {
            --bg-color: #2d2e2f;
            --text-color: #f7f7f7;
            --bg-pink: #3c3c3c;
            --bg-sky: #3c3c3c;
            --box-shadow: #6e6e6e;
            --text-gray: #aaa;
        }
        .user-hover {
            position: absolute;
            width: 412px;
            min-height: 
            background: var(--bg-color);
            box-shadow: 0px 0px 4px 1px var(--box-shadow);
            transition: all .2s ease-in;
            transform: translate(0,6px);
            font-size: 12px;
            z-index:999;
            color: var(--text-color);
            line-height: 130%;
            border-radius: 5px;
            -webkit-border-radius: 5px;
            backdrop-filter: blur(10px) contrast(95%);
            -webkit-backdrop-filter: blur(10px) contrast(95%);
        }
        .fix-avatar-hover{
            transform: translate(45px,20px)
        }
        
        div.dataready {
            padding: 8px;
            font-weight: normal;
            text-align: left;
        }
        span.user-lastevent {
            margin-top: 3px;
            display: inline-block;
            vertical-align: top;
            color: var(--text-gray);
        }
        div.dataready img {
            height: 75px;
            width:75px;
            border-radius: 5px;
        }
        .user-info {
            display: inline-block;
            vertical-align: top;
            max-width: 250px;
            margin: 0 0 10px 10px;
        }
        .user-info .user-name {
            font-size: 20px;
            font-weight: bold;
        }
        .user-info .user-joindate {
            background-color: #f09199;
            display: inline-block;
            color: #f7f7f7;
            border-radius: 10px;
            padding: 0 10px;
            margin: 8px 4px 3px 0;
        }
        .user-info .user-id{
            font-size: 12px;
            font-weight:normal;
            color: var(--text-gray);
        }
        .user-info .user-sign {
            word-break: break-all;
            margin-top: 3px;
        }
        .user-stats {
            padding: 10px 0px 5px;
            margin-bottom: 10px;
        }
        .user-stats span {
            display: inline-block;
            padding: 4px;
            width: 19%;
            box-sizing: border-box;
            border-left: 4px solid #f09199;
            background-color: var(--bg-pink) !important;
            color: var(--text-color) !important;
            margin: 0 1% 1% 0;
        }
        .stats-even span {
            border-left: 4px solid #369cf8;
            background-color: var(--bg-sky) !important;
        }
        .shinkuro {
            width: 100%;
            height: 20px;
            background-color: var(--bg-sky) !important;
            line-height: 20px;
            border-radius: 10px;
        }
        .shinkuro-text {
            position: absolute;
            width: 100%;
            height: 20px;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }
        .shinkuro-text span {
            color: var(--text-color) !important;
        }
        .shinkuroritsu {
            height: 20px;
            float: left;
            border-radius: 10px;
            background: linear-gradient(to right, #9acdfb 0%,#4aa5f8 100%);
        }
        .shinkuro-text span:nth-of-type(1) {
            margin-left: 10px;
        }
        .shinkuro-text span:nth-of-type(2) {
            margin-right: 26px;
        }
        a.hover-panel-btn, span.my-friend, span.my-friend-fail {
            display: inline-block;
            float: right;
            margin-bottom: 8px;
            color: white;
            padding: 1px 8px;
            border-radius: 10px;
            margin-left:10px;
            transition: all .2s ease-in;
        }
        a.hover-panel-btn {
            background: #f09199;
            transition: all .2s ease-in;
        }
        span.my-friend {
            background: #6eb76e;
        }
        span.my-friend-fail {
            background: red;
        }

        .lds-roller {
            display: inline-block;
            position: relative;
            width: 64px;
            height: 64px;
            margin:10px 20px
        }
        .lds-roller div {
            animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
            transform-origin: 32px 32px;
        }
        .lds-roller div:after {
            content: " ";
            display: block;
            position: absolute;
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: #f09199;
            margin: -3px 0 0 -3px;
        }
        .lds-roller div:nth-child(1) {
            animation-delay: -0.036s;
        }
        .lds-roller div:nth-child(1):after {
            top: 50px;
            left: 50px;
        }
        .lds-roller div:nth-child(2) {
            animation-delay: -0.072s;
        }
        .lds-roller div:nth-child(2):after {
            top: 54px;
            left: 45px;
        }
        .lds-roller div:nth-child(3) {
            animation-delay: -0.108s;
        }
        .lds-roller div:nth-child(3):after {
            top: 57px;
            left: 39px;
        }
        .lds-roller div:nth-child(4) {
            animation-delay: -0.144s;
        }
        .lds-roller div:nth-child(4):after {
            top: 58px;
            left: 32px;
        }
        .lds-roller div:nth-child(5) {
            animation-delay: -0.18s;
        }
        .lds-roller div:nth-child(5):after {
            top: 57px;
            left: 25px;
        }
        .lds-roller div:nth-child(6) {
            animation-delay: -0.216s;
        }
        .lds-roller div:nth-child(6):after {
            top: 54px;
            left: 19px;
        }
        .lds-roller div:nth-child(7) {
            animation-delay: -0.252s;
        }
        .lds-roller div:nth-child(7):after {
            top: 50px;
            left: 14px;
        }
        .lds-roller div:nth-child(8) {
            animation-delay: -0.288s;
        }
        .lds-roller div:nth-child(8):after {
            top: 45px;
            left: 10px;
        }
        @keyframes lds-roller {
            0% {
                transform: rotate(0deg);
            }
            100% {
                transform: rotate(360deg);
            }
        }
        
        #comment_list div.sub_reply_collapse {
            opacity: 1;
        }
    `
    heads[0].append(style)
})();