update: brawl stars player stats (WIP) & brawl stars player bind (2 DC user)

This commit is contained in:
Letian Li 2025-05-06 23:13:48 +02:00
parent c2a48e6d5f
commit e21628c09c
7 changed files with 249 additions and 11 deletions

BIN
assets/brawl_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

@ -12,7 +12,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
"prisma": "^6.6.0", "prisma": "^6.7.0",
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5", "typescript": "^5",
@ -78,17 +78,17 @@
"@prisma/client": ["@prisma/client@6.6.0", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-vfp73YT/BHsWWOAuthKQ/1lBgESSqYqAWZEYyTdGXyFAHpmewwWL2Iz6ErIzkj4aHbuc6/cGSsE6ZY+pBO04Cg=="], "@prisma/client": ["@prisma/client@6.6.0", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-vfp73YT/BHsWWOAuthKQ/1lBgESSqYqAWZEYyTdGXyFAHpmewwWL2Iz6ErIzkj4aHbuc6/cGSsE6ZY+pBO04Cg=="],
"@prisma/config": ["@prisma/config@6.6.0", "", { "dependencies": { "esbuild": ">=0.12 <1", "esbuild-register": "3.6.0" } }, "sha512-d8FlXRHsx72RbN8nA2QCRORNv5AcUnPXgtPvwhXmYkQSMF/j9cKaJg+9VcUzBRXGy9QBckNzEQDEJZdEOZ+ubA=="], "@prisma/config": ["@prisma/config@6.7.0", "", { "dependencies": { "esbuild": ">=0.12 <1", "esbuild-register": "3.6.0" } }, "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA=="],
"@prisma/debug": ["@prisma/debug@6.6.0", "", {}, "sha512-DL6n4IKlW5k2LEXzpN60SQ1kP/F6fqaCgU/McgaYsxSf43GZ8lwtmXLke9efS+L1uGmrhtBUP4npV/QKF8s2ZQ=="], "@prisma/debug": ["@prisma/debug@6.7.0", "", {}, "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w=="],
"@prisma/engines": ["@prisma/engines@6.6.0", "", { "dependencies": { "@prisma/debug": "6.6.0", "@prisma/engines-version": "6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a", "@prisma/fetch-engine": "6.6.0", "@prisma/get-platform": "6.6.0" } }, "sha512-nC0IV4NHh7500cozD1fBoTwTD1ydJERndreIjpZr/S3mno3P6tm8qnXmIND5SwUkibNeSJMpgl4gAnlqJ/gVlg=="], "@prisma/engines": ["@prisma/engines@6.7.0", "", { "dependencies": { "@prisma/debug": "6.7.0", "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", "@prisma/fetch-engine": "6.7.0", "@prisma/get-platform": "6.7.0" } }, "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw=="],
"@prisma/engines-version": ["@prisma/engines-version@6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a", "", {}, "sha512-JzRaQ5Em1fuEcbR3nUsMNYaIYrOT1iMheenjCvzZblJcjv/3JIuxXN7RCNT5i6lRkLodW5ojCGhR7n5yvnNKrw=="], "@prisma/engines-version": ["@prisma/engines-version@6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", "", {}, "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA=="],
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.6.0", "", { "dependencies": { "@prisma/debug": "6.6.0", "@prisma/engines-version": "6.6.0-53.f676762280b54cd07c770017ed3711ddde35f37a", "@prisma/get-platform": "6.6.0" } }, "sha512-Ohfo8gKp05LFLZaBlPUApM0M7k43a0jmo86YY35u1/4t+vuQH9mRGU7jGwVzGFY3v+9edeb/cowb1oG4buM1yw=="], "@prisma/fetch-engine": ["@prisma/fetch-engine@6.7.0", "", { "dependencies": { "@prisma/debug": "6.7.0", "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", "@prisma/get-platform": "6.7.0" } }, "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ=="],
"@prisma/get-platform": ["@prisma/get-platform@6.6.0", "", { "dependencies": { "@prisma/debug": "6.6.0" } }, "sha512-3qCwmnT4Jh5WCGUrkWcc6VZaw0JY7eWN175/pcb5Z6FiLZZ3ygY93UX0WuV41bG51a6JN/oBH0uywJ90Y+V5eA=="], "@prisma/get-platform": ["@prisma/get-platform@6.7.0", "", { "dependencies": { "@prisma/debug": "6.7.0" } }, "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw=="],
"@puppeteer/browsers": ["@puppeteer/browsers@2.10.2", "", { "dependencies": { "debug": "^4.4.0", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.1", "tar-fs": "^3.0.8", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-i4Ez+s9oRWQbNjtI/3+jxr7OH508mjAKvza0ekPJem0ZtmsYHP3B5dq62+IaBHKaGCOuqJxXzvFLUhJvQ6jtsQ=="], "@puppeteer/browsers": ["@puppeteer/browsers@2.10.2", "", { "dependencies": { "debug": "^4.4.0", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.1", "tar-fs": "^3.0.8", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-i4Ez+s9oRWQbNjtI/3+jxr7OH508mjAKvza0ekPJem0ZtmsYHP3B5dq62+IaBHKaGCOuqJxXzvFLUhJvQ6jtsQ=="],
@ -230,7 +230,7 @@
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"prisma": ["prisma@6.6.0", "", { "dependencies": { "@prisma/config": "6.6.0", "@prisma/engines": "6.6.0" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-SYCUykz+1cnl6Ugd8VUvtTQq5+j1Q7C0CtzKPjQ8JyA2ALh0EEJkMCS+KgdnvKW1lrxjtjCyJSHOOT236mENYg=="], "prisma": ["prisma@6.7.0", "", { "dependencies": { "@prisma/config": "6.7.0", "@prisma/engines": "6.7.0" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-vArg+4UqnQ13CVhc2WUosemwh6hr6cr6FY2uzDvCIFwH8pu8BXVv38PktoMLVjtX7sbYThxbnZF5YiR8sN2clw=="],
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],

View File

@ -1 +1 @@
[{"name":"mcs","type":1,"nsfw":false,"description":"Qeury a Minecraft server","contexts":[0,1,2],"integration_types":[0],"options":[{"name":"ip","description":"Server IP","type":3,"name_localizations":{},"description_localizations":{},"autocomplete":false},{"name":"port","description":"Server Port","type":10,"name_localizations":{},"description_localizations":{},"autocomplete":false}]},{"name":"ping","type":1,"nsfw":false,"description":"Show latency with Discord","contexts":[0,1,2],"integration_types":[0],"options":[]}] [{"name":"mcs","type":1,"nsfw":false,"description":"Qeury a Minecraft server","contexts":[0,1,2],"integration_types":[0],"options":[{"name":"ip","description":"Server IP","type":3,"name_localizations":{},"description_localizations":{},"autocomplete":false},{"name":"port","description":"Server Port","type":10,"name_localizations":{},"description_localizations":{},"autocomplete":false}]},{"name":"brawlbind","type":1,"nsfw":false,"description":"Associate your Discord account with your Brawl Stars account","contexts":[0,1,2],"integration_types":[0],"options":[{"name":"playertag","description":"Your Brawl Stars player tag","required":true,"type":3,"name_localizations":{},"description_localizations":{},"autocomplete":false}]},{"name":"ping","type":1,"nsfw":false,"description":"Show latency with Discord","contexts":[0,1,2],"integration_types":[0],"options":[]},{"name":"brawlstats","type":1,"nsfw":false,"description":"Query your Brawl Stars player stats","contexts":[0,1,2],"integration_types":[0],"options":[]}]

View File

@ -5,10 +5,10 @@
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
"prisma": "^6.6.0" "prisma": "^6.7.0"
}, },
"scripts": { "scripts": {
"dev": "bun --env-file=.env --hot src/index.ts" "dev": "bun --env-file=.env --watch src/index.ts"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5" "typescript": "^5"

View File

@ -21,4 +21,13 @@ model mcsBind {
@@map("mcs_binds") @@map("mcs_binds")
@@index([channelId]) @@index([channelId])
}
model brawlPlayer {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String
playerTag String
@@map("brawl_players")
@@index([userId])
} }

86
src/commands/brawlBind.ts Normal file
View File

@ -0,0 +1,86 @@
import {
Command,
Declare,
Options,
createStringOption,
type CommandContext
} from 'seyfert';
import { prisma } from '..';
const options = {
playertag: createStringOption({
description: 'Your Brawl Stars player tag',
required: true,
}),
}
@Declare({
name: 'brawlbind',
description: 'Associate your Discord account with your Brawl Stars account',
})
@Options(options)
export default class brawlBindCommand extends Command {
async run(ctx: CommandContext<typeof options>) {
let tag = ctx.options.playertag
if (!tag.startsWith("#")) tag = "#" + tag
const playerTagRegex = /^#[0-9A-Z]{6,10}$/
if (!playerTagRegex.test(ctx.options.playertag)) {
await ctx.write({
content: 'Invalid player tag (Example: `#A1B2C3D4E`)'
})
return
}
const userId: string = ctx.author.id
console.log(tag + `\n` + userId)
const res = await prisma.brawlPlayer.findFirst({
where: {
userId: userId
}
})
if (res && res.playerTag !== tag) {
try {
await prisma.brawlPlayer.update({
where: {
id: res.id,
userId: userId
},
data: {
playerTag: tag
}
})
} catch (e) {
await ctx.write({
content: `${e}`
})
return
}
await ctx.write({
content: `Updated your playertag from ${res.playerTag} to \`${tag}\``
})
return
} else if (res && res.playerTag === tag) {
await ctx.write({
content: `Nothing Changed, playertag already associated to \`${tag}\``
})
}
if (!res) {
try {
await prisma.brawlPlayer.create({
data: {
userId: userId,
playerTag: tag
}
})
} catch (e) {
await ctx.write({
content: `${e}`
})
}
await ctx.write({
content: `Associated to your playertag \`${tag}\``
})
return
}
}
}

143
src/commands/brawlStats.ts Normal file
View File

@ -0,0 +1,143 @@
import {
AttachmentBuilder,
Command,
Declare,
Embed,
Options,
createStringOption,
type CommandContext
} from 'seyfert';
import { prisma, browser } from '..';
interface BrawlPlayer {
name: string;
tag: string;
namecolor: string;
nameColor: string;
icon: any;
trophies: number;
highestTrophies: number;
expLevel: number;
expPoints: number;
isQualifiedFromChampionshipChallenge: boolean;
"3vs3Victories": number;
soloVictories: number;
duoVictories: number;
bestRoboRumbleTime: number;
bestTimeAsBigBrawler: number;
club: any;
brawlers: any[];
}
@Declare({
name: 'brawlstats',
description: 'Query your Brawl Stars player stats'
})
export default class brawlStatsCommand extends Command {
async run(ctx: CommandContext) {
const res = await prisma.brawlPlayer.findFirst({
where: {
userId: ctx.author.id
}
})
if (!res) {
await ctx.write({
content: `Your Discord account has not associated to a Brawl Stars playertag, use \`brawlbind\` to associate`
})
return
}
let tag = res.playerTag.replace('#', '%23')
const data = await fetchBrawlPlayer(tag) as BrawlPlayer
if (!data || data === null) {
await ctx.write({
content: `ERROR, CONTACT ADMIN`
})
return
}
const page = await browser.newPage();
const result = generateBrawlStatsHtml(data)
await page.setViewport({ width: 650, height: 200 })
await page.setContent(result)
await page.waitForSelector('body')
const screenshot = await page.screenshot({
encoding: 'base64',
type: 'webp',
fullPage: true
})
await page.close()
const buffer = Buffer.from(screenshot, 'base64')
await ctx.write({
files: [
new AttachmentBuilder()
.setName('brawlstats.png')
.setFile('buffer', buffer)
]
});
return
}
}
function generateBrawlStatsHtml(data: BrawlPlayer): string {
const stats = [
`Player: ${data.name}`,
`Trophies: ${data.trophies}`,
`3vs3 Victories: ${data['3vs3Victories']}`,
`Solo Victories: ${data.soloVictories}`,
`Duo Victories: ${data.duoVictories}`,
`Total Brawlers: ${data.brawlers.length}`
].join('<br>')
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=auto, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
background-color: #1e1e2e;
color: #cdd6f4;
}
</style>
</head>
<body style="width: 650px">
<div class="container mx-auto py-4">
<div class="px-6 flex items-center">
<img src="https://cdn-assets-eu.frontify.com/s3/frontify-enterprise-files-eu/eyJwYXRoIjoic3VwZXJjZWxsXC9maWxlXC9WeGZCYTJZM2VidFliNHhRNDJhdS5wbmcifQ:supercell:4JRrhrJjKTux8065H80-L2EiHN2bJg9E9QuhQD9ztIs?width=2400" class="h-32 w-32 object-contain mr-6" alt="Brawl Stars Logo" />
<div class="text-lg font-bold text-[#cdd6f4]">${stats}</div>
</div>
</div>
<footer class="bg-[#313244] text-center py-2">
<p class="text-sm text-[#cdd6f4]">Generated by Fantastic Spoon - Discord</p>
<p class="text-sm text-[#cdd6f4]">Open Sourced on git.itzdrli.cc © 2025 itzdrli - All Rights Reserved. </p>
</footer>
</body>
</html>`;
}
async function fetchBrawlPlayer(playerTag: string) {
const brawlUrl = `https://api.brawlstars.com/v1/players/${playerTag}`
try {
const res = await fetch(brawlUrl, {
method: 'GET',
headers: {
'Authorization': `Bearer ${process.env.BRAWL_KEY}`,
'Accept': 'application/json'
}
})
if (!res.ok) {
console.error(`Error fetching Brawl Stars player data: ${res.statusText}`)
return null
}
return res.json()
} catch (e) {
console.error(e)
return null
}
}