A UserScript to export your characters data from Battle Chronicle to Genshin Optimizer
Last active
June 27, 2025 02:36
-
-
Save atouu/ccf615f6ccd2d228a101118b558cd4d3 to your computer and use it in GitHub Desktop.
Battle Chronicle to GO
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Battle Chronicle to GO | |
// @namespace https://github.com/atouu | |
// @match https://act.hoyolab.com/app/community-game-records-sea/* | |
// @exclude https://act.hoyolab.com/app/community-game-records-sea/rpg/* | |
// @grant GM.xmlHttpRequest | |
// @grant GM_addStyle | |
// @version 1.4 | |
// @author atouu | |
// @description Export Battle Chronicle characters data to Genshin Optimizer | |
// @downloadURL https://gist.github.com/atouu/ccf615f6ccd2d228a101118b558cd4d3/raw/bctogo.user.js | |
// @updateURL https://gist.github.com/atouu/ccf615f6ccd2d228a101118b558cd4d3/raw/bctogo.user.js | |
// ==/UserScript== | |
const PROP_TO_GO = { | |
1: "hp", 2: "hp", 3: "hp_", 5: "atk", | |
6: "atk_", 7: "def", 8: "def", 9: "def_", | |
20: "critRate_", 22: "critDMG_", 23: "enerRech_", 26: "heal_", | |
28: "eleMas", 30: "physical_dmg_", 40: "pyro_dmg_", 41: "electro_dmg_", | |
42: "hydro_dmg_", 43: "dendro_dmg_", 44: "anemo_dmg_", 45: "geo_dmg_", | |
46: "cryo_dmg_" | |
} | |
const SLOT_TO_GO = { | |
1: "flower", | |
2: "plume", | |
3: "sands", | |
4: "goblet", | |
5: "circlet" | |
} | |
const exportBtn = document.createElement("button") | |
exportBtn.classList.add("go-export-btn") | |
exportBtn.innerText = "Export" | |
exportBtn.addEventListener("click", async () => { | |
exportBtn.innerText = "Exporting..." | |
exportBtn.disabled = true | |
const charDetails = await getCharacterDetails() | |
const characters = [] | |
const artifacts = [] | |
const weapons = [] | |
charDetails.data.list.forEach(t => { | |
const charName = t.base.name.replace(' ', '') | |
characters.push({ | |
key: charName == "Traveler" ? "Traveler" + t.base.element : charName, | |
level: t.base.level, | |
constellation: t.base.actived_constellation_num, | |
talent: calcTalents(t.skills, t.constellations) | |
}) | |
t.relics.forEach(u => { | |
artifacts.push({ | |
setKey: toPascalCase(u.set.name), | |
slotKey: SLOT_TO_GO[u.pos], | |
rarity: u.rarity, | |
level: u.level, | |
mainStatKey: PROP_TO_GO[u.main_property.property_type], | |
location: charName, | |
substats: u.sub_property_list.map(x => ({ | |
key: PROP_TO_GO[x.property_type], | |
value: parseFloat(x.value.replace('%', '')) | |
})) | |
}) | |
}) | |
weapons.push({ | |
key: toPascalCase(t.weapon.name), | |
level: t.weapon.level, | |
ascension: t.weapon.promote_level, | |
refinement: t.weapon.affix_level, | |
location: charName, | |
lock: false | |
}) | |
}) | |
showExportDialog(JSON.stringify({ | |
format: "GOOD", | |
version: 2, | |
source: "Battle Chronicle to GO", | |
characters: characters, | |
artifacts: artifacts, | |
weapons: weapons | |
}, null, 2)) | |
exportBtn.innerText = "Export" | |
exportBtn.disabled = false | |
}) | |
const observer = new MutationObserver(() => { | |
const header = document.querySelector(".mhy-hoyolab-header__right, .nav-bar div.right") | |
if (!header) return | |
observer.disconnect(); | |
header.prepend(exportBtn) | |
}); | |
observer.observe(document.body, { | |
childList: true, | |
subtree: true | |
}); | |
async function getCharacterDetails() { | |
const getChars = await gmXHRAsync({ | |
method: "POST", | |
url: "https://bbs-api-os.hoyolab.com/game_record/genshin/api/character/list", | |
headers: { | |
"Accept": "application/json, text/plain, */*", | |
"Content-Type": "application/json;charset=utf-8", | |
"x-rpc-language": "en-us", | |
"x-rpc-lang": "en-us", | |
}, | |
data: JSON.stringify({ | |
role_id: unsafeWindow._gs_.state.crtRole.game_uid, | |
server: unsafeWindow._gs_.state.crtRole.region | |
}) | |
}) | |
const charIds = JSON.parse(getChars.responseText).data.list.map(v => v.id) | |
const getDetails = await gmXHRAsync({ | |
method: "POST", | |
url: "https://bbs-api-os.hoyolab.com/game_record/genshin/api/character/detail", | |
headers: { | |
"Accept": "application/json, text/plain, */*", | |
"Content-Type": "application/json;charset=utf-8", | |
"x-rpc-language": "en-us", | |
"x-rpc-lang": "en-us", | |
}, | |
data: JSON.stringify({ | |
character_ids: charIds, | |
role_id: unsafeWindow._gs_.state.crtRole.game_uid, | |
server: unsafeWindow._gs_.state.crtRole.region | |
}) | |
}) | |
return (JSON.parse(getDetails.responseText)) | |
} | |
function showExportDialog(content) { | |
const dialogHtml = ` | |
<div class="go-export-backdrop"> | |
<div class="go-export-dialog"> | |
<h1>Battle Chronicle to GO</h1> | |
<p>Copy and paste the JSON below into Genshin Optimizer. Note that if your character was ascended and stays at level 20, 40, 50, 60, | |
70 or 80, you need to manually set their ascension level in Genshin Optimizer as battle chronicle doesn't have a data about it.</p> | |
<textarea onfocus="this.select()" readonly></textarea> | |
<span>Click anywhere outside to close.</span> | |
</div> | |
</div> | |
` | |
document.body.insertAdjacentHTML("beforeend", dialogHtml) | |
const backdrop = document.querySelector(".go-export-backdrop") | |
backdrop.addEventListener("click", (e) => { | |
if (e.target == backdrop) { | |
backdrop.remove() | |
} | |
}) | |
const textarea = document.querySelector(".go-export-dialog textarea") | |
textarea.value = content | |
} | |
function calcTalents(talents, cons) { | |
const consEffects = [2,4].map(e => cons[e].is_actived ? cons[e].effect : null ).toString() | |
const levels = talents.map(e => consEffects.includes(e.name) ? e.level - 3 : e.level) | |
return { | |
auto: levels[0], | |
skill: levels[1], | |
burst: levels[2] | |
} | |
} | |
function toPascalCase(s) { | |
return s.replace(/"|'/g, '').split(/ |-/).map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('') | |
} | |
function gmXHRAsync(options) { | |
return new Promise((resolve, reject) => { | |
GM.xmlHttpRequest({ | |
...options, | |
onload: (response) => resolve(response), | |
onerror: (error) => reject(error), | |
ontimeout: (timeout) => reject(timeout) | |
}); | |
}); | |
} | |
/** Styles **/ | |
GM_addStyle(` | |
body:has(.go-export-backdrop) { | |
overflow: hidden; | |
} | |
.go-export-btn { | |
cursor: pointer; | |
border: none; | |
display: flex; | |
align-items: center; | |
padding: 0 12px; | |
height: 32px; | |
border-radius: 16px; | |
margin-right: 16px; | |
background-color: #343746; | |
color: #8592a3; | |
font-size: 14px; | |
font-family: SFProText-Semibold,SFProText,sans-serif; | |
font-weight: 600; | |
} | |
.go-export-backdrop { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
width: 100vw; | |
height: 100vh; | |
background-color: rgba(0,0,0,.5); | |
position: fixed; | |
top: 0; | |
z-index: 9999; | |
} | |
.go-export-dialog { | |
display: flex; | |
flex-flow: column; | |
gap: 0.5em; | |
border-radius: 1em; | |
margin: 1em; | |
padding: 1em; | |
background: rgba(0,0,0,.48); | |
max-width: 45em; | |
backdrop-filter: blur(2em); | |
z-index: 999; | |
} | |
.go-export-dialog h1 { | |
font-size: 1.5em; | |
color: hsla(0,0%,100%,.85); | |
} | |
.go-export-dialog p { | |
color: hsla(0,0%,100%,.75); | |
} | |
.go-export-dialog textarea { | |
display: block; | |
width: 100%; | |
height: 20em; | |
resize: none; | |
font-family: monospace, monospace; | |
} | |
.go-export-dialog span { | |
color: hsla(0,0%,100%,.45); | |
} | |
`) |
@frzyc Fixed! Thank you!
You also broke link matching for https://act.hoyolab.com/app/community-game-records-sea/index.html#/ys
with v1.2
@frzyc Didn't know Violentmonkey @match
was different from others, fixed it now.
How do I actually run the javascript
@FantasticDanger0 I apologize for very late reply but you need either ViolentMonkey or TamperMonkey
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is super cool!
There are some issues with the way in which you convert the display name for weapons:

Here is a list of GO keys for weapons. https://github.com/frzyc/genshin-optimizer/blob/master/libs/gi/consts/src/weapon.ts