Geoguessr change Avatar to Pin/ProfilePicture

Changes the Avatar to your Pin/Profile Picture. Works in Duels, Br, Profile and many more places and not only your Avatar but also the Avatar of others

目前為 2023-06-01 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Geoguessr change Avatar to Pin/ProfilePicture
// @description  Changes the Avatar to your Pin/Profile Picture. Works in Duels, Br, Profile and many more places and not only your Avatar but also the Avatar of others
// @version      1.1.0
// @license      MIT
// @author       joniber#5011
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @namespace https://greasyfork.org/users/1072330
// ==/UserScript==

//any questons, feedback, problems or feature request -> Discord joniber#5011

//=====================================================================================\\
//    change these values however you like (make sure to hit ctrl+s afterwards)        \\
//                PIN = PROFILE PICTURE                                                \\
//=====================================================================================\\

const PIN_IN_OWN_PROFILE = true
//                         ^^^^  set this to <false> to NOT change your Avatar to a PIN in your own profile
const PIN_IN_OTHERS_PROFILE = true
//                            ^^^^^  set this to <false> to NOT change the Avatar to a PIN in other profiles
const PIN_IN_DUELS_MATCHMAKING = true
//                         ^^^^  set this to <false> to NOT change the Avatar to a PIN in that area
const PIN_IN_BR_MATCHMAKING = true
//                         ^^^^  set this to <false> to NOT change the Avatar to a PIN in that area
const NO_AVATARS_END_OF_DUEL = true
//                         ^^^^  set this to <false> to NOT change the Avatar to a PIN in that area
const PIN_IN_TEAM_DUEL_PRIVATE_LOBBY = true
//                         ^^^^  set this to <false> to NOT change the Avatar to a PIN in that area
const PIN_IN_FRIEND_TAB = true
//                         ^^^^  set this to <false> to NOT change the Avatar to a PIN in that area

//=====================================================================================\\
//  don't edit anything after this point unless you know what you're doing             \\
//=====================================================================================\\

const GEOGUESSR_USER_ENDPOINT = 'https://geoguessr.com/api/v3/users'

const SCRIPT_PREFIX = 'up__'
const USER_PIN_CLASS = SCRIPT_PREFIX + 'userPin'
const USER_PIN_ID = SCRIPT_PREFIX + 'profilePin'
let av = null
let index = 0
let player1 = null
let player2 = null

async function fillPin(pin, userId) {
    const userData = await getUserData(userId)
    const pinURL = userData.pin.url
    pin.setAttribute('src', '/images/auto/144/144/ce/0/plain/' + pinURL)
}

async function fillLvl(lvl, userId) {
    const userData = await getUserData(userId)
    let level = roundToNearest10(userData.progress.level)
    if(level == 210){
        level = 200
    }
    lvl.firstChild.setAttribute(
        'srcset',
        '/static/avatars/tiers/low-quality/tier-' + level + '.avif'
    )
    lvl.children[1].setAttribute(
        'srcset',
        '/static/avatars/tiers/low-quality/tier-' + level + '.webp'
    )
    lvl.lastChild.setAttribute('src', '/static/avatars/tiers/low-quality/tier-' + level + '.png')
}


function fillImageMultiple(link, id, setLvl){
    if (!link.querySelector(`.${USER_PIN_CLASS}`)) {
        if(wrapper){
            link.firstChild.style.display="none"
            let destination = link
            fillImage(link, link, id, true, setLvl)
        }
    }
}

async function changePinInFriendTab(link) {
    if(link){
        if (!link.querySelector(`.${USER_PIN_CLASS}`)) {
            const wrapper = link.querySelector('[class^="transparent-avatar_imageWrapper__"]')
            if(wrapper){
                wrapper.classList.remove('transparent-avatar_imageWrapper__izUAM')
                wrapper.classList.add('styles_circle__QFYEk', 'styles_variantFloating__Srm_N', 'styles_colorTransparent__2bG5I', 'styles_borderColorTransparent__CwSAk', 'styles_borderSizeFactorZero__MEFpf')
                wrapper.insertAdjacentHTML('afterbegin', wrapperFriend1())
                const destination = link.querySelector('.styles_content__otIVG')
                const pin = destination.firstChild
                let id = link.querySelector('.anchor_variantNoUnderline__SPwsd').href
                const userId = retrieveIdFromLink(id)

                const userData = await getUserData(userId)
                const pinURL = userData.pin.url
                pin.setAttribute('src', '/images/auto/48/48/ce/0/plain/' + pinURL)
                wrapper.lastChild.style="display:none"
            }
        }
    }
}

function fillImage(destination, link, id, c, withLvl){
    if(c){
        destination.insertAdjacentHTML('afterbegin', wrapperDuels())
        destination = link.querySelector('.profile-header_avatarWrapper__5_jDA.up__')
        destination.insertAdjacentHTML('afterbegin', wrapper())
        destination = link.querySelector('.avatar_titleAvatarImage__A51Dx.up__')
        destination.insertAdjacentHTML('beforeend', profilePicture())
        const pin = destination.lastChild
        fillPin(pin, retrieveIdFromLink(id))
        if(withLvl){
            destination = link.querySelector('.styles_rectangle___6gqv.up__')
            destination.insertAdjacentHTML('beforeend', levelBorder())
            const lvl = destination.lastChild
            fillLvl(lvl, retrieveIdFromLink(id))
        }
    }
    else{
        destination.insertAdjacentHTML('beforeend', wrapper())

        destination = document.querySelector('.avatar_titleAvatarImage__A51Dx.up__')
        destination.insertAdjacentHTML('beforeend', profilePicture1())
        const pin = destination.lastChild
        fillPin(pin, retrieveIdFromLink(id))

        if(withLvl){
            destination = document.querySelector('.styles_rectangle___6gqv.up__')
            destination.insertAdjacentHTML('beforeend', levelBorder1())
            const lvl = destination.lastChild
            fillLvl(lvl, retrieveIdFromLink(id))
        }

    }


}

function changeAvatarToPin(link, avatarToRemove) {
    if (!link.querySelector(`.${USER_PIN_CLASS}`)) {
        if (link) {
            let destination = null
            if (avatarToRemove == link) {
                link.querySelector('.styles_image__8M_kp').style = 'display: none'
                destination = link.firstChild
            } else {
                link.querySelector('.player-card_lobbyCardInner__G_xzy').firstChild.remove()
                destination = link.querySelector('.player-card_lobbyCardInner__G_xzy')
            }
            let id = '/me/profile'
            if (link.querySelector('.player-card_userLink__HhoDo') != null) {
                id = link.querySelector('.player-card_userLink__HhoDo').href
            }

            fillImage(destination, link, id, true, true)
        }
    }
}

let pfpToLink = {}
let nameToLink = {}

const originalSend = WebSocket.prototype.send
WebSocket.prototype.send = function(...args) {
    window.ws = this
    if (this.geoguessrClickablePartyCards == 0) return originalSend.call(this, ...args)
    this.geoguessrClickablePartyCards = 0
    const originalOnmessage = this.onmessage
    this.onmessage = ({ data }) => {
        if (originalOnmessage != null) originalOnmessage({ data })
        const received = JSON.parse(data)
        if(received.code == 'PartyUpdated'){
            let payload = JSON.parse(received.payload)
            let players = payload.players
            for (let player of players){
                nameToLink[player.name] = "user/"+player.id
            }
        }
        if (received.code == 'PartyMemberListUpdated') {
            let payload = JSON.parse(received.payload)
            let members = payload.members
            for (let member of members) {
                if (member.pin != "") pfpToLink[member.pin] = "user/"+member.userId
                if (member.fullBodyPin != "") pfpToLink[member.fullBodyPin] = "user/"+member.userId
            }
        }
    }
    return originalSend.call(this, ...args)
}

function addPinToPartyCard(wrapper) {
    const image = wrapper.querySelector('.member-card_imageWrapper__5L2DF img')
    if(image){
        const imgPin = image.src.substr(57)
        const id = pfpToLink[imgPin]
        if (id != null) {
            const link = wrapper.querySelector('.member-card_imageWrapper__5L2DF')
            fillImageMultiple(link, id, false)
        }
    }
}

function addPinToTeamDuelsCard(wrapper){
    const name = wrapper.querySelector('.user-nick_nick__y4VIt').innerHTML.replace('&nbsp', '').replace(';','')
    if(name){
        if(nameToLink.length != 0){
            const id = nameToLink[name]

            if(id){
                const link = wrapper.querySelector('.team-player-card_lobbyCardInner__fT8A_')
                fillImageMultiple(link, id, true)
            }
        }
    }
}
let inBattleRoyale = false
let inDuels = false
let inParty = false
let inTeamDuel = false
let lastOpenedMapHighscoreTab = 0

function onMutationTeamDuel(mutations,observer){
    if (!isTeamDuel()) return

    const cards = document.querySelectorAll('.team-player-card_lobbyCard__7p_OY')
    for(let card of cards){
        addPinToTeamDuelsCard(card)
    }

    if(document.querySelector('.game-finished_avatarContainer__S63IS')){
        onMutationEnd()
    }
}

function onMutationParty(mutations,observer){
    if (location.pathname != "/party") return
    const cards = document.querySelectorAll(".member-card_root__zbBxx")
    for (let card of cards) {
        addPinToPartyCard(card)
    }
}

function onMutationEnd() {
    if (NO_AVATARS_END_OF_DUEL) {
        const gameFinishedAvatarContainer = document.querySelector(
            '.game-finished_avatarContainer__S63IS'
        )
        if (gameFinishedAvatarContainer) {
            if (document.querySelector('.styles_rectangle___6gqv.up__') == null) {
                gameFinishedAvatarContainer.firstChild.remove()
                let destination = gameFinishedAvatarContainer
                destination.style = 'height: fit-content'
                destination.insertAdjacentHTML('afterbegin', wrapperDuels())
                destination = document.querySelector('.profile-header_avatarWrapper__5_jDA.up__')
                destination.insertAdjacentHTML('afterbegin', wrapper())
                document.querySelector('.profile-header_avatar__y6vsp').style =
                    'display:flex; justify-content:center; align-items:center'
                document.querySelector('.avatar_titleAvatar__0pdL9').style = 'width:20rem'
                destination = document.querySelector('.avatar_titleAvatarImage__A51Dx.up__')
                destination.insertAdjacentHTML('beforeend', profilePicture())
                const pin = destination.lastChild
                let id = '/me/profile'
                fillPin(pin, retrieveIdFromLink(id))
                destination = document.querySelector('.styles_rectangle___6gqv.up__')
                destination.insertAdjacentHTML('beforeend', levelBorder())
                const lvl = destination.lastChild
                if (
                    document.querySelector('.shadow-text_root__o3lD9').innerHTML.startsWith('YOU')
                ) {
                    fillLvl(lvl, retrieveIdFromLink(id))
                } else {
                    lvl.lastChild.setAttribute(
                        'src',
                        '/_next/static/images/laurel-wreath-gold-53fd377e1d268ce57fcd6f0dfb9f3727.png'
                    )
                }
            }
        }
    }
}

function onMutationsDuels(mutations, observer) {
    if (player1 == null) {
        player1 = document.querySelectorAll('.lobby_avatarContainer__kN2RK')[0];
        if (player1 != null) {
            player1.children[0].firstChild.remove();
            player1.children[0].remove();
            let destination1 = player1;
            destination1.insertAdjacentHTML('afterbegin', wrapperDuels());
            destination1 = document.querySelector('.profile-header_avatarWrapper__5_jDA.up__');
            destination1.insertAdjacentHTML('afterbegin', wrapper());
            destination1 = document.querySelector('.avatar_titleAvatarImage__A51Dx.up__');
            destination1.insertAdjacentHTML('beforeend', profilePicture());
            const pin = destination1.lastChild;
            let id = player1.lastChild.firstChild.href;

            fillPin(pin, retrieveIdFromLink(id));
            destination1 = document.querySelector('.styles_rectangle___6gqv.up__');
            destination1.insertAdjacentHTML('beforeend', levelBorder());
            const lvl = destination1.lastChild;
            fillLvl(lvl, retrieveIdFromLink(id));
        }
        player2 = document.querySelectorAll('.lobby_avatarContainer__kN2RK')[1];
        if (player2 != null) {
            player2.children[0].firstChild.remove();
            player2.children[0].remove();
            let destination2 = player2;
            destination2.insertAdjacentHTML('afterbegin', wrapperDuels());
            destination2 = document.querySelectorAll(
                '.profile-header_avatarWrapper__5_jDA.up__'
            )[1];
            destination2.insertAdjacentHTML('afterbegin', wrapper());
            destination2 = document.querySelectorAll('.avatar_titleAvatarImage__A51Dx.up__')[1];
            destination2.insertAdjacentHTML('beforeend', profilePicture());
            const pin1 = destination2.lastChild;
            let id1 = player2.lastChild.firstChild.href;

            fillPin(pin1, retrieveIdFromLink(id1));
            destination2 = document.querySelectorAll('.styles_rectangle___6gqv.up__')[1];
            destination2.insertAdjacentHTML('beforeend', levelBorder());
            const lvl1 = destination2.lastChild;
            fillLvl(lvl1, retrieveIdFromLink(id1));
        }
    }
    if (player1 != null && player2 != null) {
        player1 = null;
        player2 = null;
    }

    const endOfDuelsObserver = new MutationObserver(onMutationEnd);
    if (endOfDuelsObserver) {
        endOfDuelsObserver.observe(
            document.querySelector('.overlay_overlay__AR02x'),
            OBSERVER_CONFIG
        );
    }
}
function onMutationsStandard(mutations, observer) {
    if (isDuels() && !inDuels && document.querySelector('.game_hud__fhdo5')) {
        inDuels = true
        const duelsObserver = new MutationObserver(onMutationsDuels)
        duelsObserver.observe(document.querySelector('.game_hud__fhdo5'), OBSERVER_CONFIG)
    } else if (inDuels && !document.querySelector('.game_hud__fhdo5')) {
        inDuels = false
    }
    else if(isParty() && !inParty){
        inParty=true
        const partyObserver = new MutationObserver(onMutationParty)
        partyObserver.observe(document.body, OBSERVER_CONFIG)
    } else if (inParty && !document.querySelector('.party_root__EQz_N')) {
        inParty = false
    }

    else if(isTeamDuel() && !inTeamDuel){
        inTeamDuel=true
        const teamDuelObserver = new MutationObserver(onMutationTeamDuel)
        teamDuelObserver.observe(document.body, OBSERVER_CONFIG1)
    } else if (inParty && !document.querySelector('.lobby_root__Z9W2f')) {
        inTeamDuel = false
    }
    if (inBattleRoyale || inDuels || inParty) {
        return
    }

    if (isProfile()) {
        if (!PIN_IN_OWN_PROFILE && isOwnProfile()) {
            return
        }
        if (!PIN_IN_OTHERS_PROFILE && isOtherProfile()) {
            return
        }
        if (!document.querySelector(`#${USER_PIN_ID}`)) {
            if(document.querySelector('.profile-header_fullBodyAvatar___LRnw')){
            document.querySelector('.profile-header_fullBodyAvatar___LRnw').remove()

            let destination = document.querySelector('.profile-header_avatarWrapper__5_jDA')
            fillImage(destination, document, location.href, false, true)
            }
        }
    }

    if (PIN_IN_BR_MATCHMAKING) {
        // battle royale matchmaking
        for (const link of document.querySelectorAll('.player-card_root__NvK_s')) {
            changeAvatarToPin(link, 'player-card_lobbyCardInner__G_xzy')
        }
    }
    if (PIN_IN_FRIEND_TAB) {
        for (const link of document.querySelectorAll('.chat-friend_content__zbEBt')) {
            changePinInFriendTab(link)
        }
    }
    if (isDuels() && PIN_IN_DUELS_MATCHMAKING) {
        if (!document.querySelector(`#${USER_PIN_ID}`)) {
            if (av == null) {
                av = document.querySelector('.lobby_avatarContainer__kN2RK')
            }
            if (av != null) {
                av.children[0].remove()
                fillImage(av, document, retrieveIdFromLink('/me/profile'), false, true)
            }
            if (av != null) {
                av = null
            }
        }
    }
    if (document.getElementById('60cf2c9800ea7b00015231a2')) {
        document.getElementById('60cf2c9800ea7b00015231a2').remove()
        if (!document.querySelector(`#${USER_PIN_ID}`)) {
            let destination = document.querySelector('.join-party_avatarWrapper__2WzCN')
            fillImage(destination, document, retrieveIdFromLink('/me/profile'), false, true)

        }
    }
}

//helpfunctions
function roundToNearest10(zahl) {
    if (zahl % 10 === 0) {
        return zahl + 10
    } else {
        return Math.ceil(zahl / 10) * 10
    }
}

function retrieveIdFromLink(link) {
    if (link.endsWith('/me/profile')) {
        const data = document.querySelector('#__NEXT_DATA__').text
        const json = JSON.parse(data)
        return json.props.middlewareResults[1].account.user.userId
    }
    return link.split('/').at(-1)
}

function isOtherProfile() {
    return pathMatches('user/.+')
}

function isOwnProfile() {
    return pathMatches('me/profile')
}

function isProfile() {
    return isOwnProfile() || isOtherProfile()
}

function isBattleRoyale() {
    return pathMatches('battle-royale/.+')
}

function isParty(){
    return pathMatches('party')
}

function isTeamDuel(){
    return pathMatches('team-duels/.+')
}

function isDuels() {
    return pathMatches('duels/.+')
}

async function getUserData(id) {
    const response = await fetch(`${GEOGUESSR_USER_ENDPOINT}/${id}`)
    const json = await response.json()
    return json
}

function wrapperFriend1(){
    return `<div class="styles_rectangle___6gqv" style="padding-top: 100%"><div class="styles_innerCircle__Y_L_e"><div class="styles_content__otIVG up__userPin"><img class="styles_image__8M_kp ${USER_PIN_CLASS}" loading="auto" style="object-fit: cover"></div></div></div>`
}

function wrapperFriend() {
    return `<div class="styles_circle__QFYEk  styles_variantFloating__Srm_N styles_colorTransparent__2bG5I styles_borderColorTransparent__CwSAk styles_borderSizeFactorZero__MEFpf"><div class="styles_rectangle___6gqv" style="padding-top: 100%"><div class="styles_innerCircle__Y_L_e"><div class="styles_content__otIVG up__"></div></div></div></div>`
}
function wrapperDuels() {
    return `<div class="profile-header_avatarWrapper__5_jDA up__"></div>`
}

function wrapperEndOfDuels() {
    return `<div class="profile-header_avatar__y6vsp up__"></div>`
}

function wrapper() {
    return `<div class="profile-header_avatar__y6vsp"><div class="avatar_titleAvatar__0pdL9"><div class="styles_rectangle___6gqv up__" style="padding-top: 100%"><div class="avatar_titleAvatarImage__A51Dx up__"></div></div></div></div>`
}

function profilePicture() {
    return `<img class="styles_image__8M_kp ${USER_PIN_CLASS}" loading="auto" style="object-fit:cover">`
}
function profilePicture1() {
    return `<img class="styles_image__8M_kp" id="${USER_PIN_ID}" loading="auto" style="object-fit:cover">`
}

function levelBorder() {
    return `<picture class="avatar_titleAvatarFrame__AT57_ ${USER_PIN_CLASS}"><source type="image/avif"><source type="image/webp"><img class="avatar_titleAvatarFrame__AT57_"></picture>`
}

function levelBorder1() {
    return `<picture class="avatar_titleAvatarFrame__AT57_" id="${USER_PIN_ID}"><source type="image/avif"><source type="image/webp"><img class="avatar_titleAvatarFrame__AT57_"></picture>`
}

const OBSERVER_CONFIG = {
    characterDataOldValue: false,
    subtree: true,
    childList: true,
    characterData: false,
}

const OBSERVER_CONFIG1 = {
    characterDataOldValue: false,
    subtree: true,
    childList: true,
    characterData: true,
    attributes:    true,
    attributeFilter: ["style"]
}

const ERROR_MESSAGE = (wrong) => '${wrong}'

function pathMatches(path) {
    return location.pathname.match(new RegExp(`^/(?:[^/]+/)?${path}$`))
}

const observer = new MutationObserver(onMutationsStandard)

observer.observe(document.body, OBSERVER_CONFIG)