Greasy Fork

BF1Tracker优化

去广告,自动获取玩家名, 地图名, 北京时间

// ==UserScript==
// @name         BF1Tracker优化
// @version      2025-04-15
// @description  去广告,自动获取玩家名, 地图名, 北京时间
// @author       bilibili22
// @match        https://battlefieldtracker.com/bf1/*
// @icon         https://trackercdn.com/static-files/trackergg/production/dist/client/assets/0r7m9y2i.png
// @grant        GM_xmlhttpRequest
// @connect      ea-api.2788.pro
// @namespace    https://greasyfork.org/users/1281680
// ==/UserScript==

(function () {
    'use strict'

    function removeAdContainers() {
        document.querySelector('.primisslate')?.remove()
        document.querySelector('.ad-container')?.remove()
        document.querySelector('.bordered-davert')?.remove()
        document.querySelector('[id^="google_ads_iframe_"]')?.remove()
    }

    removeAdContainers()

    const observer = new MutationObserver(mutations => {
        for (const mutation of mutations) {
            if (mutation.type === 'childList') {
                removeAdContainers();
            }
        }
    });

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


    const mapPrettyName = { MP_Amiens: '亚眠', MP_ItalianCoast: '帝国边境', MP_ShovelTown: '攻占托尔', MP_MountainFort: '格拉巴山', MP_Graveyard: '决裂', MP_FaoFortress: '法欧堡', MP_Chateau: '流血宴厅', MP_Scar: '圣康坦的伤痕', MP_Suez: '苏伊士', MP_Desert: '西奈沙漠', MP_Forest: '阿尔贡森林', MP_Giant: '庞然暗影', MP_Verdun: '凡尔登高地', MP_Trench: '尼维尔之夜', MP_Underworld: '法乌克斯要塞', MP_Fields: '苏瓦松', MP_Valley: '加利西亚', MP_Bridge: '勃鲁西洛夫关口', MP_Tsaritsyn: '察里津', MP_Ravines: '武普库夫山口', MP_Volga: '窝瓦河', MP_Islands: '阿尔比恩', MP_Beachhead: '海丽丝岬', MP_Harbor: '泽布吕赫', MP_Ridge: '阿奇巴巴', MP_River: '卡波雷托', MP_Hell: '帕斯尚尔', MP_Offensive: '索姆河', MP_Naval: '黑尔戈兰湾', MP_Blitz: '伦敦:夜袭', MP_London: '伦敦:灾祸', MP_Alps: '剃刀边缘' }
    const modePrettyName = { BreakthroughLarge: '行动模式', Breakthrough: '闪击行动', Conquest: '征服', TugOfWar: '前线', TeamDeathMatch: '团队死斗', Possession: '战争信鸽', Domination: '抢攻', Rush: '突袭', ZoneControl: '空降补给', AirAssault: '空中突击' }

    const xhrOpen = XMLHttpRequest.prototype.open
    XMLHttpRequest.prototype.open = function (method, url) {
        const xhr = this

        function block() {
            xhr.send = () => {}
            return xhrOpen.apply(xhr, arguments)
        }

        if (url.startsWith('https://api.tracker.gg/api/v2/bf1/standard/profile') && url.endsWith('?forceCollect=true')) return block()

        function hook(callback) {
            const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'responseText').get
            Object.defineProperty(xhr, 'responseText', {
                get: () => {
                    let resp = getter.call(xhr)
                    if (xhr.status !== 200) return resp
                    resp = callback(resp)
                    return resp
                },
            })
        }

        if (url.startsWith('https://api.tracker.gg/api/v2/bf1/standard/matches/origin/')
            || url.startsWith('https://api.tracker.gg/api/v2/bf1/standard/matches/psn/')
            || url.startsWith('https://api.tracker.gg/api/v2/bf1/standard/matches/xbl/')
        ) {
            hook((resp) => {
                resp = JSON.parse(resp)
                resp.data.matches.forEach(match => {
                    match.metadata.mapName = mapPrettyName[match.attributes.mapKey]
                    match.metadata.gamemodeName = modePrettyName[match.attributes.gamemodeKey.slice(0, -1)]
                })
                resp = JSON.stringify(resp)
                return resp
            })
        } else if (url.startsWith('https://api.tracker.gg/api/v2/bf1/standard/matches/')) {
            hook((resp) => {
                resp = JSON.parse(resp)
                resp.data.metadata.mapName = mapPrettyName[resp.data.attributes.mapKey]
                resp.data.metadata.gamemodeName = modePrettyName[resp.data.attributes.gamemodeKey.slice(0, -1)]
                const date = new Date(resp.data.metadata.timestamp).toLocaleString('zh-cn', { timeZone: 'Asia/Shanghai' })
                const ids = []
                resp.data.segments
                    .filter(data => data.type === 'player' && data.metadata.playerName === 'Unknown')
                    .forEach(player => {
                        player.metadata.playerName = `#${player.attributes.playerId}`
                        ids.push(+player.attributes.playerId)
                    })
                resp = JSON.stringify(resp)

                if (ids.length) {
                    GM_xmlhttpRequest({
                        url: `https://ea-api.2788.pro/playerNames/${ids.join(';')}`,
                        onload(xhr) {
                            if (xhr.status !== 200) {
                                console.error('获取玩家名失败', xhr)
                                return
                            }
                            const playerNames = JSON.parse(xhr.responseText)
                            const replace = async function () {
                                while (true) {
                                    const elements = Array.from(document.querySelectorAll('.player-header .info .name'))
                                    if (!elements.length) {
                                        await new Promise(resolve => setTimeout(resolve, 500))
                                        continue
                                    }
                                    for (const element of elements) {
                                        const text = element.textContent.trim()
                                        if (!text.startsWith('#')) continue
                                        const name = playerNames[text.slice(1)]
                                        if (!name) continue
                                        element.textContent = name
                                        element.href += name
                                        element.parentNode.replaceChild(element.cloneNode(true), element)
                                    }
                                    const timeElement = document.querySelector('.report-info .time')
                                    timeElement.textContent = date
                                    break
                                }
                            }
                            replace()
                        },
                    })
                }

                return resp
            })
        }
        return xhrOpen.apply(xhr, arguments)
    }
})()