bonk.io Mobile Mod

This script makes bonk.io playable on mobile.

目前為 2025-11-01 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         bonk.io Mobile Mod
// @namespace    http://tampermonkey.net/
// @version      1.1.2
// @description  This script makes bonk.io playable on mobile.
// @author       kitaesq
// @match        https://bonk.io
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bonk.io
// @grant        none
// ==/UserScript==
if (!window.kitaes) window.kitaes = {}
if (!window.kitaes.findElement){
    window.kitaes.findElement = function(document, selector){return new Promise((res, rej) => {
        let interval = setInterval(() => {
            const el = document.querySelector(selector)
            if (el){
                clearInterval(interval)
                res(el)
            }
        }, 100)
    })}
}
if (!window.kitaes.requestIntercept) {
    window.kitaes.requestIntercept = () => {
        const send = XMLHttpRequest.prototype.send
        XMLHttpRequest.prototype.send = function(body){
            this.addEventListener("load", () => {
                const event = new Event("kitaes-request")
                event.body = body
                event.request = this
                window.dispatchEvent(event)
            })
            return send.apply(this, [body]);
        };
        console.log("Request interceptor loaded")
    }
    window.kitaes.requestIntercept()
}
if (!window.kitaes.fullscreen) {window.kitaes.fullscreen = async () => {
    console.log("[Fullscreen mod] Loading fullscreen mod...")
    const styleElem = document.createElement("style")
    styleElem.textContent =
`body{
    overflow: hidden;
    visibility: hidden;
}
#maingameframe{
    margin: 0 !important;
    position: fixed;
    top: 0 !important;
    left: 0 !important;
    right: 0 !important;
    top: 0 !important;
}
#theme_container{
    top: 36px;
    visibility: visible;
}`
    document.head.append(styleElem)
    document.body.style.visibility = "hidden"
    document.body.style.overflow   = "hidden"
    const maingameframe = await window.kitaes.findElement(document, "#maingameframe")
    maingameframe.style.visibility = "visible"
    maingameframe.style.marginTop = "0"
    const iframeWait = (iframe) => (new Promise(res => iframe.addEventListener('load', res)));
    if (maingameframe.contentDocument.URL === "about:blank") {
        console.log("[Fullscreen mod] iframe is not loaded yet, waiting for iframe to load...")
        await iframeWait(maingameframe)
        console.log("[Fullscreen mod] iframe was loaded")
    }
    const fdocument = maingameframe.contentDocument
    const fwindow = maingameframe.contentWindow
    const fstyleElem = document.createElement("style")
    fstyleElem.className = "kitaes-fullscreen-style"
    fstyleElem.textContent =
`#bonkiocontainer{
    width: 100% !important;
    height: 100% !important;
    border: none !important;
}
canvas{
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}
#xpbarcontainer{
    top: -4px !important
}
#fullscreen_button{
    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABPSURBVEhLYxgFhAAjlAaD/0AAZWIARiCAMsGAWLVMUJpmgOYW4PQ2epAQArj0Dv0gorkFo2DgwWg+IAhobgHOOEAH6HFCrNrRomLYAwYGACfdIBnA7J6WAAAAAElFTkSuQmCC);
    background-repeat: no-repeat;
    background-position: center;
    position: absolute;
    top: 35px;
    left: 0;
    margin: 10px;
    width: 40px;
    height: 40px;
    pointer-events: auto;
}
*{
    touch-action: none;
    user-select: none;
}`
    const bonkioContainer = await window.kitaes.findElement(fdocument, "#bonkiocontainer")
    fdocument.head.append(fstyleElem)
    console.log("[Fullscreen mod] Style was injected")
    const prettyMenu = await window.kitaes.findElement(fdocument, "#prettymenu")
    const fullscreenBtn = document.createElement("div")
    fullscreenBtn.id = "fullscreen_button"
    fullscreenBtn.className = "brownButton brownButton_classic buttonShadow"
    let isFullScreen = false
    fullscreenBtn.onclick = () => {
        if (!isFullScreen) {
            document.body.requestFullscreen()
        }
        else{
            document.exitFullscreen()
        }
        isFullScreen = !isFullScreen
    }
    prettyMenu.append(fullscreenBtn)
    console.log("[Fullscreen mod] Fullscreen button was added")
    const interval = setInterval(() => {
        if (fdocument.body.lastElementChild.tagName !== "DIV" || fdocument.body.lastElementChild.className) return
        fdocument.body.lastElementChild.style.top = "85px"
        clearInterval(interval)
        console.log("[Fullscreen mod] FPS counter was moved to the bottom")
        console.log("[Fullscreen mod] Fullscreen mod was loaded")
    }, 500)
}
window.kitaes.fullscreen()
}
if (!window.kitaes.mobile) {window.kitaes.mobile = async () => {
    console.log("loading mobile mod...")
    document.head.innerHTML += '<meta name="viewport" content="height=600px, initial-scale=0.5, maximum-scale=0.5">'
    const maingameframe = await window.kitaes.findElement(document, "#maingameframe")
    maingameframe.style.visibility = "visible"
    maingameframe.style.marginTop = "0"
    const iframeWait = (iframe) => (new Promise(res => iframe.addEventListener('load', res)));
    if (maingameframe.contentDocument.URL === "about:blank") {
        console.log("[Mobile mod] iframe is not loaded yet, waiting for iframe to load...")
        await iframeWait(maingameframe)
        console.log("[Mobile mod] iframe was loaded")
    }
    const fdocument = maingameframe.contentDocument
    const fwindow = maingameframe.contentWindow
    const buttonContainer = document.createElement("div")
    Object.assign(buttonContainer.style, {
        position: "fixed",
        bottom: "30px",
        right: "30px",
        width: "220px",
        height: "220px",
        display: "flex",
        flexWrap: "wrap",
        justifyContent: "space-between",
        alignItems: "space-between",
        lineHeight: "70px",
        fontSize: "40px"
    })
    const keybinds = {}
    fwindow.addEventListener("kitaes-request", async (e) => {
        if (e.request.responseURL !== "https://bonk2.io/scripts/login_legacy.php") return
        const data = JSON.parse(e.request.responseText)
        const x = "data:image/png;base64," + data.controls
        const arrayBuffer = await (await fetch(x)).arrayBuffer()
        const view = new DataView(arrayBuffer);
        keybinds.up = view.getUint16(2)
        keybinds.down = view.getUint16(6)
        keybinds.left = view.getUint16(10)
        keybinds.right = view.getUint16(14)
        keybinds.heavy = view.getUint16(18)
        keybinds.special = view.getUint16(22)
    })
    const buttons = [["↖", "↑", "↗"],
                     ["←", " ", "→"],
                     ["↙", "↓", "↘"]]
    const touchId = {dpad: 0, heavy: 0, special: 0}
    const activeDirKeys = {up: false, down: false, left: false, right: false}
    const activeSpecialKeys = {heavy: false, special: false}
    let isActive = false
    function simulateKey(key, down) {
        if (down === activeDirKeys[key]) return
        activeDirKeys[key] = down
        const event = document.createEvent("HTMLEvents");
	    event.initEvent("key" + (down ? "down" : "up"), true, false);
        event.keyCode = keybinds[key]
        fdocument.dispatchEvent(event);
    }
    function simulateKeys(keys, type) {
        for (const key of keys){
            simulateKey(key, type)
        }
    }
    function releaseAllKeys(){
        for (const a of Object.keys(keybinds)){
            if (!activeDirKeys[a]) continue
            simulateKey(a, false)
        }
    }
    buttonContainer.ontouchstart = (e) => {
        touchId.dpad = e.changedTouches[0].identifier
        simulateKeys(e.target.keys, true)
        isActive = true
        console.log("down")
    }
    oncontextmenu = e => e.preventDefault()
    fwindow.ontouchend = (e) => {
        console.log("up")
        if (e.changedTouches[0].identifier === touchId.dpad){
            releaseAllKeys()
            isActive = false
        }
        else if (e.changedTouches[0].identifier === touchId.heavy){
            simulateKey("heavy", false)
        }
        else if (e.changedTouches[0].identifier === touchId.special){
            simulateKey("special", false)
        }
    }
    const gamerenderer = await window.kitaes.findElement(fdocument, "#gamerenderer")
    const gameOverlay = document.createElement("div")
    gameOverlay.addEventListener('contextmenu', e => e.preventDefault());
    gameOverlay.style.zIndex = 99999
    gameOverlay.style.position = "fixed"
    gamerenderer.append(gameOverlay)
    gameOverlay.append(buttonContainer)
    const size = {height: "70px", width: "70px"}
    for (let i = 0; i < 3; i++){
        for (let j = 0; j < 3; j++){
            if (j === 1 && i === 1) {
                const div = document.createElement("div")
                Object.assign(div.style, size)
                buttonContainer.append(div)
                continue
            }
            const keys = []
            if      (j === 0) keys.push("left")
            else if (j === 2) keys.push("right")
            if      (i === 0) keys.push("up")
            else if (i === 2) keys.push("down")
            const button = document.createElement("div")
            Object.assign(button.style, size)
            button.keys = keys
            button.textContent = buttons[i][j]
            button.className = "brownButton brownButton_classic buttonShadow"
            buttonContainer.append(button)
        }
    }
    buttonContainer.ontouchmove = (e) => {
        e = e.targetTouches[0]
        const ContainerPos = buttonContainer.getBoundingClientRect()
        const x = e.clientX - ContainerPos.x
        const y = e.clientY - ContainerPos.y
        releaseAllKeys()
        if (x < 74) {
            simulateKey("left", true)
        }
        else if (x > 147) {
            simulateKey("right", true)
        }
        if (y < 74) {
            simulateKey("up", true)
        }
        else if (y > 147) {
            simulateKey("down", true)
        }
    }
    const list = ["special", "heavy"]
    const leftStyle = {height: "70px", width: "200px", fontSize: "30px", lineHeight: "70px", textTransform: "capitalize", position: "fixed", left: "30px"}
    for (let i = 0; i < 2; i++){
        const button = document.createElement("div")
        button.className = "brownButton brownButton_classic buttonShadow"
        button.key = list[i]
        button.textContent = button.key
        Object.assign(button.style, leftStyle)
        button.style.bottom = (30 + (i * 100)) + "px"
        button.ontouchstart = (e) => {
            console.log(button.key)
            simulateKey(button.key, true)
            touchId[button.key] = e.changedTouches[0].identifier
        }
        gameOverlay.append(button)
    }
}
window.kitaes.mobile()}