Geoguessr Custom Emotes

Allows you to use many custom emotes and some commands in the Geoguessr chat

目前為 2022-10-25 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Geoguessr Custom Emotes
// @description  Allows you to use many custom emotes and some commands in the Geoguessr chat
// @version      2.1.3
// @author       victheturtle#5159
// @license      MIT
// @match        https://www.geoguessr.com/*
// @icon         https://www.geoguessr.com/_next/static/images/emote-gg-cf17a1f5d51d0ed53f01c65e941beb6d.png
// @namespace    https://gf.qytechs.cn/users/967692-victheturtle
// ==/UserScript==

let geoguessrCustomEmotes = {};

const customEmotesInjectedClass = "custom-emotes-injected";
const getAllNewMessages = () => document.querySelectorAll(`div[class*="chat-message_messageContent__"]:not([class*="${customEmotesInjectedClass}"])`);

const _cndic = {};
const hrefset = new Set();
async function scanStyles() {
    for (let node of document.querySelectorAll('head link[rel="preload"], head style[data-n-href*=".css"]')) {
        const href = node.href || location.origin+node.dataset.nHref;
        if (hrefset.has(href)) continue;
        hrefset.add(href);
        await fetch(href)
        .then(res => res.text())
        .then(stylesheet => {
            for (let className of stylesheet.split(".")) {
                const ind = className.indexOf("__");
                if (ind != -1) _cndic[className.substr(0, ind+2)] = className.substr(0, ind+7);
            };
        });
    };
}
const cn = (classNameStart) => _cndic[classNameStart]; // cn("status_section__") -> "status_section__8uP8o"

const emoteInjectionTemplate = (emoteSrc) => `</span>
<span class="${cn("chat-message_emoteWrapper__")}"><img src="${emoteSrc}" class="${cn("chat-message_messageEmote__")}"></span>
<span class="${cn("chat-message_messageText__")}">`;

async function fetchWithCors(url, method, body) {
    return await fetch(url, {
        "headers": {
            "accept": "*/*",
            "accept-language": "en-US,en;q=0.8",
            "content-type": "application/json",
            "sec-fetch-dest": "empty",
            "sec-fetch-mode": "cors",
            "sec-fetch-site": "same-site",
            "sec-gpc": "1",
            "x-client": "web"
        },
        "referrer": "https://www.geoguessr.com/",
        "referrerPolicy": "strict-origin-when-cross-origin",
        "body": (method == "GET") ? null : JSON.stringify(body),
        "method": method,
        "mode": "cors",
        "credentials": "include"
    });
};

const getGameId = () => location.pathname.split("/")[2];
const getPartyId = async () => await fetchWithCors(getLobbyApi(getGameId()), "POST", {})
                              .then(it => it.json()).then(it => it.partyId);
const getPlayerId = async (nick) => await fetchWithCors(getLobbyApi(getGameId()), "POST", {})
                              .then(it => it.json()).then(it => {
                                  let matches = it.players.filter(it => it.nick.toLowerCase() == nick.toLowerCase()).map(it => it.playerId).sort();
                                  return matches[matches.length-1];
                              });
const getLobbyApi = (gameId) => `https://game-server.geoguessr.com/api/lobby/${gameId}/join`;
const getKickApi = (gameId) => `https://game-server.geoguessr.com/api/lobby/${gameId}/kick`;
const getBanApi = (partyId) => `https://www.geoguessr.com/api/v4/parties/${partyId}/ban`;
const getRoundNumberApi = (gameId) => `https://game-server.geoguessr.com/api/duels/${gameId}/`;
const getRoundNumber = async () => await fetchWithCors(getRoundNumberApi(getGameId()), "GET")
                              .then(it => it.json()).then(it => it.currentRoundNumber);
const getGuessApi = (gameId) => `https://game-server.geoguessr.com/api/duels/${gameId}/guess`;

async function ban(nick) {
    const playerId = await getPlayerId(nick);
    const partyId = await getPartyId();
    fetchWithCors(getKickApi(getGameId()), "POST", {playerId: playerId}).catch(e => console.log(e));
    fetchWithCors(getBanApi(partyId), "POST", {userId: playerId, ban: true}).catch(e => console.log(e));
};

async function unban(nick) {
    const playerId = await getPlayerId(nick);
    const partyId = await getPartyId();
    fetchWithCors(getBanApi(partyId), "POST", {userId: playerId, ban: false}).catch(e => console.log(e));
};

async function openProfile(nick) {
    const playerId = await getPlayerId(nick);
    window.open("/user/"+playerId);
};

async function guessEiffelTower() {
    const rn = await getRoundNumber();
    fetchWithCors(getGuessApi(getGameId()), "POST", {"lat": 48.85837, "lng": 2.29448, "roundNumber": rn}).catch(e => console.log(e));
};

function handleCommand(type, args, isSelf) {
    try {
        console.log(type)
        console.log(args)
        if (type == "/ban") {
            if (args.length != 0 && isSelf) ban(args);
        } else if (type == "/unban") {
            if (args.length != 0 && isSelf) unban(args);
        } else if (type == "/mute") {
            if (args.length != 0 && isSelf) localStorage.setItem("CustomEmotesMuted"+args.toLowerCase(), "1");
        } else if (type == "/unmute") {
            if (args.length != 0 && isSelf) localStorage.setItem("CustomEmotesMuted"+args.toLowerCase(), "0");
        } else if (type == "/check") {
            if (args.length != 0 && isSelf) openProfile(args);
        } else if (type == "/eiffel") {
            if (location.pathname.includes("duel") && isSelf) guessEiffelTower();
        }
    } catch (e) { console.log(e); };
};

function injectCustomEmotes(words) {
    for (let i=0; i<words.length; i+=2) {
        if (words[i] == "") continue;
        const lowercaseWord = words[i].toLowerCase();
        for (let emoteName in geoguessrCustomEmotes) {
            if (lowercaseWord == emoteName.toLowerCase() || lowercaseWord[0] == ":" && lowercaseWord == ":"+emoteName.toLowerCase()+":") {
                words[i] = emoteInjectionTemplate(geoguessrCustomEmotes[emoteName]);
                break;
            }
        }
    }
    return words.join("");
}

let observer = new MutationObserver((mutations) => {
    const newMessages = getAllNewMessages();
    if (newMessages.length == 0) return;
    scanStyles().then(() => {
    for (let message of newMessages) {
        if (message.classList.contains(customEmotesInjectedClass)) continue;
        message.classList.add(customEmotesInjectedClass);
        const words = message.innerHTML.split(/((?:<|>|&lt;|&gt;|,| |\.)+)/g);
        const author = message.innerHTML.split(/(?:<|>)+/)[2];
        const messageContentStart = words.indexOf("><");
        const isSelf = message.parentNode.className.includes("isSelf");
        if (!isSelf && localStorage.getItem("CustomEmotesMuted"+author.toLowerCase()) == "1") {
            message.innerHTML = words.slice(0, messageContentStart+5).join("") + "[Muted]" + words.slice(words.length-4).join("");
        } else {
            if (words.length >= messageContentStart+10 && words[messageContentStart+5][0] == "/") {
                handleCommand(words[messageContentStart+5], words.slice(messageContentStart+7, words.length-4).join(""), isSelf)
            }
            message.innerHTML = injectCustomEmotes(words);
        }
    }})
});

async function fetchEmotesRepository() {
    const lastTimeFetched = localStorage.getItem("CustomEmotesLastFetched")*1
    if (Date.now() - lastTimeFetched < 60*1000) { // Github API has a limit rate of 60 requests per hour so prevent more than 1 request per minute
        return localStorage.getItem("CustomEmotesStored")
    } else {
        const emotesRepositoryContent = await fetch("https://api.github.com/gists/7e5046589b0f020c1ec80629c582cca6")
        .then(it => it.json())
        .then(it => it.files["GeoguessrCustomEmotesRepository.json"].content);
        localStorage.setItem("CustomEmotesStored", emotesRepositoryContent);
        localStorage.setItem("CustomEmotesLastFetched", Date.now());
        return emotesRepositoryContent;
    }
}

(() => {
    fetchEmotesRepository().then(emotesRepositoryContent => {
        geoguessrCustomEmotes = JSON.parse(emotesRepositoryContent);
        observer.observe(document.body, { subtree: true, childList: true });
    }).catch(err => console.log(`Geoguessr Custom Emotes error at fetchEmotesRepository(): ${err}`));
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址