Skip to content

Commit

Permalink
Added support for exporting Encounters
Browse files Browse the repository at this point in the history
  • Loading branch information
rrgeorge committed Oct 1, 2021
1 parent 32170d4 commit 0f03cfe
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 116 deletions.
113 changes: 113 additions & 0 deletions ddb.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,112 @@ class DDB {
const res = await this.getRequest(url).catch(e => console.log(`Could not populate campaings: ${e}`))
this.campaigns = res?.data || []
}
async getEncounterCount(campaignIds=null) {
const url = "https://encounter-service.dndbeyond.com/v1/encounters"
const params = (campaignIds)? qs.stringify({ 'skip': 0, 'take': 1,campaignIds: campaignIds }) : qs.stringify({ 'skip': 0, 'take': 1 })
await this.getCobaltAuth()
const response = await this.getRequest(`${url}?${params}`,true).catch((e)=>console.log(`Error getting encounter count for source id ${source}: ${e}`))
return response.pagination.total
}
async getEncounters(campaignIds=null,filename = null,zip = null){
const url = "https://encounter-service.dndbeyond.com/v1/encounters"
let params
const count = await this.getEncounterCount(campaignIds).catch((e)=>console.log(e))
console.log(`There are ${count} encounters`)
let pos = 0
console.log("creating progress bar")
const prog = new ProgressBar({title: "Please wait...",text: "Exporting encounters...", detail: "Please wait...", indeterminate: false, maxValue: count})
//prog.on('progress', (v) => prog.detail = `Converting ${v} of ${prog.getOptions().maxValue}`)
console.log("creating zip file")
if (filename) zip = new AdmZip()
var campaign = {
_name: "campaign",
_attrs: { id: uuid5("https://www.dndbeyond.com/my-encounters",uuid5.URL) },
_content: [
{ name: "D&D Beyond Encounters" },
{ description: "This campaign contains encounters imported from D&D Beyond" }
]
}
console.log("begining retrieval")
while ( pos <= count ) {
console.log("Retrieving 100...")
params = (campaignIds)? qs.stringify({ 'skip': pos, 'take': 100,campaignIds: campaignIds }) : qs.stringify({ 'skip': pos, 'take': 100 })
const response = await this.getRequest(`${url}?${params}`,true).catch((e)=>console.log(`Error getting encounters ${pos}/${count}: ${e}`))
console.log(`Retrieved ${response.data.length}`)
let sort = 2000;
for (const encounter of response.data) {
//console.log(encounter)
let parentId = (encounter.campaign)? uuid5(`https://www.dndbeyond.com/campaigns/${encounter.campaign.id}`,uuid5.URL) : campaign._attrs.id
if (!campaign._content.find(g=>g.group&&g.group._attrs.id==parentId)) {
let groupName = (encounter.campaign)? encounter.campaign.name : "No Campaign"
campaign._content.push({
group: {
_attrs: { id: parentId, sort: sort-1000 },
name: groupName,
slug: slugify(groupName)
}
})
}
let enc = {
_name: "encounter",
_attrs: { sort: sort, parent: parentId, id: encounter.id },
_content: [
{name: encounter.name || "Untitled Encounter"},
{description: `${encounter.flavorText+"\n"||""}\n${encounter.description||""}${(encounter.rewards)?`\nRewards: ${encounter.rewards}`:""}`}
]
}
let ids = encounter.monsters.map(m=>m.id)
.filter((v,i,s)=>s.indexOf(v)===i)
const monsters = await this.getMonsterById(ids).catch(e=>`Error getting stat blocks ${e}`)
let labels = []
for (const monster of encounter.monsters) {
let qty = monster.quantity
let name = monster.name
let slug = slugify(monsters.find(m=>m.id===monster.id)?.name)
let namelabel = /(.*) \((.*)\)/.exec(monster.name)
let combatant = {
combatant: {
name: name||monsters.find(m=>m.id===monster.id)?.name,
role: "hostile",
monster: { _attrs: {ref: `/monster/${slug}`} }
}
}
if (namelabel) {
combatant.combatant.name = (namelabel[2])? namelabel[1]: monster.name
if (namelabel[2] && !labels.includes(namelabel[2])) {
combatant.combatant.label = namelabel[2]
labels.push(namelabel[2])
}
}
if (!combatant.combatant.label) {
let i = 1
let label = combatant.combatant.name.substr(0,1) + i.toString()
while(labels.includes(label)) {
i++
label = combatant.combatant.name.substr(0,1) + i.toString()
}
combatant.combatant.label = label
labels.push(label)
}
enc._content.push(combatant)
}
campaign._content.push(enc)
sort += 1
prog.value += 1
}
pos += 100
}
if (filename) {
prog.detail = `Creating XML`
const campaignXML = toXML(campaign,{indent:'\t'})
await zip.addFile("campaign.xml",Buffer.from(campaignXML,'utf8'),null)
prog.detail = `Writing campaign file`
zip.writeZip(filename)
prog.detail = `Saved campaign`
setTimeout(()=>prog.setCompleted(),1000)
}
return campaign
}
async getSources() {
if (!this.cobaltsession) await this.setCobaltSession()
const url = "https://www.dndbeyond.com/mobile/api/v6/available-user-content"
Expand Down Expand Up @@ -652,6 +758,13 @@ class DDB {
const response = await this.getRequest(`${url}?${params}`,true).catch((e)=>console.log(`Error getting monster count for source id ${source}: ${e}`))
return response.pagination.total
}
async getMonsterById(id) {
const url = "https://monster-service.dndbeyond.com/v1/Monster"
const params = qs.stringify({ 'ids': id })
await this.getCobaltAuth()
const response = await this.getRequest(`${url}?${params}`,true).catch((e)=>console.log(`Error getting monster count for source id ${source}: ${e}`))
return response.data
}
async getMonsters(source = 0,filename,zip=null,imageMap=null,prog=null,homebrew=false) {
const url = "https://monster-service.dndbeyond.com/v1/Monster"
var params
Expand Down
58 changes: 52 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,30 @@ app.on('ready', () => {
click: (m) => _win?.loadURL(`https://www.dndbeyond.com/my-campaigns`)
}))
campaignMenu.submenu.append(new MenuItem({type: 'separator'}))
for (var campaign of ddb.campaigns) {
for (const campaign of ddb.campaigns) {
campaignMenu.submenu.append( new MenuItem({
label: he.decode(campaign.name).replaceAll("&","&&"),
id: campaign.id,
toolTip: "Jump to campaign",
click: (m) => _win?.loadURL(`https://www.dndbeyond.com/campaigns/${m.id}`)
click: (m) => _win?.loadURL(`https://www.dndbeyond.com/campaigns/${m.id}`),
}))
}
campaignMenu.submenu.append(new MenuItem({type: 'separator'}))
campaignMenu.submenu.append( new MenuItem({
label: "Export All Encounters",
toolTip: "Export all Encounters from the Encounter Builder",
click: () => {
dialog.showSaveDialog(win,{
title: "Save Encounters",
filters: [ { name: "EncounterPlus Campaign", extensions: ["campaign"]} ],
defaultPath: `encounters.campaign`,
}).then((save) => {
if (save.filePath)
ddb.getEncounters(null,save.filePath).catch(e=>displayError(e))
}
)
}
}))
Menu.setApplicationMenu(menu)
}).then(
ddb.getSources().then(() => {
Expand Down Expand Up @@ -540,7 +556,7 @@ function requestCampaignChars(gameId,cobalt) {
click: (m) => _win?.loadURL(`https://www.dndbeyond.com/my-campaigns`)
}))
campaignMenu.submenu.append(new MenuItem({type: 'separator'}))
for (var campaign of ddb.campaigns) {
for (const campaign of ddb.campaigns) {
campaignMenu.submenu.append( new MenuItem({
label: he.decode(campaign.name).replaceAll("&","&&"),
id: campaign.id,
Expand All @@ -549,17 +565,47 @@ function requestCampaignChars(gameId,cobalt) {
}))
}
campaignMenu.submenu.append(new MenuItem({type: 'separator'}))
campaignMenu.submenu.append( new MenuItem({
label: "Export All Encounters",
toolTip: "Export all Encounters from the Encounter Builder",
click: () => {
dialog.showSaveDialog(win,{
title: "Save Encounters",
filters: [ { name: "EncounterPlus Campaign", extensions: ["campaign"]} ],
defaultPath: `encounters.campaign`,
}).then((save) => {
if (save.filePath)
ddb.getEncounters(null,save.filePath).catch(e=>displayError(e))
}
)
}
}))
campaignMenu.submenu.append(new MenuItem({type: 'separator'}))
campaignMenu.submenu.append(new MenuItem({
label: "Export this Campaign's Encounters",
click: () => {
dialog.showSaveDialog(_win,{
title: "Save exported encounters",
filters: [ { name: "EncounterPlus Campaign", extensions: ["campaign"]} ],
defaultPath: `${thisCampaign.label.replaceAll("&&","&")}-encounters.campaign`,
}).then((save) => {
if (save.filePath) {
ddb.getEncounters(thisCampaign.id,save.filePath).catch(e=>displayError(e))
}
})
}
}))
campaignMenu.submenu.append(new MenuItem({
label: "Convert These Characters for EncounterPlus",
label: "Export this Campaign's Characters",
click: () => {
dialog.showSaveDialog(_win,{
title: "Save exported characters",
filters: [ { name: "EncounterPlus Compendium", extensions: ["compendium"]} ],
defaultPath: `${thisCampaign.label}.compendium`,
defaultPath: `${thisCampaign.label.replaceAll("&&","&")}.compendium`,
}).then((save) => {
if (save.filePath) {
const prog = new ProgressBar({title: "Converting campaign characters...", text: "Converting campaign characters...", detail: "Please wait..."})
download(_win,`https://w.bobg.us/ddb.php?tokenmap=true&circles=true&campaign=https://ddb.ac/characters/${campaignChars[0].id}`,{
download(_win,`https://play5e.online/ddb.php?tokenmap=true&circles=true&campaign=https://ddb.ac/characters/${campaignChars[0].id}`,{
filename: path.basename(save.filePath),
directory: path.dirname(save.filePath),
onStarted: () => prog.setCompleted()
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "encounterlog",
"productName": "EncounterLog",
"version": "2.1.10",
"version": "2.2.0",
"description": "Connect D&D Beyond to EncounterPlus",
"repository": {
"type": "git",
Expand All @@ -14,11 +14,11 @@
"build-win": "npm_config_platform=win32 npm_config_arch=x64 yarn add opencv4nodejs-prebuilt@5.3.3 sharp; electron-builder build --win;yarn add opencv4nodejs-prebuilt@5.3.3 sharp",
"build-deb": "npm_config_platform=linux npm_config_arch=x64 yarn add opencv4nodejs-prebuilt@5.3.3 sharp; electron-builder build --linux deb;yarn add opencv4nodejs-prebuilt@5.3.3 sharp",
"build-all": "yarn build-mac;yarn build-win;yarn build-deb",
"publish-mac": "security unlock;electron-builder -p always --mac",
"publish-mac": "electron-builder -p always --mac",
"publish-win": "npm_config_platform=win32 npm_config_arch=x64 yarn add opencv4nodejs-prebuilt@5.3.3 sharp; electron-builder -p always --win;yarn add opencv4nodejs-prebuilt@5.3.3 sharp",
"publish-deb": "npm_config_platform=linux npm_config_arch=x64 yarn add opencv4nodejs-prebuilt@5.3.3 sharp; electron-builder -p always --linux;yarn add opencv4nodejs-prebuilt@5.3.3 sharp",
"publish-all": "yarn publish-mac;yarn publish-win;yarn publish-deb",
"release": "yarn publish-all",
"release": "security unlock;git push;git push --tags;yarn publish-all",
"postinstall": "electron-builder install-app-deps",
"app": "electron ."
},
Expand Down Expand Up @@ -144,7 +144,7 @@
"ws": "^7.4.5"
},
"devDependencies": {
"electron": "^13.2.3",
"electron": "13.2.3",
"electron-builder": "^22.11.7",
"electron-notarize": "^1.1.0"
}
Expand Down
Loading

0 comments on commit 0f03cfe

Please sign in to comment.