4 Commits

Author SHA1 Message Date
6b5b8bb54f add deprecation notice
All checks were successful
Publish / docker (push) Successful in 1m59s
2023-11-05 23:17:44 +00:00
403c34befd Merge branch 'main' of https://git.fascinated.cc/Fascinated/beatsaber-overlay
All checks were successful
Publish / docker (push) Successful in 2m31s
2023-10-31 11:44:53 +00:00
0185a9ed3e silly mee 2023-10-31 11:44:50 +00:00
47a255e48a fix proxy 2023-10-31 11:44:27 +00:00
10 changed files with 441 additions and 453 deletions

View File

@ -1,12 +1,12 @@
REACT_APP_HTTP_PROXY=https://proxy.fascinated.cc REACT_APP_HTTP_PROXY=<https://proxy.fascinated.cc>
REACT_APP_SITE_NAME=BeatSaber Overlay REACT_APP_SITE_NAME=BeatSaber Overlay
REACT_APP_SITE_TITLE=BeatSaber Overlay - Simple and easy to use BeatSaber overlay REACT_APP_SITE_TITLE=BeatSaber Overlay - Simple and easy to use BeatSaber overlay
REACT_APP_SITE_DESCRIPTION=ScoreSaber and BeatLeader overlay for Twitch streamers - Elevate your Beat Saber streams with our professional, customizable overlay that displays your real-time scores, rankings, and leaderboard information for your viewers. REACT_APP_SITE_DESCRIPTION=ScoreSaber and BeatLeader overlay for Twitch streamers - Elevate your Beat Saber streams with our professional, customizable overlay that displays your real-time scores, rankings, and leaderboard information for your viewers.
REACT_APP_SITE_COLOR=0EBFE9 REACT_APP_SITE_COLOR=0EBFE9
REACT_APP_SITE_URL=http://localhost:3000 REACT_APP_SITE_URL=<http://localhost:3000>
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_HOST=localhost REDIS_HOST=localhost
REDIS_PASSWORD=set me REDIS_PASSWORD=set me
REDIS_DATABASE=0 REDIS_DATABASE=0

View File

@ -20,7 +20,7 @@
"core-js-pure": "^3.33.0", "core-js-pure": "^3.33.0",
"critters": "^0.0.20", "critters": "^0.0.20",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"next": "^14.0.0", "next": "^13.5.4",
"next-seo": "^6.1.0", "next-seo": "^6.1.0",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
@ -37,7 +37,7 @@
"@types/react": "^18.2.28", "@types/react": "^18.2.28",
"@types/websocket": "^1.0.7", "@types/websocket": "^1.0.7",
"eslint": "^8.51.0", "eslint": "^8.51.0",
"eslint-config-next": "^14.0.0", "eslint-config-next": "^13.5.4",
"npm-check": "^6.0.1", "npm-check": "^6.0.1",
"typescript": "^5.2.2" "typescript": "^5.2.2"
} }

96
pnpm-lock.yaml generated
View File

@ -39,14 +39,14 @@ dependencies:
specifier: ^5.3.2 specifier: ^5.3.2
version: 5.3.2 version: 5.3.2
next: next:
specifier: ^14.0.0 specifier: ^13.5.4
version: 14.0.0(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0) version: 13.5.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)
next-seo: next-seo:
specifier: ^6.1.0 specifier: ^6.1.0
version: 6.1.0(next@14.0.0)(react-dom@18.2.0)(react@18.2.0) version: 6.1.0(next@13.5.4)(react-dom@18.2.0)(react@18.2.0)
next-themes: next-themes:
specifier: ^0.2.1 specifier: ^0.2.1
version: 0.2.1(next@14.0.0)(react-dom@18.2.0)(react@18.2.0) version: 0.2.1(next@13.5.4)(react-dom@18.2.0)(react@18.2.0)
prop-types: prop-types:
specifier: ^15.8.1 specifier: ^15.8.1
version: 15.8.1 version: 15.8.1
@ -86,8 +86,8 @@ devDependencies:
specifier: ^8.51.0 specifier: ^8.51.0
version: 8.51.0 version: 8.51.0
eslint-config-next: eslint-config-next:
specifier: ^14.0.0 specifier: ^13.5.4
version: 14.0.0(eslint@8.51.0)(typescript@5.2.2) version: 13.5.4(eslint@8.51.0)(typescript@5.2.2)
npm-check: npm-check:
specifier: ^6.0.1 specifier: ^6.0.1
version: 6.0.1 version: 6.0.1
@ -528,18 +528,18 @@ packages:
'@jridgewell/resolve-uri': 3.1.1 '@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
/@next/env@14.0.0: /@next/env@13.5.4:
resolution: {integrity: sha512-cIKhxkfVELB6hFjYsbtEeTus2mwrTC+JissfZYM0n+8Fv+g8ucUfOlm3VEDtwtwydZ0Nuauv3bl0qF82nnCAqA==} resolution: {integrity: sha512-LGegJkMvRNw90WWphGJ3RMHMVplYcOfRWf2Be3td3sUa+1AaxmsYyANsA+znrGCBjXJNi4XAQlSoEfUxs/4kIQ==}
dev: false dev: false
/@next/eslint-plugin-next@14.0.0: /@next/eslint-plugin-next@13.5.4:
resolution: {integrity: sha512-Ye37nNI09V3yt7pzuzSQtwlvuJ2CGzFszHXkcTHHZgNr7EhTMFLipn3VSJChy+e5+ahTdNApPphc3qCPUsn10A==} resolution: {integrity: sha512-vI94U+D7RNgX6XypSyjeFrOzxGlZyxOplU0dVE5norIfZGn/LDjJYPHdvdsR5vN1eRtl6PDAsOHmycFEOljK5A==}
dependencies: dependencies:
glob: 7.1.7 glob: 7.1.7
dev: true dev: true
/@next/swc-darwin-arm64@14.0.0: /@next/swc-darwin-arm64@13.5.4:
resolution: {integrity: sha512-HQKi159jCz4SRsPesVCiNN6tPSAFUkOuSkpJsqYTIlbHLKr1mD6be/J0TvWV6fwJekj81bZV9V/Tgx3C2HO9lA==} resolution: {integrity: sha512-Df8SHuXgF1p+aonBMcDPEsaahNo2TCwuie7VXED4FVyECvdXfRT9unapm54NssV9tF3OQFKBFOdlje4T43VO0w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@ -547,8 +547,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-x64@14.0.0: /@next/swc-darwin-x64@13.5.4:
resolution: {integrity: sha512-4YyQLMSaCgX/kgC1jjF3s3xSoBnwHuDhnF6WA1DWNEYRsbOOPWjcYhv8TKhRe2ApdOam+VfQSffC4ZD+X4u1Cg==} resolution: {integrity: sha512-siPuUwO45PnNRMeZnSa8n/Lye5ZX93IJom9wQRB5DEOdFrw0JjOMu1GINB8jAEdwa7Vdyn1oJ2xGNaQpdQQ9Pw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@ -556,8 +556,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-gnu@14.0.0: /@next/swc-linux-arm64-gnu@13.5.4:
resolution: {integrity: sha512-io7fMkJ28Glj7SH8yvnlD6naIhRDnDxeE55CmpQkj3+uaA2Hko6WGY2pT5SzpQLTnGGnviK85cy8EJ2qsETj/g==} resolution: {integrity: sha512-l/k/fvRP/zmB2jkFMfefmFkyZbDkYW0mRM/LB+tH5u9pB98WsHXC0WvDHlGCYp3CH/jlkJPL7gN8nkTQVrQ/2w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -565,8 +565,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-musl@14.0.0: /@next/swc-linux-arm64-musl@13.5.4:
resolution: {integrity: sha512-nC2h0l1Jt8LEzyQeSs/BKpXAMe0mnHIMykYALWaeddTqCv5UEN8nGO3BG8JAqW/Y8iutqJsaMe2A9itS0d/r8w==} resolution: {integrity: sha512-YYGb7SlLkI+XqfQa8VPErljb7k9nUnhhRrVaOdfJNCaQnHBcvbT7cx/UjDQLdleJcfyg1Hkn5YSSIeVfjgmkTg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -574,8 +574,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-gnu@14.0.0: /@next/swc-linux-x64-gnu@13.5.4:
resolution: {integrity: sha512-Wf+WjXibJQ7hHXOdNOmSMW5bxeJHVf46Pwb3eLSD2L76NrytQlif9NH7JpHuFlYKCQGfKfgSYYre5rIfmnSwQw==} resolution: {integrity: sha512-uE61vyUSClnCH18YHjA8tE1prr/PBFlBFhxBZis4XBRJoR+txAky5d7gGNUIbQ8sZZ7LVkSVgm/5Fc7mwXmRAg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -583,8 +583,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-musl@14.0.0: /@next/swc-linux-x64-musl@13.5.4:
resolution: {integrity: sha512-WTZb2G7B+CTsdigcJVkRxfcAIQj7Lf0ipPNRJ3vlSadU8f0CFGv/ST+sJwF5eSwIe6dxKoX0DG6OljDBaad+rg==} resolution: {integrity: sha512-qVEKFYML/GvJSy9CfYqAdUexA6M5AklYcQCW+8JECmkQHGoPxCf04iMh7CPR7wkHyWWK+XLt4Ja7hhsPJtSnhg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -592,8 +592,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-arm64-msvc@14.0.0: /@next/swc-win32-arm64-msvc@13.5.4:
resolution: {integrity: sha512-7R8/x6oQODmNpnWVW00rlWX90sIlwluJwcvMT6GXNIBOvEf01t3fBg0AGURNKdTJg2xNuP7TyLchCL7Lh2DTiw==} resolution: {integrity: sha512-mDSQfqxAlfpeZOLPxLymZkX0hYF3juN57W6vFHTvwKlnHfmh12Pt7hPIRLYIShk8uYRsKPtMTth/EzpwRI+u8w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@ -601,8 +601,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-ia32-msvc@14.0.0: /@next/swc-win32-ia32-msvc@13.5.4:
resolution: {integrity: sha512-RLK1nELvhCnxaWPF07jGU4x3tjbyx2319q43loZELqF0+iJtKutZ+Lk8SVmf/KiJkYBc7Cragadz7hb3uQvz4g==} resolution: {integrity: sha512-aoqAT2XIekIWoriwzOmGFAvTtVY5O7JjV21giozBTP5c6uZhpvTWRbmHXbmsjZqY4HnEZQRXWkSAppsIBweKqw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@ -610,8 +610,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-x64-msvc@14.0.0: /@next/swc-win32-x64-msvc@13.5.4:
resolution: {integrity: sha512-g6hLf1SUko+hnnaywQQZzzb3BRecQsoKkF3o/C+F+dOA4w/noVAJngUVkfwF0+2/8FzNznM7ofM6TGZO9svn7w==} resolution: {integrity: sha512-cyRvlAxwlddlqeB9xtPSfNSCRy8BOa4wtMo0IuI9P7Y0XT2qpDrpFKRyZ7kUngZis59mPVla5k8X1oOJ8RxDYg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -2740,8 +2740,8 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'} engines: {node: '>=10'}
/eslint-config-next@14.0.0(eslint@8.51.0)(typescript@5.2.2): /eslint-config-next@13.5.4(eslint@8.51.0)(typescript@5.2.2):
resolution: {integrity: sha512-jtXeE+/pGQ3h9n11QyyuPN50kO13GO5XvjU5ZRq6W+XTpOMjyobWmK2s7aowy0FtzA49krJzYzEU9s1RMwoJ6g==} resolution: {integrity: sha512-FzQGIj4UEszRX7fcRSJK6L1LrDiVZvDFW320VVntVKh3BSU8Fb9kpaoxQx0cdFgf3MQXdeSbrCXJ/5Z/NndDkQ==}
peerDependencies: peerDependencies:
eslint: ^7.23.0 || ^8.0.0 eslint: ^7.23.0 || ^8.0.0
typescript: '>=3.3.1' typescript: '>=3.3.1'
@ -2749,7 +2749,7 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@next/eslint-plugin-next': 14.0.0 '@next/eslint-plugin-next': 13.5.4
'@rushstack/eslint-patch': 1.5.1 '@rushstack/eslint-patch': 1.5.1
'@typescript-eslint/parser': 6.7.5(eslint@8.51.0)(typescript@5.2.2) '@typescript-eslint/parser': 6.7.5(eslint@8.51.0)(typescript@5.2.2)
eslint: 8.51.0 eslint: 8.51.0
@ -4214,26 +4214,26 @@ packages:
/natural-compare@1.4.0: /natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
/next-seo@6.1.0(next@14.0.0)(react-dom@18.2.0)(react@18.2.0): /next-seo@6.1.0(next@13.5.4)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-iMBpFoJsR5zWhguHJvsoBDxDSmdYTHtnVPB1ij+CD0NReQCP78ZxxbdL9qkKIf4oEuZEqZkrjAQLB0bkII7RYA==} resolution: {integrity: sha512-iMBpFoJsR5zWhguHJvsoBDxDSmdYTHtnVPB1ij+CD0NReQCP78ZxxbdL9qkKIf4oEuZEqZkrjAQLB0bkII7RYA==}
peerDependencies: peerDependencies:
next: ^8.1.1-canary.54 || >=9.0.0 next: ^8.1.1-canary.54 || >=9.0.0
react: '>=16.0.0' react: '>=16.0.0'
react-dom: '>=16.0.0' react-dom: '>=16.0.0'
dependencies: dependencies:
next: 14.0.0(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0) next: 13.5.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
/next-themes@0.2.1(next@14.0.0)(react-dom@18.2.0)(react@18.2.0): /next-themes@0.2.1(next@13.5.4)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
peerDependencies: peerDependencies:
next: '*' next: '*'
react: '*' react: '*'
react-dom: '*' react-dom: '*'
dependencies: dependencies:
next: 14.0.0(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0) next: 13.5.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
@ -4242,9 +4242,9 @@ packages:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
dev: false dev: false
/next@14.0.0(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0): /next@13.5.4(@babel/core@7.23.2)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-J0jHKBJpB9zd4+c153sair0sz44mbaCHxggs8ryVXSFBuBqJ8XdE9/ozoV85xGh2VnSjahwntBZZgsihL9QznA==} resolution: {integrity: sha512-+93un5S779gho8y9ASQhb/bTkQF17FNQOtXLKAj3lsNgltEcF0C5PMLLncDmH+8X1EnJH1kbqAERa29nRXqhjA==}
engines: {node: '>=18.17.0'} engines: {node: '>=16.14.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@opentelemetry/api': ^1.1.0 '@opentelemetry/api': ^1.1.0
@ -4257,7 +4257,7 @@ packages:
sass: sass:
optional: true optional: true
dependencies: dependencies:
'@next/env': 14.0.0 '@next/env': 13.5.4
'@swc/helpers': 0.5.2 '@swc/helpers': 0.5.2
busboy: 1.6.0 busboy: 1.6.0
caniuse-lite: 1.0.30001549 caniuse-lite: 1.0.30001549
@ -4267,15 +4267,15 @@ packages:
styled-jsx: 5.1.1(@babel/core@7.23.2)(react@18.2.0) styled-jsx: 5.1.1(@babel/core@7.23.2)(react@18.2.0)
watchpack: 2.4.0 watchpack: 2.4.0
optionalDependencies: optionalDependencies:
'@next/swc-darwin-arm64': 14.0.0 '@next/swc-darwin-arm64': 13.5.4
'@next/swc-darwin-x64': 14.0.0 '@next/swc-darwin-x64': 13.5.4
'@next/swc-linux-arm64-gnu': 14.0.0 '@next/swc-linux-arm64-gnu': 13.5.4
'@next/swc-linux-arm64-musl': 14.0.0 '@next/swc-linux-arm64-musl': 13.5.4
'@next/swc-linux-x64-gnu': 14.0.0 '@next/swc-linux-x64-gnu': 13.5.4
'@next/swc-linux-x64-musl': 14.0.0 '@next/swc-linux-x64-musl': 13.5.4
'@next/swc-win32-arm64-msvc': 14.0.0 '@next/swc-win32-arm64-msvc': 13.5.4
'@next/swc-win32-ia32-msvc': 14.0.0 '@next/swc-win32-ia32-msvc': 13.5.4
'@next/swc-win32-x64-msvc': 14.0.0 '@next/swc-win32-x64-msvc': 13.5.4
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros

View File

@ -5,17 +5,17 @@ import { BeatSaverMapData } from "../types/BeatSaverMapData";
import { getValue, setValue, valueExists } from "../utils/redisUtils"; import { getValue, setValue, valueExists } from "../utils/redisUtils";
const BEATSAVER_MAP_API = const BEATSAVER_MAP_API =
env(VARS.HTTP_PROXY) + "/https://api.beatsaver.com/maps/hash/%s"; env(VARS.HTTP_PROXY) + "/https://api.beatsaver.com/maps/hash/%s";
const KEY = "BS_MAP_DATA_"; const KEY = "BS_MAP_DATA_";
function getLatestMapArt(data: BeatSaverMapData) { function getLatestMapArt(data: BeatSaverMapData) {
return data.versions[data.versions.length - 1].coverURL; return data.versions[data.versions.length - 1].coverURL;
} }
type MapData = { type MapData = {
bsr: string; bsr: string;
mapArt: string | undefined; mapArt: string | undefined;
}; };
/** /**
@ -25,34 +25,30 @@ type MapData = {
* @returns The map data * @returns The map data
*/ */
export async function getMapData(hash: string): Promise<MapData | undefined> { export async function getMapData(hash: string): Promise<MapData | undefined> {
const mapHash = hash.replace("custom_level_", "").toLowerCase(); const mapHash = hash.replace("custom_level_", "").toLowerCase();
const key = `${KEY}${mapHash}`; const key = `${KEY}${mapHash}`;
const exists = await valueExists(key); const exists = await valueExists(key);
if (exists) { if (exists) {
const data = await getValue(key); const data = await getValue(key);
return JSON.parse(data); return JSON.parse(data);
} }
const before = Date.now(); const before = Date.now();
const response = await axios.get(BEATSAVER_MAP_API.replace("%s", mapHash), { const response = await axios.get(BEATSAVER_MAP_API.replace("%s", mapHash));
headers: { if (response.status === 404) {
"X-Requested-With": "BeatSaber Overlay", return undefined;
}, }
}); const jsonResponse = response.data;
if (response.status === 404) { const json = {
return undefined; bsr: jsonResponse.id,
} mapArt: getLatestMapArt(jsonResponse),
const jsonResponse = response.data; };
const json = { await setValue(key, JSON.stringify(json), 86400 * 7); // Expire in a week
bsr: jsonResponse.id, console.log(
mapArt: getLatestMapArt(jsonResponse), `[Cache]: Cached BS Map Data for hash ${mapHash} in ${
}; Date.now() - before
await setValue(key, JSON.stringify(json), 86400 * 7); // Expire in a week }ms`
console.log( );
`[Cache]: Cached BS Map Data for hash ${mapHash} in ${ return json;
Date.now() - before
}ms`
);
return json;
} }

View File

@ -2,13 +2,8 @@ import axios from "axios";
import LeaderboardType from "../../consts/LeaderboardType"; import LeaderboardType from "../../consts/LeaderboardType";
export async function getPlayerData(leaderboardType, playerId) { export async function getPlayerData(leaderboardType, playerId) {
const data = await axios.get( const data = await axios.get(
LeaderboardType[leaderboardType].ApiUrl.PlayerData.replace("%s", playerId), LeaderboardType[leaderboardType].ApiUrl.PlayerData.replace("%s", playerId)
{ );
headers: { return data;
"x-requested-with": "BeatSaber Overlay",
},
}
);
return data;
} }

View File

@ -11,97 +11,92 @@ const KEY = "BL_MAP_DATA_";
* @returns * @returns
*/ */
export default async function handler(req, res) { export default async function handler(req, res) {
if (!req.query.hash || !req.query.difficulty || !req.query.characteristic) { if (!req.query.hash || !req.query.difficulty || !req.query.characteristic) {
return res.status(404).json({ return res.status(404).json({
status: 404, status: 404,
message: "Invalid request", message: "Invalid request",
}); });
} }
const mapHash = req.query.hash.replace("custom_level_", "").toLowerCase(); const mapHash = req.query.hash.replace("custom_level_", "").toLowerCase();
const difficulty = req.query.difficulty.replace(" ", ""); const difficulty = req.query.difficulty.replace(" ", "");
const characteristic = req.query.characteristic; const characteristic = req.query.characteristic;
const key = `${KEY}${difficulty}-${characteristic}-${mapHash}`; const key = `${KEY}${difficulty}-${characteristic}-${mapHash}`;
const exists = await valueExists(key); const exists = await valueExists(key);
if (exists) { if (exists) {
const data = await getValue(key); const data = await getValue(key);
const json = JSON.parse(data); const json = JSON.parse(data);
res.setHeader("Cache-Status", "hit"); res.setHeader("Cache-Status", "hit");
return res.status(200).json({ return res.status(200).json({
status: "OK", status: "OK",
difficulty: difficulty, difficulty: difficulty,
stars: json.stars, stars: json.stars,
modifiers: json.modifiers, modifiers: json.modifiers,
passRating: json.passRating, passRating: json.passRating,
accRating: json.accRating, accRating: json.accRating,
techRating: json.techRating, techRating: json.techRating,
}); });
} }
const before = Date.now(); const before = Date.now();
const reesponse = await axios.get( const reesponse = await axios.get(
WebsiteTypes.BeatLeader.ApiUrl.MapData.replace("%h", mapHash), WebsiteTypes.BeatLeader.ApiUrl.MapData.replace("%h", mapHash)
{ );
headers: { if (reesponse.status === 404) {
"X-Requested-With": "BeatSaber Overlay", return res.status(404).json({
}, status: 404,
} message: "Unknown Map Hash",
); });
if (reesponse.status === 404) { }
return res.status(404).json({ const json = reesponse.data;
status: 404, let starCount = undefined;
message: "Unknown Map Hash", let modifiers = undefined;
}); let passRating = undefined;
} let accRating = undefined;
const json = reesponse.data; let techRating = undefined;
let starCount = undefined;
let modifiers = undefined;
let passRating = undefined;
let accRating = undefined;
let techRating = undefined;
for (const diff of json.difficulties) { for (const diff of json.difficulties) {
if ( if (
diff.difficultyName === difficulty && diff.difficultyName === difficulty &&
diff.modeName === characteristic diff.modeName === characteristic
) { ) {
starCount = diff.stars; starCount = diff.stars;
modifiers = diff.modifierValues; modifiers = diff.modifierValues;
passRating = diff.passRating; passRating = diff.passRating;
accRating = diff.accRating; accRating = diff.accRating;
techRating = diff.techRating; techRating = diff.techRating;
} }
} }
if (starCount === undefined) { if (starCount === undefined) {
return res.status(404).json({ return res.status(404).json({
status: 404, status: 404,
message: "Unknown Map Hash", message: "Unknown Map Hash",
}); });
} }
await setValue( await setValue(
key, key,
JSON.stringify({ JSON.stringify({
stars: starCount, stars: starCount,
modifiers: modifiers, modifiers: modifiers,
passRating: passRating, passRating: passRating,
accRating: accRating, accRating: accRating,
techRating: techRating, techRating: techRating,
}) })
); );
console.log( console.log(
`[Cache]: Cached BL Star Count for hash ${mapHash} in ${ `[Cache]: Cached BL Star Count for hash ${mapHash} in ${
Date.now() - before Date.now() - before
}ms` }ms`
); );
res.setHeader("Cache-Status", "miss"); res.setHeader("Cache-Status", "miss");
return res.status(200).json({ return res.status(200).json({
status: "OK", status: "OK",
difficulty: difficulty, difficulty: difficulty,
stars: starCount, stars: starCount,
modifiers: modifiers, modifiers: modifiers,
passRating: passRating, passRating: passRating,
accRating: accRating, accRating: accRating,
techRating: techRating, techRating: techRating,
}); });
} }

View File

@ -12,65 +12,60 @@ const KEY = "SS_MAP_STAR_";
* @returns * @returns
*/ */
export default async function handler(req, res) { export default async function handler(req, res) {
if (!req.query.hash) { if (!req.query.hash) {
return res.status(404).json({ return res.status(404).json({
status: 404, status: 404,
message: "Invalid request", message: "Invalid request",
}); });
} }
const mapHash = req.query.hash.replace("custom_level_", "").toLowerCase(); const mapHash = req.query.hash.replace("custom_level_", "").toLowerCase();
const difficulty = req.query.difficulty.replace(" ", ""); const difficulty = req.query.difficulty.replace(" ", "");
const characteristic = req.query.characteristic; const characteristic = req.query.characteristic;
const key = `${KEY}${difficulty}-${characteristic}-${mapHash}`; const key = `${KEY}${difficulty}-${characteristic}-${mapHash}`;
const exists = await valueExists(key); const exists = await valueExists(key);
if (exists) { if (exists) {
const data = await getValue(key); const data = await getValue(key);
res.setHeader("Cache-Status", "hit"); res.setHeader("Cache-Status", "hit");
return res.status(200).json({ return res.status(200).json({
status: "OK", status: "OK",
difficulty: difficulty, difficulty: difficulty,
stars: Number.parseFloat(data), stars: Number.parseFloat(data),
}); });
} }
const before = Date.now(); const before = Date.now();
const response = await axios.get( const response = await axios.get(
WebsiteTypes.ScoreSaber.ApiUrl.MapData.replace("%h", mapHash).replace( WebsiteTypes.ScoreSaber.ApiUrl.MapData.replace("%h", mapHash).replace(
"%d", "%d",
diffToScoreSaberDiff(difficulty) diffToScoreSaberDiff(difficulty)
), )
{ );
headers: { if (response.status === 404) {
"X-Requested-With": "BeatSaber Overlay", return res.status(404).json({
}, status: 404,
} message: "Unknown Map Hash",
); });
if (response.status === 404) { }
return res.status(404).json({ const json = response.data;
status: 404, let starCount = json.stars;
message: "Unknown Map Hash", if (starCount === undefined) {
}); return res.status(404).json({
} status: 404,
const json = response.data; message: "Unknown Map Hash",
let starCount = json.stars; });
if (starCount === undefined) { }
return res.status(404).json({ await setValue(key, starCount);
status: 404, console.log(
message: "Unknown Map Hash", `[Cache]: Cached SS Star Count for hash ${mapHash} in ${
}); Date.now() - before
} }ms`
await setValue(key, starCount); );
console.log( res.setHeader("Cache-Status", "miss");
`[Cache]: Cached SS Star Count for hash ${mapHash} in ${ return res.status(200).json({
Date.now() - before status: "OK",
}ms` difficulty: difficulty,
); stars: starCount,
res.setHeader("Cache-Status", "miss"); });
return res.status(200).json({
status: "OK",
difficulty: difficulty,
stars: starCount,
});
} }

View File

@ -12,71 +12,86 @@ import { usePlayerDataStore } from "../store/playerDataStore";
import styles from "../styles/overlay.module.css"; import styles from "../styles/overlay.module.css";
export default function Overlay(props) { export default function Overlay(props) {
const query = JSON.parse(props.query); const query = JSON.parse(props.query);
const [setOverlaySettings, mounted, setMounted] = useSettingsStore( const [setOverlaySettings, mounted, setMounted] = useSettingsStore(
(state) => [state.setOverlaySettings, state.mounted, state.setMounted] (state) => [state.setOverlaySettings, state.mounted, state.setMounted]
); );
const updatePlayerData = usePlayerDataStore( const updatePlayerData = usePlayerDataStore(
(state) => state.updatePlayerData (state) => state.updatePlayerData
); );
useEffect(() => { useEffect(() => {
if (!mounted && props.isValidSteamId) { if (!mounted && props.isValidSteamId) {
setMounted(true); setMounted(true);
async function setup() { async function setup() {
await setOverlaySettings(query); await setOverlaySettings(query);
const showSongInfo = useSettingsStore.getState().showSongInfo; const showSongInfo = useSettingsStore.getState().showSongInfo;
const showScoreInfo = useSettingsStore.getState().showScoreInfo; const showScoreInfo = useSettingsStore.getState().showScoreInfo;
if (showSongInfo || (showScoreInfo && typeof window !== "undefined")) { if (showSongInfo || (showScoreInfo && typeof window !== "undefined")) {
await connectClient(); await connectClient();
} }
const showPlayerStats = useSettingsStore.getState().showPlayerStats; const showPlayerStats = useSettingsStore.getState().showPlayerStats;
if (showPlayerStats) { if (showPlayerStats) {
await updatePlayerData(); await updatePlayerData();
} }
} }
setup(); setup();
} }
}, [ }, [
query, query,
props.isValidSteamId, props.isValidSteamId,
setOverlaySettings, setOverlaySettings,
mounted, mounted,
setMounted, setMounted,
updatePlayerData, updatePlayerData,
]); ]);
if (!props.isValidSteamId) { if (!props.isValidSteamId) {
return ( return (
<div className={styles.invalidPlayer}> <div className={styles.invalidPlayer}>
<h1>Invalid Steam ID</h1> <h1>Invalid Steam ID</h1>
<h3>Please check the id field in the url</h3> <h3>Please check the id field in the url</h3>
</div> </div>
); );
} }
return ( return (
<div className={styles.main}> <div className={styles.main}>
<NextSeo title="Overlay" /> <NextSeo title="Overlay" />
<PlayerStats /> <PlayerStats />
<SongInfo /> <SongInfo />
<ScoreStats /> <ScoreStats />
<CutStats /> <CutStats />
</div>
); <p
style={{
fontSize: 50,
position: "absolute",
top: 0,
right: 0,
paddingRight: "10px",
}}
>
This overlay is deprecated use:{" "}
<a href="https://ssr.fascinated.cc/overlay/builder">
https://ssr.fascinated.cc/overlay/builder
</a>
</p>
</div>
);
} }
export async function getServerSideProps(context) { export async function getServerSideProps(context) {
const steamId = context.query.id; const steamId = context.query.id;
const steamIdResponse = await axios.get( const steamIdResponse = await axios.get(
`${process.env.REACT_APP_SITE_URL}/api/validateid?steamid=${steamId}` `${process.env.REACT_APP_SITE_URL}/api/validateid?steamid=${steamId}`
); );
return { return {
props: { props: {
isValidSteamId: steamIdResponse.data.message === "Valid", isValidSteamId: steamIdResponse.data.message === "Valid",
query: JSON.stringify(context.query), query: JSON.stringify(context.query),
}, },
}; };
} }

View File

@ -4,52 +4,48 @@ import Utils from "../utils/utils";
import { useSettingsStore } from "./overlaySettingsStore"; import { useSettingsStore } from "./overlaySettingsStore";
interface PlayerDataState { interface PlayerDataState {
isLoading: boolean; isLoading: boolean;
id: string; id: string;
pp: number; pp: number;
avatar: string; avatar: string;
globalPos: number; globalPos: number;
countryRank: number; countryRank: number;
country: string; country: string;
updatePlayerData: () => void; updatePlayerData: () => void;
} }
export const usePlayerDataStore = create<PlayerDataState>()((set) => ({ export const usePlayerDataStore = create<PlayerDataState>()((set) => ({
isLoading: true, isLoading: true,
id: "", id: "",
pp: 0, pp: 0,
avatar: "", avatar: "",
globalPos: 0, globalPos: 0,
countryRank: 0, countryRank: 0,
country: "", country: "",
updatePlayerData: async () => { updatePlayerData: async () => {
const leaderboardType = useSettingsStore.getState().leaderboardType; const leaderboardType = useSettingsStore.getState().leaderboardType;
const playerId = useSettingsStore.getState().id; const playerId = useSettingsStore.getState().id;
const apiUrl = Utils.getWebsiteApi( const apiUrl = Utils.getWebsiteApi(
leaderboardType leaderboardType
).ApiUrl.PlayerData.replace("%s", playerId); ).ApiUrl.PlayerData.replace("%s", playerId);
const response = await axios.get(apiUrl, { const response = await axios.get(apiUrl);
headers: { if (response.status !== 200) {
"x-requested-with": "BeatSaber Overlay", return;
}, }
}); const data = response.data;
if (response.status !== 200) {
return;
}
const data = response.data;
console.log("Updated player data"); console.log("Updated player data");
set(() => ({ set(() => ({
id: playerId, id: playerId,
isLoading: false, isLoading: false,
pp: data.pp, pp: data.pp,
avatar: data.avatar || data.profilePicture, avatar: data.avatar || data.profilePicture,
globalPos: data.rank, globalPos: data.rank,
countryRank: data.countryRank, countryRank: data.countryRank,
country: data.country, country: data.country,
})); }));
}, },
})); }));

View File

@ -5,134 +5,130 @@ import { getScoreSaberPP } from "../curve/ScoreSaberCurve";
import { useSongDataStore } from "../store/songDataStore"; import { useSongDataStore } from "../store/songDataStore";
export default class Utils { export default class Utils {
/** /**
* Returns the information for the given website type. * Returns the information for the given website type.
* *
* @param {string} website * @param {string} website
* @returns The website type's information. * @returns The website type's information.
*/ */
static getWebsiteApi(website) { static getWebsiteApi(website) {
return LeaderboardType[website]; return LeaderboardType[website];
} }
static openInNewTab(url) { static openInNewTab(url) {
window.open(url, "_blank"); window.open(url, "_blank");
} }
static async isLeaderboardValid(url, steamId) { static async isLeaderboardValid(url, steamId) {
const response = await axios.get(url.replace("%s", steamId), { const response = await axios.get(url.replace("%s", steamId));
headers: { if (response.status === 429) {
"X-Requested-With": "BeatSaber Overlay", return true; // Just assume it's true is we are rate limited
}, }
}); const json = response.data;
if (response.status === 429) { return !!json.pp;
return true; // Just assume it's true is we are rate limited }
}
const json = response.data;
return !!json.pp;
}
static calculatePP(stars, acc, type) { static calculatePP(stars, acc, type) {
if (stars <= 0) { if (stars <= 0) {
return undefined; return undefined;
} }
if (type === "BeatLeader") { if (type === "BeatLeader") {
const leaderboardData = const leaderboardData =
useSongDataStore.getState().mapLeaderboardData.beatLeader; useSongDataStore.getState().mapLeaderboardData.beatLeader;
return getBeatLeaderPP( return getBeatLeaderPP(
acc, acc,
leaderboardData.accRating, leaderboardData.accRating,
leaderboardData.passRating, leaderboardData.passRating,
leaderboardData.techRating leaderboardData.techRating
); );
} }
if (type === "ScoreSaber") { if (type === "ScoreSaber") {
return getScoreSaberPP(acc, stars); return getScoreSaberPP(acc, stars);
} }
return undefined; return undefined;
} }
static calculateModifierBonus() { static calculateModifierBonus() {
const songMods = useSongDataStore.getState().songModifiers; const songMods = useSongDataStore.getState().songModifiers;
const modifierMulipliers = const modifierMulipliers =
useSongDataStore.getState().mapLeaderboardData.beatLeader.modifiers; useSongDataStore.getState().mapLeaderboardData.beatLeader.modifiers;
let bonus = 1; let bonus = 1;
// No Fail // No Fail
if ( if (
songMods.noFail == true && songMods.noFail == true &&
modifierMulipliers.nf < 0 && modifierMulipliers.nf < 0 &&
useSongDataStore.getState().failed useSongDataStore.getState().failed
) { ) {
bonus -= modifierMulipliers.nf; bonus -= modifierMulipliers.nf;
} }
// Speed Modifiers // Speed Modifiers
if (songMods.songSpeed != "Normal") { if (songMods.songSpeed != "Normal") {
if (songMods.songSpeed == "SuperSlow" && modifierMulipliers.ss > 0) { if (songMods.songSpeed == "SuperSlow" && modifierMulipliers.ss > 0) {
bonus -= modifierMulipliers.ss; bonus -= modifierMulipliers.ss;
} }
if (songMods.songSpeed == "Faster" && modifierMulipliers.fs > 0) { if (songMods.songSpeed == "Faster" && modifierMulipliers.fs > 0) {
bonus += modifierMulipliers.fs; bonus += modifierMulipliers.fs;
} }
if (songMods.songSpeed == "SuperFast" && modifierMulipliers.sf > 0) { if (songMods.songSpeed == "SuperFast" && modifierMulipliers.sf > 0) {
bonus += modifierMulipliers.sf; bonus += modifierMulipliers.sf;
} }
} }
// Disappearing Arrows // Disappearing Arrows
if (songMods.disappearingArrows == true && modifierMulipliers.da > 0) { if (songMods.disappearingArrows == true && modifierMulipliers.da > 0) {
bonus += modifierMulipliers.da; bonus += modifierMulipliers.da;
} }
// Ghost Notes // Ghost Notes
if (songMods.ghostNotes == true && modifierMulipliers.gn > 0) { if (songMods.ghostNotes == true && modifierMulipliers.gn > 0) {
toAdd += modifierMulipliers.gn; toAdd += modifierMulipliers.gn;
} }
// No Arrows // No Arrows
if (songMods.noArrows == true && modifierMulipliers.na < 0) { if (songMods.noArrows == true && modifierMulipliers.na < 0) {
bonus -= modifierMulipliers.na; bonus -= modifierMulipliers.na;
} }
// No Bombs // No Bombs
if (songMods.noBombs == true && modifierMulipliers.nb < 0) { if (songMods.noBombs == true && modifierMulipliers.nb < 0) {
bonus -= modifierMulipliers.nb; bonus -= modifierMulipliers.nb;
} }
// No Obstacles // No Obstacles
if (songMods.obstacles == false && modifierMulipliers.no < 0) { if (songMods.obstacles == false && modifierMulipliers.no < 0) {
bonus -= modifierMulipliers.no; bonus -= modifierMulipliers.no;
} }
return bonus; return bonus;
} }
static base64ToArrayBuffer(base64) { static base64ToArrayBuffer(base64) {
return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
} }
static stringToBoolean = (stringValue) => { static stringToBoolean = (stringValue) => {
switch (stringValue?.toLowerCase()?.trim()) { switch (stringValue?.toLowerCase()?.trim()) {
case "true": case "true":
case "yes": case "yes":
case "1": case "1":
return true; return true;
case "false": case "false":
case "no": case "no":
case "0": case "0":
case null: case null:
case undefined: case undefined:
return false; return false;
default: default:
return JSON.parse(stringValue); return JSON.parse(stringValue);
} }
}; };
static capitalizeFirstLetter(string) { static capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
} }
} }