feat: 🎉 initial commit

This commit is contained in:
Walid 2023-04-06 14:18:26 +00:00
commit 4500993c3a
Signed by: Walidoux
GPG Key ID: CCF21881FE8BEBAF
64 changed files with 7826 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
}

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

50
package.json Normal file
View File

@ -0,0 +1,50 @@
{
"name": "@rypidev/rypi-scrapper",
"private": true,
"license": "MIT",
"version": "0.0.0",
"description": "",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@fontsource/press-start-2p": "^4.5.11",
"@tauri-apps/api": "^1.2.0",
"classnames": "^2.3.2",
"fast-xml-parser": "4.1.3",
"framer-motion": "^10.10.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-feather": "^2.0.10"
},
"devDependencies": {
"@tauri-apps/cli": "^1.2.2",
"@types/node": "^18.7.10",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^3.0.0",
"@walidoux/eslint-config": "1.0.3",
"@walidoux/prettier-config": "1.0.2",
"autoprefixer": "10.4.14",
"eslint": "8.36.0",
"postcss": "8.4.21",
"prettier": "2.8.7",
"prettier-plugin-tailwindcss": "0.2.6",
"tailwindcss": "3.3.1",
"typescript": "^4.7.4",
"vite": "^4.0.0"
},
"prettier": "@walidoux/prettier-config",
"eslintConfig": {
"extends": [
"@walidoux/eslint-config"
],
"ignorePatterns": [
"**/*.config.js",
"**/*.config.ts"
]
}
}

3363
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

24
public/Logo.svg Normal file
View File

@ -0,0 +1,24 @@
<svg width="135" height="75" viewBox="0 0 135 75" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g clip-path="url(#clip0_14_41)">
<rect x="17" y="61" width="92" height="14" fill="url(#pattern0)"/>
<rect x="106.166" y="-12" width="44.2825" height="47.5385" transform="rotate(27.5 106.166 -12)" fill="url(#pattern1)"/>
<rect x="0.555054" y="16.0185" width="123.471" height="41.4509" fill="url(#pattern2)"/>
</g>
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_14_41" transform="matrix(0.00469484 0 0 0.0308518 0 -0.00905433)"/>
</pattern>
<pattern id="pattern1" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image1_14_41" transform="matrix(0.0210496 0 0 0.0196078 -0.00519013 0)"/>
</pattern>
<pattern id="pattern2" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image2_14_41" transform="matrix(0.0102041 0 0 0.0303951 0 -0.00151981)"/>
</pattern>
<clipPath id="clip0_14_41">
<rect width="135" height="75" fill="white"/>
</clipPath>
<image id="image0_14_41" width="213" height="33" xlink:href="data:image/png;base64,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"/>
<image id="image1_14_41" width="48" height="51" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAzCAMAAADmSHsbAAABGlBMVEUAAAAAAAAAAAD/zk7/z07Hhhb/4iT+qQ3Rcz7SdD6mZwD/5DD/z0+vZACCPxCAPhDTdj/SdD+3XSfRcz3TdT/7vIv/0E+2XCf/z03/56//6LDcqk6nZQD/z0urfCP/64ulZACAPxG3XCfSd0DRdD/TdUD/zk3/6K+0Xij7uounZwD8vIqmZQCDQBClZwD/0U2zXib/zkz7vIr9vIqlZQD/5677vYv/0EzRcj7/5q+mZgCnaAC3Xie2XyeCQRD8uouoZwDSdEDRdD60XieCQRG0XSe2XSf7uYqCQhLSdj+kZQCCQA//z0y3XSi3XCb8uoyBPxH/5rD8vIuAPhHTdECnZgD/0Ev/zk+zXSb/6LH/5a7/57CmZAD/57FeylhiAAAAAnRSTlMAmf9A5tgAAAJPSURBVHhe7dHVjtxAEIbRcTUZhxmWmZnCzMz0/q+R+tu2NO7synsZRWnLd99xdcmVv/L8P5Sda+dGG6N1pK9HkCutYaLoGoR0pPD5KNIQhkp7nfVWsKKy+yilIfjRplSQ0jox6FOBXbSiKwnh64nBBHvy7ZnQFQA3MhA5MHaeXZ7A3F7pxDapMaxR2z9Cs6ORCxAkBgJHJSqvyfPQO4BUNkFN5ajRe8Q5eYVlCBkm4GGX1rUPhx0IAIz5E+S9NkSN9tebt1ICQJcATvlN2CJvtB89tmTTrjA7O5rhYVNAoWaT5379Qc8S9Djop4CyIEG+dIY8qO81QbhHjPWnge2xA907CxaJgnqwvNZjkvUv3q8XQZL2RB9bDSIGy36wtrDWy/rq3u0TyvtcoF9s328sAviBHwJmfXWhigmVaYB+u+0vZyDcsXHWV5u1Jy5QEZ3UguCZvwSws8v9zIztXzF4XXtTAGS4p85bv73UajHYFTFxan9Zr8lgobZeBCoyCXVqQSNo3SAh4okAiAHGc7jS/B1nB4oiOqwG/t0t0RUTISUR9wD1+vx8tbm9WQQVj4POw425WMZ9yWc/jiVhBQZMxk8PXOB5dPBcCDmRckX2RcyEsl7ud1+GzpUgaHU1PFqRYqXfjWUfE4i4F1J2x+HqsQNAiAbvNsJTXAhEMJOC6dzGcEj5j3bJ8PxT+PkLSJbLrYvjb8gvBR7I94vwFCTNw8EP5JcDCFxs8BME+dHwl83d3iXnIMgHJXmBlOYucfJy4ublxM3LiZuXk8q/eX4DFpU/BKxEv6oAAAAASUVORK5CYII="/>
<image id="image2_14_41" width="98" height="33" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGIAAAAhCAYAAAAiaX7MAAAAAXNSR0IArs4c6QAABVxJREFUaEPtms9vE0cUx98k+EfsSJVMewiKFJo6FNlInHJrztxoz1z6DxTBoaeqZ04cQOkdVap6L/9CcmpyJBGqUFpQSpAoFkS2wTaw6I0zm9m38+ONMwuO1L0EvLtv3vd95v3YtQUAQKvVSnZ2dqDdbsNwOIROpwODwQB6vR6eDj7q9Xp6T6/XE8RAEmxwwhtarRacAl0yPgIh1Go12NragtXVVej3+3B4eCj/IhDuoQcf76lUKoB21bG/v48LFgKBro1rLi0tyfVPiS4hkiTJBadztwaDRwlUmmLiv5dvn4VqtQrlcjndlbu7uymYpz9UWfbP3nyd3vPizlzOn+GTElRWZmH0+AyUlgWM9hJYXu9Dt9vN7aFp04Ub6KjqCJkRjUYDNjY24KSOLqy/hoPrc5kAX/mtne7K7e1tGRxTQF3gz/3yBmzgRv9WoLxShtF+CSrLZ2Cw9xZKiyP45s7nMO26Vu7Nynhg+RbNZjNRu/akIGgGIRj9EGLcLmKCePtsHsoXyjA6qMIXPz2C57eaUFp4A6u35tJsnFZdl36vqV4sxOLioixNf373H6tUuHauKSPwevx8bW0NNjc3CwFRuViDz77/J2X+6tfzcOnnd/L/06wrA6LRaEgQD671YWF9mdubLdftAEBbnju4vpeCRRA4COilqXHjS9ZaWTs+/8brv7z3Cr76cZyN06wrA6Jer0sQ3e4SJH89ZQUneTjMXTdz9XjUfX+/DjNXz+eAYK3HI0la8P7+8Q42LSouluXH4sI56Nz9GxCc6R68jvqDa8/PP5b3T5supVX3UfYINVJicDgglGgM/Djg9mcNdV7VaAXCB0NB8IEwQcDPZr5+mbL9mLo4G4yCSKemEBCmTFDBoBmh73KkjyVGB2GDoUNwgeBAUGv4NlgsXVwQ6DtmuhDpOM/PCJezx5THWUIPLggKQQby4VCWOb0s2SDg9Xo2cEDE1MUBofs++21fhYoHguOsDMJRuTLVfAymtgMypQODbINAbYVA8IGIrcsHgvoeBILrLKfLawt7azh3XR0gzQYXCK79EF0uEKYNxAYR6qwrI1CQvrD+3ok2VO66DgjOIYRr31dy1XmlywaC0c/spSnU2ZCdg1OpDQR3XVrKSDZYQXDtc/RwQDBLqRlEEc4aMkJWDiUmdDdlpjJtXD2CnNrWs61oXVSDCwIZLPIginb2KIDqOwonCJ8QNVWR3pOxrUAUpUvfYDoIju9aFmdBcJ9cQ9KWpi8BYc0KjhAMrgVCJiOK1GUCMYHvbhAmgxEgoAn9W7tcVnCFkHJntGsqebF0mXoEPjzS7KP9TJ23Tk2+h6YCIKQtwtUrGEIoBGtGxIZgK01Kj8132jMz04UCEctZw3MD/f5aZyszw9TwMhdpLxzJOEz3Sc5eLF36QrrGEN+9IGI666jfpuQyBs4GgQoxGMzYi6mLA8KVCYa+yZuaJilJjCbt3MGhQlwgTHX7JJos2tJs5vjuzYgYDlIbzMxIdzB9W2obPfXxz5cRsXVxX9eYfJcNLfu63j++xhLAgGEEYYOAfn0KECYA+qChbyIXBOq/sVnHCr4jM0xNOwfCBaEoEK5AW+JifJ3igxANBDr87o/jH5Bx4HGnHFtNd7xfcoL1fS1ra74eTblnIddTvCpHus2gJ2ubM0WCsD0JU1+KKE2ezWILh3H01i82fd9y6kCYRHyE0uR65nFOfPQkx//M62huqdHr6CT3kFccaa9T/9BtMkTIrLdNTfh5RB+dGWFai+t/YT8MDqivORCcfkOucYKYwJ4NrhfEpGt9ChDe1xyBYmLbC4Vw0o00/ll+oOj/Ly8oAh8A/keUfCzV468AAAAASUVORK5CYII="/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
public/icons/game.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
public/icons/picture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
public/images/Gamedata.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

4
src-tauri/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/

3400
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

23
src-tauri/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "rypi-scrapper"
version = "0.0.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.2", features = [] }
[dependencies]
tauri = { version = "1.2", features = ["fs-create-dir", "fs-exists", "fs-read-dir", "fs-write-file", "http-all", "shell-all", "window-close", "window-maximize", "window-minimize", "window-unmaximize"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem
# DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]

3
src-tauri/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

8
src-tauri/src/main.rs Normal file
View File

@ -0,0 +1,8 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

78
src-tauri/tauri.conf.json Normal file
View File

@ -0,0 +1,78 @@
{
"build": {
"beforeDevCommand": "yarn dev",
"beforeBuildCommand": "yarn build",
"devPath": "http://localhost:1420",
"distDir": "../dist",
"withGlobalTauri": false
},
"package": {
"productName": "rypi-scrapper",
"version": "0.0.0"
},
"tauri": {
"allowlist": {
"window": {
"close": true,
"minimize": true,
"maximize": true,
"unmaximize": true
},
"os": {
"all": false
},
"fs": {
"createDir": true,
"exists": true,
"readDir": true,
"writeFile": true,
"scope": ["$DOWNLOAD/**/*"]
},
"shell": {
"all": true,
"execute": true,
"sidecar": true,
"open": true,
"scope": [
{
"name": "download-habbo-downloader",
"cmd": "npm",
"args": ["install", "-g", "habbo-downloader"]
}
]
},
"http": {
"all": true,
"request": true,
"scope": ["https://www.habbo.*/*", "https://images.habbo.*/*"]
}
},
"bundle": {
"active": true,
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "com.tauri.dev",
"targets": "all"
},
"security": {
"csp": null
},
"updater": {
"active": false
},
"windows": [
{
"decorations": false,
"center": true,
"resizable": true,
"width": 800,
"height": 500
}
]
}
}

53
src/App.tsx Normal file
View File

@ -0,0 +1,53 @@
import { useState } from 'react'
import { Image, Window, Button, Downloader } from './components'
import { fetchGameData } from './utils/fetchGameData'
import {
GameAssetsDownloader,
GameDataDownloader
} from './config/GameDownloader'
const Main: React.FC = () => {
const [error, setError] = useState('')
const [response, setResponse] = useState('')
const [loading, setLoading] = useState(false)
const callback = (message: string, error = false): void => {
if (error) return setError(message)
if (message === 'COMPLETED') setLoading(false)
else setResponse(message)
}
return (
<Window>
<span className='mb-20 text-white'>I would like to:</span>
<ul className='flex gap-x-8'>
<Downloader content={GameDataDownloader}>
<Image src='/images/Gamedata.png' className='w-[400px]' />
<Button
value='Download Gamedata'
icon={<Image src='/icons/game.png' size={22} />}
className='download-button border-gamedata-secondary bg-gamedata-primary shadow-gamedata-primary/20'
handler={async () => {
return await fetchGameData('com', callback)
}}
/>
</Downloader>
<Downloader content={GameAssetsDownloader}>
<Image src='/images/GameAssets.png' className='w-[400px]' />
<Button
value='Download GameAssets'
icon={<Image src='/icons/picture.png' icon />}
className='download-button border-gameAssets-secondary bg-gameAssets-primary shadow-gameAssets-primary/40'
/>
</Downloader>
</ul>
</Window>
)
}
export default Main

View File

@ -0,0 +1,27 @@
import classNames from 'classnames'
interface ButtonProps extends React.ComponentPropsWithoutRef<'button'> {
icon: JSX.Element
value: string
className?: string
handler?: () => void
}
export const Button: React.FC<ButtonProps> = ({
icon,
value,
className,
handler
}) => {
return (
<button
onClick={handler}
className={classNames(
className,
'flex items-center justify-center gap-x-5'
)}>
{icon}
<span className='uppercase'>{value}</span>
</button>
)
}

View File

@ -0,0 +1 @@
export * from './Button'

View File

@ -0,0 +1,68 @@
import classNames from 'classnames'
import { AnimatePresence, motion } from 'framer-motion'
import { useState } from 'react'
export interface IDownloaderContent {
title: string
features: string[]
}
interface DownloaderProps {
className?: string
children: React.ReactNode
content: IDownloaderContent
}
export const Downloader: React.FC<DownloaderProps> = ({
className,
children,
content
}) => {
const [activeContent, setActiveContent] = useState(false)
const handleActiveContent = (): void => {
return setActiveContent(!activeContent)
}
return (
<li
className={classNames(
className,
'relative rounded-xl shadow-red-500 hover:shadow-2xl'
)}
onMouseEnter={handleActiveContent}
onMouseLeave={handleActiveContent}>
{children}
<AnimatePresence>
{activeContent && (
<motion.ul
initial={{ opacity: 0 }}
animate={{ opacity: 1, transition: { staggerChildren: 0.5 } }}
exit={{ opacity: 0 }}
className='pointer-events-none absolute left-0 top-0 flex h-full w-full flex-col items-center justify-center rounded-xl bg-black/70 pb-5 text-[12px] text-white'>
<motion.h1
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className='mb-3 text-[16px]'>
{content.title}
</motion.h1>
{content.features.map((feature) => {
return (
<motion.span
initial={{ opacity: 0, y: 50, scale: 0.75 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 50, scale: 0.75 }}
key={feature}>
{feature}
</motion.span>
)
})}
</motion.ul>
)}
</AnimatePresence>
</li>
)
}

View File

@ -0,0 +1 @@
export * from './Downloader'

View File

@ -0,0 +1,19 @@
import classNames from 'classnames'
interface ImageProps extends React.ComponentPropsWithoutRef<'img'> {
size?: number
icon?: boolean
}
export const Image: React.FC<ImageProps> = ({ size, icon, ...rest }) => {
return (
<img
{...rest}
draggable={false}
style={{ imageRendering: Boolean(icon) ? 'pixelated' : 'unset' }}
className={classNames(rest.className, 'select-none')}
height={size ?? rest.height}
width={size ?? rest.width}
/>
)
}

View File

@ -0,0 +1 @@
export * from './Image'

View File

@ -0,0 +1,38 @@
import { Maximize, Minus, X } from 'react-feather'
import { appWindow } from '@tauri-apps/api/window'
import { Image } from '../Image'
export const TitleBar: React.FC = () => {
return (
<nav className='flex h-[37.5px] items-center justify-between bg-[#1f1f1f] text-white'>
<Image src='/Logo.svg' size={60} className='ml-3 p-1' />
<ul className='flex h-full'>
<li
onClick={async () => {
return await appWindow.minimize()
}}
className='grid h-full w-14 cursor-pointer place-items-center transition-colors duration-[10ms] hover:bg-[#2a2a2a]'>
<Minus />
</li>
<li
onClick={async () => {
return (await appWindow.isMaximized())
? await appWindow.unmaximize()
: await appWindow.maximize()
}}
className='grid h-full w-14 cursor-pointer place-items-center transition-colors duration-[10ms] hover:bg-[#2a2a2a]'>
<Maximize size={20} />
</li>
<li
onClick={async () => {
return await appWindow.close()
}}
className='grid h-full w-14 cursor-pointer place-items-center transition-colors duration-[10ms] hover:bg-red-500'>
<X />
</li>
</ul>
</nav>
)
}

View File

@ -0,0 +1 @@
export * from './TitleBar'

View File

@ -0,0 +1,19 @@
import { Fragment } from 'react'
import { TitleBar } from '../TitleBar/TitleBar'
interface WindowProps {
children: React.ReactNode
}
export const Window: React.FC<WindowProps> = ({ children }) => {
return (
<Fragment>
<TitleBar />
<main className='flex h-screen w-screen flex-col items-center justify-center overflow-hidden bg-[#242424] p-10'>
{children}
</main>
</Fragment>
)
}

View File

@ -0,0 +1 @@
export * from './Window'

4
src/components/index.ts Normal file
View File

@ -0,0 +1,4 @@
export * from './Window'
export * from './Image'
export * from './Button'
export * from './Downloader'

62
src/config/ENDPOINTS.ts Normal file
View File

@ -0,0 +1,62 @@
import { getClient, ResponseType } from '@tauri-apps/api/http'
import type { DomainTypes, GameEndPointsTypes } from '../types'
const PROD_VERSION_REGEX = /(production-[^/]+)/im
const STABLE_PROD_VERSION = 'PRODUCTION-202303282207-162719871'
export let PROD_VERSION: string | undefined
const HABBO_URL = (domain: DomainTypes): string => {
return `https://www.habbo.${domain}`
}
const HABBO_IMAGES = `https://images.habbo.com/gordon/${
PROD_VERSION ?? STABLE_PROD_VERSION
}`
export const client = await getClient()
await client
.get(`${HABBO_URL('com')}/gamedata/external_variables/0`, {
responseType: ResponseType.Text
})
.then(({ data }) => {
return (PROD_VERSION = (data as string).match(PROD_VERSION_REGEX)?.[0])
})
export const GAME_ENDPOINTS: GameEndPointsTypes = (domain) => {
return [
{
src: `${HABBO_URL(domain)}/gamedata/figuredata/0`,
convert: 'XML',
fileName: 'FigureData'
}
/* {
src: `${HABBO_IMAGES}/figuremap.xml`,
convert: 'XML',
fileName: 'FigureMap'
},
{
src: `${HABBO_URL(domain)}/gamedata/furnidata_json/0`,
fileName: 'FurniData'
},
{
src: `${HABBO_URL(domain)}/gamedata/productdata_json/0`,
fileName: 'ProductData'
},
{
src: `${HABBO_IMAGES}/effectmap.xml`,
convert: 'XML',
fileName: 'EffectMap'
},
{
src: `${HABBO_URL(domain)}/gamedata/external_variables/0`,
convert: 'TXT',
fileName: 'ExternalTexts'
} */
]
}
export const ASSETS_ENDPOINTS = (domain: DomainTypes): string[] => {
return [`${HABBO_URL(domain)}/`]
}

View File

@ -0,0 +1,17 @@
import type { IDownloaderContent } from '../components'
export const GameDataDownloader: IDownloaderContent = {
title: 'Converts and bundles:',
features: ['XML/TXT to minified JSON files', 'Converts SWF files to JSV']
}
export const GameAssetsDownloader: IDownloaderContent = {
title: 'Fetches PNG/JPEG:',
features: [
'Badges + Badgeparts',
'Album + Recepetion images',
'Catalogue + Furni icons',
'Habbo Web Promo + Articles',
'MP3 Sounds machine'
]
}

12
src/main.tsx Normal file
View File

@ -0,0 +1,12 @@
import React from 'react'
import { createRoot } from 'react-dom/client'
import './styles.css'
import '@fontsource/press-start-2p'
import App from './App'
createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
)

View File

@ -0,0 +1,3 @@
export const convertTXT = (data: string) => {
console.log(data)
}

194
src/mapping/convertXML.ts Normal file
View File

@ -0,0 +1,194 @@
import type {
IEffectMap,
IEffectMapLibrary,
IFigureData,
IFigureDataColor,
IFigureDataHiddenLayer,
IFigureDataPalette,
IFigureDataPart,
IFigureDataSet,
IFigureDataSetType,
IFigureMap,
IFigureMapLibrary,
IFigureMapLibraryPart
} from '../types'
export const convertXML = (
XML: any,
url: string
): IFigureData | IFigureMap | IEffectMap => {
if (url.includes('figuredata')) {
const output: IFigureData = { palettes: [], setTypes: [] }
for (const paletteXML of XML.figuredata.colors.palette) {
const palette: IFigureDataPalette = { id: 0, color: [] }
if (paletteXML.id !== undefined) palette.id = paletteXML.id
if (paletteXML.color !== undefined) {
for (const colorXML of paletteXML.color) {
const color: IFigureDataColor = {
id: 0,
index: 0,
club: 0,
selectable: false,
hexCode: ''
}
const hexColor = String(colorXML['#text' as keyof IFigureDataColor])
if (colorXML.id !== undefined) color.id = colorXML.id
if (colorXML.index !== undefined) color.index = colorXML.index
if (colorXML.club !== undefined) color.club = colorXML.club
if (colorXML.selectable !== undefined)
color.selectable = colorXML.selectable === 0 ? true : false
if (hexColor !== undefined) color.hexCode = hexColor
palette.color.push(color)
}
}
output.palettes.push(palette)
}
for (const setTypeXML of XML.figuredata.sets.settype) {
const settype: IFigureDataSetType = {
type: '',
paletteId: 0,
mandatoryF0: false,
mandatoryF1: false,
mandatoryM0: false,
mandatoryM1: false,
sets: []
}
if (setTypeXML.type !== undefined) settype.type = setTypeXML.type
if (setTypeXML.paletteId !== undefined)
settype.paletteId = setTypeXML.paletteId
if (setTypeXML.mandatoryF0 !== undefined)
settype.mandatoryF0 = setTypeXML.mandatoryF0
if (setTypeXML.mandatoryF1 !== undefined)
settype.mandatoryF1 = setTypeXML.mandatoryF1
if (setTypeXML.mandatoryM0 !== undefined)
settype.mandatoryM0 = setTypeXML.mandatoryM0
if (setTypeXML.mandatoryM1 !== undefined)
settype.mandatoryM1 = setTypeXML.mandatoryM1
if (setTypeXML.sets !== undefined) {
for (const setXML of setTypeXML.sets) {
const setType: IFigureDataSet = {
id: 0,
gender: 'U',
club: 0,
colorable: false,
selectable: false,
preselectable: false,
sellable: false,
parts: [],
hiddenLayers: []
}
if (setXML.id !== undefined) setType.id = setXML.id
if (setXML.gender !== undefined) setType.gender = setXML.gender
if (setXML.club !== undefined) setType.club = setXML.club
if (setXML.colorable !== undefined)
setType.colorable = setXML.colorable === 0 ? true : false
if (setXML.selectable !== undefined)
setType.selectable = setXML.selectable
if (setXML.preselectable !== undefined)
setType.preselectable = setXML.preselectable
if (setXML.sellable !== undefined) setType.sellable = setXML.sellable
if (setXML.parts !== undefined) {
for (const partXML of setXML.parts) {
const part: IFigureDataPart = {
id: 0,
type: '',
colorable: false,
index: 0,
colorindex: 0
}
if (partXML.id !== undefined) part.id = partXML.id
if (partXML.type !== undefined) part.type = partXML.type
if (partXML.colorable !== undefined)
part.colorable = partXML.colorable
if (partXML.index !== undefined) part.index = partXML.index
if (partXML.colorindex !== undefined)
part.colorindex = partXML.colorindex
setType.parts.push(part)
}
}
if (setXML.hiddenLayers !== undefined) {
for (const hiddenLayerXML of setXML.hiddenLayers) {
const hiddenLayer: IFigureDataHiddenLayer = { partType: '' }
if (hiddenLayerXML.partType !== undefined)
hiddenLayer.partType = hiddenLayerXML.partType
setType.hiddenLayers?.push(hiddenLayer)
}
}
settype.sets.push(setType)
}
}
output.setTypes.push(setTypeXML)
}
return output
} else if (url.includes('figuremap')) {
const output: IFigureMap = { libraries: [] }
for (const libraryXML of XML.map.lib as IFigureMapLibrary[]) {
const library: IFigureMapLibrary = { id: '', revision: 0, part: [] }
if (libraryXML.id !== undefined) library.id = libraryXML.id
if (libraryXML.revision !== undefined)
library.revision = libraryXML.revision
if (Array.isArray(libraryXML.part)) {
for (const libraryPart of libraryXML.part) {
const libraryPartXML: IFigureMapLibraryPart = { id: 0, type: '' }
if (libraryPart.id !== undefined) libraryPartXML.id = libraryPart.id
if (libraryPart.type !== undefined)
libraryPartXML.type = libraryPart.type
library.part.push(libraryPartXML)
}
} else {
const libraryPart = libraryXML.part as unknown as IFigureMapLibraryPart
library.part.push({ id: libraryPart.id, type: libraryPart.type })
}
output.libraries.push(library)
}
return output
} else {
const output: IEffectMap = { effects: [] }
for (const libraryXML of XML.map.effect as IEffectMapLibrary[]) {
const library: IEffectMapLibrary = {
id: 0,
lib: '',
type: '',
revision: 0
}
if (libraryXML.id !== undefined) library.id = libraryXML.id
if (libraryXML.lib !== undefined) library.lib = libraryXML.lib
if (libraryXML.type !== undefined) library.type = libraryXML.type
if (libraryXML.revision !== undefined)
library.revision = libraryXML.revision
output.effects.push(library)
}
return output
}
}

2
src/mapping/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './convertXML'
export * from './convertTXT'

25
src/styles.css Normal file
View File

@ -0,0 +1,25 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
#root {
font-family: 'Press Start 2P';
}
@layer utilities {
.retro-text-shadow {
text-shadow: 0px 1.5px 0px #000000, -1px 3px 0px rgba(0, 0, 0, 0.54);
}
.center-x-axis {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
}
@layer components {
.download-button {
@apply retro-text-shadow center-x-axis center-x-axis -bottom-4 z-10 h-12 w-10/12 border-2 text-xs text-white shadow-2xl transition-all hover:-translate-x-1/2 hover:scale-105 hover:duration-200 active:-translate-x-1/2 active:scale-95 active:opacity-80 active:shadow-none active:duration-75;
}
}

30
src/types/Converters.d.ts vendored Normal file
View File

@ -0,0 +1,30 @@
import type {
IEffectMapLibrary,
IFigureDataPalette,
IFigureDataSetType,
IFigureMapLibrary,
IFurni,
IProduct
} from './SubConverters'
export interface IFigureData {
palettes: IFigureDataPalette[]
setTypes: IFigureDataSetType[]
}
export interface IFigureMap {
libraries: IFigureMapLibrary[]
}
export interface IFurniData {
roomitemtypes: { furnitype: IFurni }
wallitemtypes: { furnitype: IFurni }
}
export interface IEffectMap {
effects: IEffectMapLibrary[]
}
export interface IProductData {
productData: { product: IProduct }
}

10
src/types/Domain.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
export type DomainTypes =
| 'com.br'
| 'com.tr'
| 'com'
| 'de'
| 'es'
| 'fi'
| 'fr'
| 'it'
| 'nl'

3
src/types/Endpoint.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export type GameEndPointsTypes = (
domain: DomainTypes
) => Array<{ src: string; convert?: 'TXT' | 'XML'; fileName: string }>

98
src/types/SubConverters.d.ts vendored Normal file
View File

@ -0,0 +1,98 @@
export interface IFurni {
id: number
classname: string
revision: number
category: string
defaultdir: number
xdim: number
ydim: number
partcolors: { color: string[] }
name: string
description: string
adurl: string
offerid: number
buyout: boolean
rentofferid: number
rentbuyout: boolean
bc: boolean
excludeddynamic: boolean
customparams: string
specialtype: number
canstandon: boolean
cansiton: boolean
canlayon: boolean
furniline: string
environment: string
rare: boolean
}
export interface IFigureDataColor {
id: number
index: number
club: number // must be changed, either 0, 1, 2
selectable: boolean
hexCode: string
}
export interface IFigureDataPalette {
id: number
color: IFigureDataColor[]
}
export interface IFigureDataPart {
id: number
type: string // must be changed
colorable: boolean // must be changed
index: number
colorindex: number
}
export interface IFigureDataHiddenLayer {
partType: string // must be changed
}
export interface IFigureDataSet {
id: number
gender: 'M' | 'F' | 'U'
club: number // 0, 1, 2
colorable: boolean // must be changed
selectable: boolean // must be changed
preselectable: boolean // must be changed
sellable?: boolean // must be changed
parts: IFigureDataPart[]
hiddenLayers?: IFigureDataHiddenLayer[]
}
export interface IFigureDataSetType {
type: string // must be changed
paletteId: number
mandatoryF0: boolean // 0, 1
mandatoryF1: boolean // 0, 1
mandatoryM0: boolean // 0, 1
mandatoryM1: boolean // 0, 1
sets: IFigureDataSet[]
}
export interface IFigureMapLibraryPart {
id: number
type: string
}
export interface IFigureMapLibrary {
id: string
revision: number
part: IFigureMapLibraryPart[]
}
export interface IEffectMapLibrary {
id: number
lib: string
type: string
revision: number
}
export interface IProduct {
color: string
name: string
description: string
}

4
src/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export * from './Converters'
export * from './SubConverters'
export * from './Endpoint'
export * from './Domain'

View File

@ -0,0 +1,45 @@
import { ResponseType } from '@tauri-apps/api/http'
import { XMLParser } from 'fast-xml-parser'
import { GAME_ENDPOINTS, client } from '../config/ENDPOINTS'
import type { DomainTypes } from '../types/Domain'
import { convertXML } from '../mapping'
import { parseData } from './parseData'
export const fetchGameData = async (
domain: DomainTypes,
callback: (message: string, error: boolean) => void,
assetsOption = false
): Promise<void> => {
if (!assetsOption) {
await Promise.all(
GAME_ENDPOINTS(domain).map(async (endpoint) => {
await client
.get(endpoint.src, { responseType: ResponseType.Text })
.then(async ({ data }) => {
switch (endpoint.convert) {
case 'XML':
const convertedData = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: ''
}).parse(data as string)
const XML2JSON = convertXML(convertedData, endpoint.src)
console.log(XML2JSON)
return await parseData(endpoint.fileName, XML2JSON)
}
})
.catch((error) => {
console.error(error)
return callback(error, true)
})
})
)
} else {
/* ASSETS_ENDPOINTS(domain).map((endpoint) => {
client.get()
}) */
}
}

19
src/utils/parseData.ts Normal file
View File

@ -0,0 +1,19 @@
import { downloadDir } from '@tauri-apps/api/path'
import { createDir, exists, writeTextFile } from '@tauri-apps/api/fs'
import { PROD_VERSION } from '../config/ENDPOINTS'
export const parseData = async (
fileName: string,
fileContent: string | object
): Promise<void> => {
const outputDir = (await downloadDir()).concat(String(PROD_VERSION))
const fileDir = outputDir.concat(`/${fileName}.json`)
if (!(await exists(outputDir))) await createDir(outputDir)
await writeTextFile(
fileDir,
typeof fileContent === 'object' ? JSON.stringify(fileContent) : fileContent
)
}

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

13
tailwind.config.js Normal file
View File

@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.tsx'],
theme: {
extend: {
colors: {
gamedata: { primary: '#FF7A00', secondary: '#FFA978' },
gameAssets: { primary: '#423FD9', secondary: '#625FFA' }
}
}
},
plugins: []
}

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

9
tsconfig.node.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

26
vite.config.ts Normal file
View File

@ -0,0 +1,26 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(async () => ({
plugins: [react()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors
clearScreen: false,
// tauri expects a fixed port, fail if that port is not available
server: {
port: 1420,
strictPort: true
},
// to make use of `TAURI_DEBUG` and other env variables
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
envPrefix: ['VITE_', 'TAURI_'],
build: {
// Tauri supports es2021
target: process.env.TAURI_PLATFORM == 'windows' ? 'chrome105' : 'safari13',
// don't minify for debug builds
minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
// produce sourcemaps for debug builds
sourcemap: !!process.env.TAURI_DEBUG
}
}))