├── .gitignore ├── 404.html ├── LICENSE ├── README.md ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── app.html ├── apple-touch-icon.png ├── assets ├── JSON │ └── errors.json ├── css │ ├── bulma.css │ ├── cropbox.css │ ├── fonts.css │ ├── frame.css │ └── main.css ├── error │ ├── 403.html │ ├── 404.html │ ├── 503.html │ ├── failed.html │ ├── proxy_detection.html │ └── unknown.html ├── fonts │ └── montserrat │ │ ├── Montserrat-Black.woff │ │ ├── Montserrat-Black.woff2 │ │ ├── Montserrat-BlackItalic.woff │ │ ├── Montserrat-BlackItalic.woff2 │ │ ├── Montserrat-Bold.woff │ │ ├── Montserrat-Bold.woff2 │ │ ├── Montserrat-BoldItalic.woff │ │ ├── Montserrat-BoldItalic.woff2 │ │ ├── Montserrat-ExtraBold.woff │ │ ├── Montserrat-ExtraBold.woff2 │ │ ├── Montserrat-ExtraBoldItalic.woff │ │ ├── Montserrat-ExtraBoldItalic.woff2 │ │ ├── Montserrat-ExtraLight.woff │ │ ├── Montserrat-ExtraLight.woff2 │ │ ├── Montserrat-ExtraLightItalic.woff │ │ ├── Montserrat-ExtraLightItalic.woff2 │ │ ├── Montserrat-Italic.woff │ │ ├── Montserrat-Italic.woff2 │ │ ├── Montserrat-Light.woff │ │ ├── Montserrat-Light.woff2 │ │ ├── Montserrat-LightItalic.woff │ │ ├── Montserrat-LightItalic.woff2 │ │ ├── Montserrat-Medium.woff │ │ ├── Montserrat-Medium.woff2 │ │ ├── Montserrat-MediumItalic.woff │ │ ├── Montserrat-MediumItalic.woff2 │ │ ├── Montserrat-Regular.woff │ │ ├── Montserrat-Regular.woff2 │ │ ├── Montserrat-SemiBold.woff │ │ ├── Montserrat-SemiBold.woff2 │ │ ├── Montserrat-SemiBoldItalic.woff │ │ ├── Montserrat-SemiBoldItalic.woff2 │ │ ├── Montserrat-Thin.woff │ │ ├── Montserrat-Thin.woff2 │ │ ├── Montserrat-ThinItalic.woff │ │ ├── Montserrat-ThinItalic.woff2 │ │ └── stylesheet.css ├── img │ ├── background.jpeg │ ├── cutout.svg │ ├── footer-cutout.svg │ ├── halfheight-cutout.svg │ └── logo.png ├── js │ ├── analytics.js │ ├── api.js │ ├── cookie.js │ ├── cropbox.js │ ├── depend.js │ ├── gamemanger.js │ ├── inject.js │ ├── main.js │ ├── nav.js │ ├── notification.js │ ├── page.js │ ├── profile.js │ └── tab.js ├── pages │ ├── cropper.html │ ├── featured.html │ ├── profile.html │ └── settings.html └── public │ ├── gs │ ├── emulator.html │ ├── game.html │ └── gameframe.html │ └── pages │ └── comments.html ├── auth.html ├── config.json ├── development └── contribute.md ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── home.html ├── index.html ├── logout.html ├── package-lock.json ├── package.json ├── server ├── index.js └── mirror.js └── site.webmanifest /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /server/mirror 3 | .gitpod.yml -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 404 | GameHub 15 | 16 | 17 | 18 | 19 |
20 |

Error 404

21 |

The page you tried to access could not be found. Home

22 |
23 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

GameHub

5 | 6 |

A privacy centered, feature packed and easily deployable game site.

7 |

Looking for the link? Play gamehub here.

8 | 9 |
10 |

Join our discord server for announcements, links and updates.

11 | 12 | 13 | 14 | 15 |

Deploy GameHub

16 | 17 |

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

26 | 27 |

Host the GameHub API Proxy

28 | 29 |

30 | You can find the open source code for the api proxy here. 31 |

32 | 33 |

Contributors

34 | 35 |

36 | 37 |

38 | -------------------------------------------------------------------------------- /android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/android-chrome-192x192.png -------------------------------------------------------------------------------- /android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/android-chrome-512x512.png -------------------------------------------------------------------------------- /app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GameHub 15 | 16 | 17 | 18 | 134 | 135 |
136 |
137 |
138 |
139 |
140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/JSON/errors.json: -------------------------------------------------------------------------------- 1 | [403, 404, 408, 503, "failed", "unknown"] -------------------------------------------------------------------------------- /assets/css/cropbox.css: -------------------------------------------------------------------------------- 1 | @import url('/assets/css/fonts.css'); 2 | 3 | * { 4 | font-family: montserrat; 5 | } 6 | 7 | .imageBox { 8 | position: relative; 9 | height: 400px; 10 | width: 400px; 11 | border: 1px solid #aaa; 12 | overflow: hidden; 13 | background-repeat: no-repeat; 14 | cursor: move; 15 | } 16 | 17 | .imageBox .thumbBox { 18 | position: absolute; 19 | top: 50%; 20 | left: 50%; 21 | width: 200px; 22 | height: 200px; 23 | margin-top: -100px; 24 | margin-left: -100px; 25 | box-sizing: border-box; 26 | border: 1px solid rgb(102, 102, 102); 27 | box-shadow: 0 0 0 1000px rgba(0, 0, 0, 0.5); 28 | } 29 | 30 | .imageBox .spinner { 31 | color: #000; 32 | position: absolute; 33 | top: 0; 34 | left: 0; 35 | bottom: 0; 36 | right: 0; 37 | text-align: center; 38 | line-height: 400px; 39 | user-select: none; 40 | -moz-user-select: none; 41 | -khtml-user-select: none; 42 | -webkit-user-select: none; 43 | -o-user-select: none; 44 | } -------------------------------------------------------------------------------- /assets/css/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Montserrat-LightItalic'; 3 | src: url('/assets/fonts/montserrat/Montserrat-LightItalic.woff2') format('woff2'), 4 | url('/assets/fonts/montserrat/Montserrat-LightItalic.woff') format('woff'); 5 | font-weight: 300; 6 | font-style: italic; 7 | font-display: swap; 8 | } 9 | 10 | @font-face { 11 | font-family: 'Montserrat-ExtraLightItalic'; 12 | src: url('/assets/fonts/montserrat/Montserrat-ExtraLightItalic.woff2') format('woff2'), 13 | url('/assets/fonts/montserrat/Montserrat-ExtraLightItalic.woff') format('woff'); 14 | font-weight: 200; 15 | font-style: italic; 16 | font-display: swap; 17 | } 18 | 19 | @font-face { 20 | font-family: 'Montserrat-Italic'; 21 | src: url('/assets/fonts/montserrat/Montserrat-Italic.woff2') format('woff2'), 22 | url('/assets/fonts/montserrat/Montserrat-Italic.woff') format('woff'); 23 | font-weight: normal; 24 | font-style: italic; 25 | font-display: swap; 26 | } 27 | 28 | @font-face { 29 | font-family: 'Montserrat-Light'; 30 | src: url('/assets/fonts/montserrat/Montserrat-Light.woff2') format('woff2'), 31 | url('/assets/fonts/montserrat/Montserrat-Light.woff') format('woff'); 32 | font-weight: 300; 33 | font-style: normal; 34 | font-display: swap; 35 | } 36 | 37 | @font-face { 38 | font-family: 'Montserrat'; 39 | src: url('/assets/fonts/montserrat/Montserrat-Regular.woff2') format('woff2'), 40 | url('/assets/fonts/montserrat/Montserrat-Regular.woff') format('woff'); 41 | font-weight: normal; 42 | font-style: normal; 43 | font-display: swap; 44 | } 45 | 46 | @font-face { 47 | font-family: 'Montserrat-SemiBoldItalic'; 48 | src: url('/assets/fonts/montserrat/Montserrat-SemiBoldItalic.woff2') format('woff2'), 49 | url('/assets/fonts/montserrat/Montserrat-SemiBoldItalic.woff') format('woff'); 50 | font-weight: 600; 51 | font-style: italic; 52 | font-display: swap; 53 | } 54 | 55 | @font-face { 56 | font-family: 'Montserrat-Medium'; 57 | src: url('/assets/fonts/montserrat/Montserrat-Medium.woff2') format('woff2'), 58 | url('/assets/fonts/montserrat/Montserrat-Medium.woff') format('woff'); 59 | font-weight: 500; 60 | font-style: normal; 61 | font-display: swap; 62 | } 63 | 64 | @font-face { 65 | font-family: 'Montserrat-Thin'; 66 | src: url('/assets/fonts/montserrat/Montserrat-Thin.woff2') format('woff2'), 67 | url('/assets/fonts/montserrat/Montserrat-Thin.woff') format('woff'); 68 | font-weight: 100; 69 | font-style: normal; 70 | font-display: swap; 71 | } 72 | 73 | @font-face { 74 | font-family: 'Montserrat-SemiBold'; 75 | src: url('/assets/fonts/montserrat/Montserrat-SemiBold.woff2') format('woff2'), 76 | url('/assets/fonts/montserrat/Montserrat-SemiBold.woff') format('woff'); 77 | font-weight: 600; 78 | font-style: normal; 79 | font-display: swap; 80 | } 81 | 82 | @font-face { 83 | font-family: 'Montserrat-MediumItalic'; 84 | src: url('/assets/fonts/montserrat/Montserrat-MediumItalic.woff2') format('woff2'), 85 | url('/assets/fonts/montserrat/Montserrat-MediumItalic.woff') format('woff'); 86 | font-weight: 500; 87 | font-style: italic; 88 | font-display: swap; 89 | } 90 | 91 | @font-face { 92 | font-family: 'Montserratt-BlackItalic'; 93 | src: url('/assets/fonts/montserrat/Montserrat-BlackItalic.woff2') format('woff2'), 94 | url('/assets/fonts/montserrat/Montserrat-BlackItalic.woff') format('woff'); 95 | font-weight: 900; 96 | font-style: italic; 97 | font-display: swap; 98 | } 99 | 100 | @font-face { 101 | font-family: 'Montserrat-ThinItalic'; 102 | src: url('/assets/fonts/montserrat/Montserrat-ThinItalic.woff2') format('woff2'), 103 | url('/assets/fonts/montserrat/Montserrat-ThinItalic.woff') format('woff'); 104 | font-weight: 100; 105 | font-style: italic; 106 | font-display: swap; 107 | } 108 | 109 | @font-face { 110 | font-family: 'Montserrat-Bold'; 111 | src: url('/assets/fonts/montserrat/Montserrat-Bold.woff2') format('woff2'), 112 | url('/assets/fonts/montserrat/Montserrat-Bold.woff') format('woff'); 113 | font-weight: bold; 114 | font-style: normal; 115 | font-display: swap; 116 | } 117 | 118 | @font-face { 119 | font-family: 'Montserrat-BoldItalic'; 120 | src: url('/assets/fonts/montserrat/Montserrat-BoldItalic.woff2') format('woff2'), 121 | url('/assets/fonts/montserrat/Montserrat-BoldItalic.woff') format('woff'); 122 | font-weight: bold; 123 | font-style: italic; 124 | font-display: swap; 125 | } 126 | 127 | @font-face { 128 | font-family: 'Montserrat-Black'; 129 | src: url('/assets/fonts/montserrat/Montserrat-Black.woff2') format('woff2'), 130 | url('/assets/fonts/montserrat/Montserrat-Black.woff') format('woff'); 131 | font-weight: 900; 132 | font-style: normal; 133 | font-display: swap; 134 | } 135 | 136 | @font-face { 137 | font-family: 'Montserrat-ExtraBoldItalic'; 138 | src: url('/assets/fonts/montserrat/Montserrat-ExtraBoldItalic.woff2') format('woff2'), 139 | url('/assets/fonts/montserrat/Montserrat-ExtraBoldItalic.woff') format('woff'); 140 | font-weight: bold; 141 | font-style: italic; 142 | font-display: swap; 143 | } 144 | 145 | @font-face { 146 | font-family: 'Montserrat-ExtraLight'; 147 | src: url('/assets/fonts/montserrat/Montserrat-ExtraLight.woff2') format('woff2'), 148 | url('/assets/fonts/montserrat/Montserrat-ExtraLight.woff') format('woff'); 149 | font-weight: 200; 150 | font-style: normal; 151 | font-display: swap; 152 | } 153 | 154 | @font-face { 155 | font-family: 'Montserrat-ExtraBold'; 156 | src: url('/assets/fonts/montserrat/Montserrat-ExtraBold.woff2') format('woff2'), 157 | url('/assets/fonts/montserrat/Montserrat-ExtraBold.woff') format('woff'); 158 | font-weight: bold; 159 | font-style: normal; 160 | font-display: swap; 161 | } -------------------------------------------------------------------------------- /assets/css/frame.css: -------------------------------------------------------------------------------- 1 | @media (prefers-color-scheme:light) { 2 | :root { 3 | --bg-color: #fff; 4 | --text-color: #242425; 5 | } 6 | } 7 | 8 | @media (prefers-color-scheme:dark) { 9 | :root { 10 | --bg-color: #242425; 11 | --text-color: #fff; 12 | } 13 | } 14 | 15 | html, 16 | body { 17 | color: var(--text-color); 18 | position: absolute; 19 | margin: 0px; 20 | width: 100vw; 21 | height: 100vh; 22 | overflow: hidden; 23 | background-color: var(--bg-color); 24 | } 25 | 26 | * { 27 | user-select: none; 28 | } 29 | 30 | .pageContent { 31 | margin: 1%; 32 | display: flex; 33 | flex-direction: row; 34 | width: 99%; 35 | height: 96%; 36 | } 37 | 38 | .mainContainer { 39 | float: left; 40 | width: 99%; 41 | height: 99%; 42 | background-position: fixed; 43 | border-radius: 20px; 44 | background-color: var(--bg-color); 45 | box-shadow: 0px 8px 8px rgba(0, 0, 0, 0.616); 46 | } 47 | 48 | .sideContainer { 49 | margin: 2vw; 50 | width: min-content; 51 | height: min-content; 52 | } 53 | 54 | .recomended { 55 | padding: 20px; 56 | text-align: center; 57 | font-size: 30px; 58 | font-family: montserrat-black; 59 | width: min-content; 60 | margin: auto; 61 | } 62 | 63 | h1 { 64 | text-align: center; 65 | color: var(--text-color); 66 | } 67 | 68 | .mainGame { 69 | margin: 0px; 70 | border-radius: 20px 20px 0px 0px; 71 | width: 100%; 72 | height: 90%; 73 | z-index: 10; 74 | } 75 | 76 | .bottom { 77 | position: absolute; 78 | width: 16em; 79 | bottom: 20px; 80 | } 81 | 82 | .gameThumb { 83 | display: inline-block; 84 | vertical-align: middle; 85 | margin: 10px; 86 | cursor: pointer; 87 | border-radius: 10px; 88 | box-shadow: 0px 8px 8px rgba(0, 0, 0, 0.616); 89 | background-color: var(--bg-color); 90 | user-select: none; 91 | transition: 0.5s; 92 | object-fit: cover; 93 | object-position: center; 94 | width: 200px; 95 | height: 200px; 96 | padding: 10px; 97 | } 98 | 99 | .gameThumb.failed img { 100 | padding: 10px; 101 | background: #fff; 102 | } 103 | 104 | .gameThumb>img { 105 | width: 180px; 106 | height: 180px; 107 | padding: 0px; 108 | user-select: none; 109 | transition: 0.5s; 110 | object-fit: cover; 111 | object-position: center; 112 | border-radius: 5px; 113 | } 114 | 115 | .gameThumb:hover { 116 | background-color: rgb(0, 0, 0, 0.7); 117 | } 118 | 119 | .gameBar { 120 | display: flex; 121 | justify-content: space-between; 122 | flex-direction: row; 123 | height: 10%; 124 | background-color: var(--bg-color); 125 | border-radius: 0px 0px 20px 20px; 126 | padding-left: 20px; 127 | } 128 | 129 | .logo { 130 | height: 70%; 131 | padding: 1%; 132 | cursor: pointer; 133 | margin: auto; 134 | margin-right: 7px 135 | } 136 | 137 | .logo:hover { 138 | animation: shake 0.5s; 139 | animation-iteration-count: 1; 140 | } 141 | 142 | .icon { 143 | float: right; 144 | position: relative; 145 | font-size: 20px; 146 | display: flex; 147 | flex-direction: row; 148 | margin: auto; 149 | margin-left: 20px; 150 | color: var(--text-color); 151 | padding: 20px; 152 | border-radius: 100%; 153 | box-shadow: 0px 8px 8px rgba(0, 0, 0, 0.616); 154 | } 155 | 156 | span.icon>p { 157 | margin: auto; 158 | } 159 | 160 | [data-attr="thumbs-up"]>svg, 161 | [data-attr="thumbs-down"]>svg { 162 | font-size: 30px; 163 | } 164 | 165 | [data-func="open-comments"]>svg { 166 | font-size: 30px; 167 | } 168 | 169 | .left { 170 | display: flex; 171 | flex-direction: row; 172 | } 173 | 174 | .right { 175 | float: right; 176 | margin-right: 50px; 177 | display: flex; 178 | flex-direction: row; 179 | width: min-content; 180 | } 181 | 182 | .gameTitle { 183 | width: 500px; 184 | text-overflow: ellipsis; 185 | white-space: nowrap; 186 | overflow: hidden; 187 | margin: auto; 188 | color: var(--text-color); 189 | font-family: montserrat-black; 190 | font-size: 150%; 191 | } 192 | 193 | .gamebar-item { 194 | float: right; 195 | padding-right: 5px; 196 | display: flex; 197 | flex-direction: row; 198 | width: 50px; 199 | height: 100%; 200 | cursor: pointer; 201 | } 202 | 203 | .commentContainer { 204 | margin: 0.5%; 205 | margin-left: 48em; 206 | position: absolute; 207 | width: 33.3em; 208 | height: 53em; 209 | background: var(--bg-color); 210 | border-radius: 0px 20px 20px 0px; 211 | box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.616); 212 | } 213 | 214 | .commentFrame { 215 | width: 100%; 216 | height: 100%; 217 | border-radius: 0px 20px 20px 0px; 218 | } 219 | 220 | @keyframes shake { 221 | 0% { 222 | transform: translate(1px, 1px) rotate(0deg); 223 | } 224 | 225 | 10% { 226 | transform: translate(-1px, -2px) rotate(-1deg); 227 | } 228 | 229 | 20% { 230 | transform: translate(-3px, 0px) rotate(1deg); 231 | } 232 | 233 | 30% { 234 | transform: translate(3px, 2px) rotate(0deg); 235 | } 236 | 237 | 40% { 238 | transform: translate(1px, -1px) rotate(1deg); 239 | } 240 | 241 | 50% { 242 | transform: translate(-1px, 2px) rotate(-1deg); 243 | } 244 | 245 | 60% { 246 | transform: translate(-3px, 1px) rotate(0deg); 247 | } 248 | 249 | 70% { 250 | transform: translate(3px, 1px) rotate(-1deg); 251 | } 252 | 253 | 80% { 254 | transform: translate(-1px, -1px) rotate(1deg); 255 | } 256 | 257 | 90% { 258 | transform: translate(1px, 2px) rotate(0deg); 259 | } 260 | 261 | 100% { 262 | transform: translate(1px, -2px) rotate(-1deg); 263 | } 264 | } -------------------------------------------------------------------------------- /assets/css/main.css: -------------------------------------------------------------------------------- 1 | @import url('https://site-assets.fontawesome.com/releases/v6.2.0/css/all.css'); 2 | @import url('/assets/css/cropbox.css'); 3 | @import url('/assets/css/bulma.css'); 4 | @import url('/assets/css/fonts.css'); 5 | 6 | @media (prefers-color-scheme:light) { 7 | :root { 8 | --bg-color: #fff; 9 | --text-color: #242425; 10 | } 11 | } 12 | 13 | @media (prefers-color-scheme:dark) { 14 | :root { 15 | --bg-color: #242425; 16 | --text-color: #fff; 17 | } 18 | 19 | .hero.is-link { 20 | filter: brightness(90%); 21 | } 22 | 23 | .navbar.is-link { 24 | filter: brightness(90%); 25 | } 26 | } 27 | 28 | .navbar.is-link>.container { 29 | filter: drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.616)); 30 | } 31 | 32 | .navbar.is-link .navTitle { 33 | text-shadow: 0px 0px 20px rgba(0, 0, 0, 0.616); 34 | } 35 | 36 | html { 37 | background: var(--bg-color); 38 | } 39 | 40 | body, 41 | html { 42 | font-family: 'Montserrat'; 43 | width: 100vw; 44 | color: var(--text-color); 45 | background-color: var(--bg-color); 46 | user-select: none; 47 | } 48 | 49 | * { 50 | font-family: 'Montserrat'; 51 | } 52 | 53 | body h1 { 54 | font-family: 'Montserrat-Black'; 55 | } 56 | 57 | .content h1 { 58 | color: var(--text-color); 59 | } 60 | 61 | .content p { 62 | max-width: 50vw; 63 | } 64 | 65 | .notifications { 66 | top: 1%; 67 | width: 20%; 68 | position: fixed; 69 | z-index: 9999; 70 | left: 50%; 71 | transform: translate(-50%, -50%); 72 | transition: .5s; 73 | height: 2%; 74 | min-width: 250px; 75 | max-width: 500px; 76 | } 77 | 78 | .notifications>.notification { 79 | padding: 15px; 80 | border-radius: 20px; 81 | margin-top: 10px; 82 | transition: .5s; 83 | cursor: pointer; 84 | min-width: 250px; 85 | max-width: 500px; 86 | padding-left: 20px; 87 | } 88 | 89 | .notifications>.notification.error { 90 | background: rgba(171, 61, 222, 0.801); 91 | color: #fff; 92 | transition: .5s; 93 | inline-size: 100%; 94 | overflow-wrap: break-word; 95 | } 96 | 97 | a:not(.button) { 98 | color: #ac3cde; 99 | text-decoration: none; 100 | position: relative; 101 | } 102 | 103 | a:not(.button):hover { 104 | color: #ac3cde; 105 | } 106 | 107 | a:not(.button)::before { 108 | background-color: #ac3cde; 109 | bottom: -0.5px; 110 | content: ''; 111 | display: block; 112 | height: 1px; 113 | left: 0; 114 | position: absolute; 115 | transform: scaleX(0); 116 | transform-origin: left top; 117 | transition: transform 0.3s ease 0s; 118 | width: 100%; 119 | } 120 | 121 | a:not(.button):hover::before, 122 | a:not(.button):focus::before { 123 | transform: scaleX(1); 124 | } 125 | 126 | .navbar a { 127 | color: #fff; 128 | } 129 | 130 | .navbar a:hover { 131 | color: #fff; 132 | } 133 | 134 | .navbar a::before { 135 | display: none; 136 | } 137 | 138 | .hero.is-link:not(.is-halfheight) { 139 | background-image: url('/assets/img/background.jpeg'); 140 | mask-image: url('/assets/img/cutout.svg'); 141 | -webkit-mask-image: url('/assets/img/cutout.svg'); 142 | mask-size: 100vw; 143 | -webkit-mask-size: 100vw; 144 | mask-repeat: no-repeat; 145 | -webkit-mask-repeat: no-repeat; 146 | min-height: calc(100vw * calc(102 / 300)); 147 | margin-bottom: 50px; 148 | } 149 | 150 | .hero.is-link.is-halfheight { 151 | background-image: url('/assets/img/background.jpeg'); 152 | mask-image: url('/assets/img/halfheight-cutout.svg'); 153 | -webkit-mask-image: url('/assets/img/halfheight-cutout.svg'); 154 | mask-size: 100vw; 155 | -webkit-mask-size: 100vw; 156 | mask-repeat: no-repeat; 157 | -webkit-mask-repeat: no-repeat; 158 | min-height: calc(100vw * calc(245 / 900)); 159 | margin-bottom: 50px; 160 | } 161 | 162 | body.modalpage { 163 | background-image: url('/assets/img/background.jpeg'); 164 | background-position: 0; 165 | text-align: center; 166 | overflow: hidden; 167 | width: 100vw; 168 | background-repeat: no-repeat; 169 | background-size: cover; 170 | height: 100vh; 171 | margin: 0px; 172 | } 173 | 174 | body>div.main.hidden+body::-webkit-scrollbar-track { 175 | display: none; 176 | } 177 | 178 | body>div.main.hidden+body::-webkit-scrollbar { 179 | display: none; 180 | } 181 | 182 | body.modalpage::-webkit-scrollbar { 183 | display: none; 184 | } 185 | 186 | body.modalpage::-webkit-scrollbar-track { 187 | display: none; 188 | } 189 | 190 | .title { 191 | font-family: montserrat-black; 192 | } 193 | 194 | :disabled { 195 | cursor: not-allowed; 196 | } 197 | 198 | html.noscroll { 199 | overflow: hidden; 200 | } 201 | 202 | body.noscroll { 203 | overflow: hidden; 204 | } 205 | 206 | body.noscroll::-webkit-scrollbar { 207 | display: none; 208 | } 209 | 210 | body.noscroll::-webkit-scrollbar-track { 211 | display: none; 212 | } 213 | 214 | ::-webkit-scrollbar { 215 | width: 16px; 216 | } 217 | 218 | ::-webkit-scrollbar-track { 219 | background: rgba(171, 61, 222, 0.2); 220 | } 221 | 222 | ::-webkit-scrollbar-thumb { 223 | background: #ab3dde; 224 | border: 4px solid transparent; 225 | background-clip: content-box; 226 | border-radius: 8px; 227 | } 228 | 229 | ::-webkit-scrollbar-corner { 230 | background: transparent; 231 | } 232 | 233 | .navbar { 234 | background-image: url('/assets/img/background.jpeg'); 235 | background-attachment: fixed; 236 | background-position: center; 237 | background-repeat: no-repeat; 238 | background-size: cover; 239 | } 240 | 241 | .navbar-item { 242 | color: #fff; 243 | } 244 | 245 | .navbar a.navbar-item.is-dropdown:hover { 246 | background-color: #ab3dde; 247 | } 248 | 249 | footer { 250 | background-image: url('/assets/img/background.jpeg'); 251 | background-attachment: fixed; 252 | background-position: center; 253 | background-repeat: no-repeat; 254 | background-size: cover; 255 | user-select: none; 256 | padding: 4% 5% 4% 5%; 257 | mask-image: url('/assets/img/footer-cutout.svg'); 258 | -webkit-mask-image: url('/assets/img/footer-cutout.svg'); 259 | mask-size: 100vw; 260 | -webkit-mask-size: 100vw; 261 | mask-repeat: no-repeat; 262 | -webkit-mask-repeat: no-repeat; 263 | color: #fff; 264 | margin-top: 50px; 265 | text-shadow: 0px 0px 20px rgba(0, 0, 0, 0.616); 266 | } 267 | 268 | footer p { 269 | text-shadow: 0px 0px 20px rgba(0, 0, 0, 0.616); 270 | } 271 | 272 | footer h1 { 273 | color: #fff; 274 | } 275 | 276 | footer::before { 277 | display: block; 278 | content: ''; 279 | height: 270px; 280 | position: fixed; 281 | bottom: 0; 282 | left: 0; 283 | width: 100vw; 284 | z-index: -1; 285 | background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.8) 100%); 286 | } 287 | 288 | footer .socials i { 289 | margin: 10px; 290 | margin-left: 5px; 291 | margin-right: 5px; 292 | font-size: 20px; 293 | cursor: pointer; 294 | } 295 | 296 | footer .socials a { 297 | color: #fff; 298 | } 299 | 300 | footer .socials a:hover { 301 | color: #fff; 302 | } 303 | 304 | footer .socials a::before { 305 | display: none; 306 | } 307 | 308 | footer .socials i:is(:first-child) { 309 | margin-left: 0px; 310 | } 311 | 312 | footer a { 313 | font-family: 'Montserrat-Bold'; 314 | } 315 | 316 | footer .right { 317 | text-align: right; 318 | } 319 | 320 | footer .title:not(:last-child) { 321 | display: flex; 322 | margin: 0px; 323 | } 324 | 325 | footer .title img { 326 | width: 40px; 327 | height: 40px; 328 | margin-left: 10px; 329 | filter: drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.616)); 330 | } 331 | 332 | span.Icon>svg { 333 | margin: auto; 334 | font-size: 20px; 335 | } 336 | 337 | span.Icon { 338 | margin: auto; 339 | } 340 | 341 | .avatarContainer { 342 | display: flex; 343 | } 344 | 345 | .userid { 346 | top: 35%; 347 | left: 62%; 348 | position: absolute; 349 | } 350 | 351 | button.button.is-rounded.is-link.has-right-sharp { 352 | border-bottom-left-radius: 0px; 353 | border-top-left-radius: 0px; 354 | } 355 | 356 | div.select.is-link.is-rounded.sortSelect>select { 357 | border-bottom-left-radius: 0px; 358 | border-top-left-radius: 0px; 359 | border-left: none; 360 | box-shadow: none; 361 | } 362 | 363 | input.is-link.is-rounded.searchBar { 364 | border-bottom-right-radius: 0px; 365 | border-top-right-radius: 0px; 366 | border-right-color: transparent; 367 | text-align: left; 368 | padding-left: 40px; 369 | } 370 | 371 | input.is-link.is-rounded.searchBar { 372 | box-shadow: none; 373 | } 374 | 375 | input.is-link.is-rounded.searchBar { 376 | box-shadow: none; 377 | } 378 | 379 | .usernameForm { 380 | padding: 20px; 381 | display: flex; 382 | width: 400px; 383 | } 384 | 385 | input.input.is-link.is-rounded[data-attr='username'] { 386 | border-bottom-right-radius: 0px; 387 | border-top-right-radius: 0px; 388 | } 389 | 390 | .author { 391 | white-space: nowrap; 392 | overflow: hidden; 393 | text-overflow: ellipsis; 394 | width: 20px; 395 | } 396 | 397 | .comment-subject { 398 | word-wrap: break-word; 399 | } 400 | 401 | .comment { 402 | width: 300px; 403 | padding: 10px; 404 | margin-top: 20px; 405 | margin-bottom: 20px; 406 | overflow: hidden; 407 | } 408 | 409 | .closeModalPannel { 410 | margin: 5px; 411 | position: fixed; 412 | cursor: pointer; 413 | left: 80%; 414 | } 415 | 416 | .closeModalPannel>i { 417 | font-size: 30px; 418 | color: #000; 419 | } 420 | 421 | [data-attr='comment-subject'] { 422 | resize: none; 423 | } 424 | 425 | .post-comment { 426 | padding: 10px; 427 | bottom: 0px; 428 | position: sticky; 429 | background: #fff; 430 | } 431 | 432 | .is-right { 433 | float: right; 434 | } 435 | 436 | .comments { 437 | padding: 2px; 438 | margin: 20px; 439 | overflow: hidden; 440 | } 441 | 442 | .card { 443 | margin: auto; 444 | background-color: var(--bg-color); 445 | border-radius: 10px; 446 | box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.616); 447 | } 448 | 449 | .username-container>i { 450 | font-size: 20px; 451 | padding: 3px; 452 | cursor: pointer; 453 | } 454 | 455 | .profile#app { 456 | position: absolute; 457 | top: 50%; 458 | left: 50%; 459 | transform: translate(-50%, -50%); 460 | margin: auto; 461 | } 462 | 463 | .new-avatar { 464 | width: 200px; 465 | height: 200px; 466 | background-position: center; 467 | background-repeat: no-repeat; 468 | background-size: cover; 469 | outline: 2px solid #fff; 470 | border-radius: 10px; 471 | } 472 | 473 | .navbar-item.has-dropdown { 474 | padding-right: 10px; 475 | } 476 | 477 | .select select { 478 | background: transparent; 479 | color: var(--text-color); 480 | } 481 | 482 | .control i { 483 | color: var(--text-color); 484 | } 485 | 486 | .input { 487 | background: transparent; 488 | color: var(--text-color); 489 | } 490 | 491 | .input::placeholder { 492 | color: var(--text-color); 493 | } 494 | 495 | .avatar-small { 496 | margin: auto; 497 | border-radius: 10px; 498 | background-color: #fff; 499 | width: 40px; 500 | height: 40px; 501 | outline: 2px solid #fff; 502 | background-position: center; 503 | background-repeat: no-repeat; 504 | background-size: cover; 505 | box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.616); 506 | background-image: url('/assets/img/logo.png'); 507 | } 508 | 509 | .database_nav { 510 | width: 100vw; 511 | top: 50px; 512 | background: var(--bg-color); 513 | z-index: 4; 514 | padding: 20px; 515 | display: flex; 516 | flex-direction: row; 517 | margin: auto; 518 | transition: .3s; 519 | position: sticky; 520 | position: -webkit-sticky; 521 | } 522 | 523 | .database_nav.shadowed { 524 | box-shadow: 0px 5px 8px rgba(0, 0, 0, 0.616); 525 | } 526 | 527 | .searchErr { 528 | margin-top: 85px; 529 | margin-bottom: 85px; 530 | text-align: center; 531 | } 532 | 533 | .searchErr h1 { 534 | font-size: 40px; 535 | } 536 | 537 | .sort { 538 | display: inline-block; 539 | margin: auto; 540 | margin-left: 0px; 541 | } 542 | 543 | .search { 544 | text-align: center; 545 | width: 500px; 546 | display: inline-block; 547 | margin: auto; 548 | margin-right: 0px; 549 | } 550 | 551 | .search input { 552 | width: 500px; 553 | text-align: center; 554 | } 555 | 556 | .search i { 557 | position: absolute; 558 | z-index: 1; 559 | padding: 12px; 560 | } 561 | 562 | .content:not(:last-child) { 563 | min-height: 32vh; 564 | margin: 100px; 565 | margin-top: 0px; 566 | margin-bottom: 0px; 567 | user-select: text; 568 | width: 100%; 569 | } 570 | 571 | .heroIcon { 572 | margin: auto; 573 | width: 40px; 574 | height: 40px; 575 | } 576 | 577 | .heroTitle { 578 | font-family: montserrat-black; 579 | font-size: 100px; 580 | text-shadow: 0px 0px 20px rgba(0, 0, 0, 0.616); 581 | } 582 | 583 | .navTitle { 584 | margin: auto; 585 | margin-left: 10px; 586 | cursor: pointer; 587 | } 588 | 589 | .err { 590 | color: red; 591 | margin: 15px; 592 | } 593 | 594 | .box { 595 | color: #000; 596 | position: absolute; 597 | top: 50%; 598 | left: 50%; 599 | transform: translate(-50%, -50%); 600 | width: 400px; 601 | box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.616) 602 | } 603 | 604 | .innerGame { 605 | width: 100%; 606 | height: 100%; 607 | background: var(--bg-color); 608 | } 609 | 610 | .games { 611 | display: flex; 612 | justify-content: center; 613 | align-items: center; 614 | flex-wrap: wrap; 615 | } 616 | 617 | .game { 618 | display: inline-block; 619 | vertical-align: middle; 620 | margin: 10px; 621 | position: relative; 622 | border-radius: 10px; 623 | box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.616); 624 | background-color: var(--bg-color); 625 | user-select: none; 626 | transition: 0.5s; 627 | object-fit: cover; 628 | object-position: center; 629 | width: 200px; 630 | height: 200px; 631 | cursor: pointer; 632 | padding: 10px; 633 | } 634 | 635 | .game.failed img { 636 | padding: 10px; 637 | background: #fff; 638 | } 639 | 640 | .game>img { 641 | width: 180px; 642 | height: 180px; 643 | padding: 0px; 644 | user-select: none; 645 | transition: 0.5s; 646 | object-fit: cover; 647 | object-position: center; 648 | border-radius: 5px; 649 | } 650 | 651 | .game>p { 652 | transition: 0.5s; 653 | text-align: center; 654 | border-radius: 0px 0px 10px 10px; 655 | padding: 10px; 656 | bottom: 0px; 657 | right: 0px; 658 | position: absolute; 659 | width: 100%; 660 | background-color: rgb(0, 0, 0, 0.7); 661 | color: white; 662 | text-overflow: ellipsis; 663 | } 664 | 665 | .game:hover { 666 | background-color: rgb(0, 0, 0, 0.5); 667 | } 668 | 669 | .gameFrame { 670 | background-color: var(--bg-color); 671 | width: 100%; 672 | height: 100vh; 673 | } 674 | 675 | .hidden { 676 | transition: 0.5s; 677 | display: none; 678 | } 679 | 680 | .avatar { 681 | margin: auto; 682 | transition: 0.5s; 683 | border-radius: 15px; 684 | width: 80px; 685 | height: 80px; 686 | background-position: center; 687 | background-repeat: no-repeat; 688 | background-size: cover; 689 | } 690 | 691 | .username { 692 | text-align: center; 693 | font-size: 30px; 694 | cursor: pointer; 695 | } 696 | 697 | .uploadIcon { 698 | transition: 0.5s; 699 | position: absolute; 700 | background-color: rgb(0, 0, 0, 0.5); 701 | color: #fff; 702 | font-size: 20px; 703 | border-radius: 15px; 704 | padding: 30px; 705 | padding-bottom: 20px; 706 | } 707 | 708 | .hero { 709 | background-attachment: fixed; 710 | background-position: center; 711 | background-repeat: no-repeat; 712 | background-size: cover; 713 | } 714 | 715 | .frame_500x500 { 716 | width: 500px; 717 | height: 500px; 718 | display: flex; 719 | justify-content: center; 720 | align-items: center; 721 | user-select: none; 722 | margin: auto; 723 | } 724 | 725 | .modalstyle_error { 726 | position: absolute; 727 | top: 50%; 728 | left: 50%; 729 | transform: translate(-50%, -50%); 730 | } 731 | 732 | .is-center { 733 | text-align: center; 734 | } 735 | 736 | .loading { 737 | background-color: #ededed; 738 | background: linear-gradient(100deg, 739 | rgba(255, 255, 255, 0) 40%, 740 | rgba(255, 255, 255, .5) 50%, 741 | rgba(255, 255, 255, 0) 60%) #ededed; 742 | background-size: 200% 100%; 743 | background-position-x: 180%; 744 | animation: 1s loading-in-out infinite; 745 | } 746 | 747 | .loading img, 748 | .loading h4, 749 | .loading .description { 750 | background-color: #ededed; 751 | background: linear-gradient(100deg, 752 | rgba(255, 255, 255, 0) 40%, 753 | rgba(255, 255, 255, .5) 50%, 754 | rgba(255, 255, 255, 0) 60%) #ededed; 755 | background-size: 200% 100%; 756 | background-position-x: 180%; 757 | animation: 1s loading-in-out infinite; 758 | } 759 | 760 | @keyframes loading { 761 | to { 762 | background-position-x: -20%; 763 | } 764 | } 765 | 766 | #loader { 767 | background: var(--bg-color); 768 | position: absolute; 769 | top: 50%; 770 | left: 50%; 771 | transform: translate(-50%, -50%); 772 | width: 80px; 773 | height: 80px; 774 | border-radius: 6px; 775 | } 776 | 777 | #loader div { 778 | display: inline-block; 779 | position: absolute; 780 | left: 8px; 781 | width: 16px; 782 | background: var(--text-color); 783 | border-radius: 3px; 784 | animation: loader 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite; 785 | } 786 | 787 | #loader div:nth-child(1) { 788 | left: 8px; 789 | animation-delay: -0.24s; 790 | } 791 | 792 | #loader div:nth-child(2) { 793 | left: 32px; 794 | animation-delay: -0.12s; 795 | } 796 | 797 | #loader div:nth-child(3) { 798 | left: 56px; 799 | animation-delay: 0; 800 | } 801 | 802 | @keyframes loader { 803 | 0% { 804 | top: 8px; 805 | height: 64px; 806 | } 807 | 808 | 50%, 809 | 100% { 810 | top: 24px; 811 | height: 32px; 812 | } 813 | } 814 | 815 | .lds-spinner { 816 | margin: auto; 817 | color: official; 818 | position: relative; 819 | width: 80px; 820 | height: 80px; 821 | } 822 | 823 | .lds-spinner div { 824 | transform-origin: 40px 40px; 825 | animation: lds-spinner 1.2s linear infinite; 826 | } 827 | 828 | .lds-spinner div:after { 829 | content: ' '; 830 | display: block; 831 | position: absolute; 832 | top: 3px; 833 | left: 37px; 834 | width: 6px; 835 | height: 18px; 836 | border-radius: 20%; 837 | background: #000; 838 | } 839 | 840 | .lds-spinner div:nth-child(1) { 841 | transform: rotate(0deg); 842 | animation-delay: -1.1s; 843 | } 844 | 845 | .lds-spinner div:nth-child(2) { 846 | transform: rotate(30deg); 847 | animation-delay: -1s; 848 | } 849 | 850 | .lds-spinner div:nth-child(3) { 851 | transform: rotate(60deg); 852 | animation-delay: -0.9s; 853 | } 854 | 855 | .lds-spinner div:nth-child(4) { 856 | transform: rotate(90deg); 857 | animation-delay: -0.8s; 858 | } 859 | 860 | .lds-spinner div:nth-child(5) { 861 | transform: rotate(120deg); 862 | animation-delay: -0.7s; 863 | } 864 | 865 | .lds-spinner div:nth-child(6) { 866 | transform: rotate(150deg); 867 | animation-delay: -0.6s; 868 | } 869 | 870 | .lds-spinner div:nth-child(7) { 871 | transform: rotate(180deg); 872 | animation-delay: -0.5s; 873 | } 874 | 875 | .lds-spinner div:nth-child(8) { 876 | transform: rotate(210deg); 877 | animation-delay: -0.4s; 878 | } 879 | 880 | .lds-spinner div:nth-child(9) { 881 | transform: rotate(240deg); 882 | animation-delay: -0.3s; 883 | } 884 | 885 | .lds-spinner div:nth-child(10) { 886 | transform: rotate(270deg); 887 | animation-delay: -0.2s; 888 | } 889 | 890 | .lds-spinner div:nth-child(11) { 891 | transform: rotate(300deg); 892 | animation-delay: -0.1s; 893 | } 894 | 895 | .lds-spinner div:nth-child(12) { 896 | transform: rotate(330deg); 897 | animation-delay: 0s; 898 | } 899 | 900 | @keyframes lds-spinner { 901 | 0% { 902 | opacity: 1; 903 | } 904 | 905 | 100% { 906 | opacity: 0; 907 | } 908 | } -------------------------------------------------------------------------------- /assets/error/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Error 403 | GameHub 12 | 13 | 14 | 15 |
16 |

Error 403

17 |

The page you tried to access is forbiden.

Home

18 |
19 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /assets/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Error 404 | GameHub 15 | 16 | 17 | 18 | 19 |
20 |

Error 404

21 |

The page you tried to access could not be found. Home

22 |
23 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /assets/error/503.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Maintenance Error | GameHub 12 | 13 | 14 | 15 |
16 |

Error 503

17 |

The page you tried to access is under maintenance. It should be up and running soon. Thanks!

18 |
19 | 20 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /assets/error/failed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Error | GameHub 12 | 13 | 14 | 15 | 16 |
17 |

Operation Failed

18 |

Please try again later

19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/error/proxy_detection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Proxy Detected | GameHub 12 | 13 | 14 | 15 | 16 |
17 |

A Proxy or VPN was Detected!

18 |

You can play GameHub at https://gamehub.dev, or you can join our discord server for more links.

19 |
20 | 21 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /assets/error/unknown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Error | GameHub 12 | 13 | 14 | 15 | 16 |
17 |

Unknown

18 |

An unknown error occoured. Please try again later.

Home

19 |
20 | 21 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Black.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Black.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-BlackItalic.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-BlackItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-BlackItalic.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Bold.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Bold.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-BoldItalic.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-BoldItalic.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ExtraBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ExtraBold.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ExtraBold.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ExtraBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ExtraBoldItalic.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ExtraBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ExtraBoldItalic.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ExtraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ExtraLight.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ExtraLight.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ExtraLightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ExtraLightItalic.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ExtraLightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ExtraLightItalic.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Italic.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Light.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Light.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-LightItalic.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-LightItalic.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Medium.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Medium.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-MediumItalic.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-MediumItalic.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Regular.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Regular.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-SemiBold.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-SemiBold.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-SemiBoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-SemiBoldItalic.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-SemiBoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-SemiBoldItalic.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Thin.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-Thin.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ThinItalic.woff -------------------------------------------------------------------------------- /assets/fonts/montserrat/Montserrat-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/fonts/montserrat/Montserrat-ThinItalic.woff2 -------------------------------------------------------------------------------- /assets/fonts/montserrat/stylesheet.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Montserrat'; 3 | src: url('Montserrat-LightItalic.woff2') format('woff2'), 4 | url('Montserrat-LightItalic.woff') format('woff'); 5 | font-weight: 300; 6 | font-style: italic; 7 | font-display: swap; 8 | } 9 | 10 | @font-face { 11 | font-family: 'Montserrat'; 12 | src: url('Montserrat-ExtraLightItalic.woff2') format('woff2'), 13 | url('Montserrat-ExtraLightItalic.woff') format('woff'); 14 | font-weight: 200; 15 | font-style: italic; 16 | font-display: swap; 17 | } 18 | 19 | @font-face { 20 | font-family: 'Montserrat'; 21 | src: url('Montserrat-Italic.woff2') format('woff2'), 22 | url('Montserrat-Italic.woff') format('woff'); 23 | font-weight: normal; 24 | font-style: italic; 25 | font-display: swap; 26 | } 27 | 28 | @font-face { 29 | font-family: 'Montserrat'; 30 | src: url('Montserrat-Light.woff2') format('woff2'), 31 | url('Montserrat-Light.woff') format('woff'); 32 | font-weight: 300; 33 | font-style: normal; 34 | font-display: swap; 35 | } 36 | 37 | @font-face { 38 | font-family: 'Montserrat'; 39 | src: url('Montserrat-Regular.woff2') format('woff2'), 40 | url('Montserrat-Regular.woff') format('woff'); 41 | font-weight: normal; 42 | font-style: normal; 43 | font-display: swap; 44 | } 45 | 46 | @font-face { 47 | font-family: 'Montserrat'; 48 | src: url('Montserrat-SemiBoldItalic.woff2') format('woff2'), 49 | url('Montserrat-SemiBoldItalic.woff') format('woff'); 50 | font-weight: 600; 51 | font-style: italic; 52 | font-display: swap; 53 | } 54 | 55 | @font-face { 56 | font-family: 'Montserrat'; 57 | src: url('Montserrat-Medium.woff2') format('woff2'), 58 | url('Montserrat-Medium.woff') format('woff'); 59 | font-weight: 500; 60 | font-style: normal; 61 | font-display: swap; 62 | } 63 | 64 | @font-face { 65 | font-family: 'Montserrat'; 66 | src: url('Montserrat-Thin.woff2') format('woff2'), 67 | url('Montserrat-Thin.woff') format('woff'); 68 | font-weight: 100; 69 | font-style: normal; 70 | font-display: swap; 71 | } 72 | 73 | @font-face { 74 | font-family: 'Montserrat'; 75 | src: url('Montserrat-SemiBold.woff2') format('woff2'), 76 | url('Montserrat-SemiBold.woff') format('woff'); 77 | font-weight: 600; 78 | font-style: normal; 79 | font-display: swap; 80 | } 81 | 82 | @font-face { 83 | font-family: 'Montserrat'; 84 | src: url('Montserrat-MediumItalic.woff2') format('woff2'), 85 | url('Montserrat-MediumItalic.woff') format('woff'); 86 | font-weight: 500; 87 | font-style: italic; 88 | font-display: swap; 89 | } 90 | 91 | @font-face { 92 | font-family: 'Montserrat'; 93 | src: url('Montserrat-BlackItalic.woff2') format('woff2'), 94 | url('Montserrat-BlackItalic.woff') format('woff'); 95 | font-weight: 900; 96 | font-style: italic; 97 | font-display: swap; 98 | } 99 | 100 | @font-face { 101 | font-family: 'Montserrat'; 102 | src: url('Montserrat-ThinItalic.woff2') format('woff2'), 103 | url('Montserrat-ThinItalic.woff') format('woff'); 104 | font-weight: 100; 105 | font-style: italic; 106 | font-display: swap; 107 | } 108 | 109 | @font-face { 110 | font-family: 'Montserrat'; 111 | src: url('Montserrat-Bold.woff2') format('woff2'), 112 | url('Montserrat-Bold.woff') format('woff'); 113 | font-weight: bold; 114 | font-style: normal; 115 | font-display: swap; 116 | } 117 | 118 | @font-face { 119 | font-family: 'Montserrat'; 120 | src: url('Montserrat-BoldItalic.woff2') format('woff2'), 121 | url('Montserrat-BoldItalic.woff') format('woff'); 122 | font-weight: bold; 123 | font-style: italic; 124 | font-display: swap; 125 | } 126 | 127 | @font-face { 128 | font-family: 'Montserrat'; 129 | src: url('Montserrat-Black.woff2') format('woff2'), 130 | url('Montserrat-Black.woff') format('woff'); 131 | font-weight: 900; 132 | font-style: normal; 133 | font-display: swap; 134 | } 135 | 136 | @font-face { 137 | font-family: 'Montserrat'; 138 | src: url('Montserrat-ExtraBoldItalic.woff2') format('woff2'), 139 | url('Montserrat-ExtraBoldItalic.woff') format('woff'); 140 | font-weight: bold; 141 | font-style: italic; 142 | font-display: swap; 143 | } 144 | 145 | @font-face { 146 | font-family: 'Montserrat'; 147 | src: url('Montserrat-ExtraLight.woff2') format('woff2'), 148 | url('Montserrat-ExtraLight.woff') format('woff'); 149 | font-weight: 200; 150 | font-style: normal; 151 | font-display: swap; 152 | } 153 | 154 | @font-face { 155 | font-family: 'Montserrat'; 156 | src: url('Montserrat-ExtraBold.woff2') format('woff2'), 157 | url('Montserrat-ExtraBold.woff') format('woff'); 158 | font-weight: bold; 159 | font-style: normal; 160 | font-display: swap; 161 | } 162 | 163 | -------------------------------------------------------------------------------- /assets/img/background.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/img/background.jpeg -------------------------------------------------------------------------------- /assets/img/cutout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/footer-cutout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/halfheight-cutout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/assets/img/logo.png -------------------------------------------------------------------------------- /assets/js/analytics.js: -------------------------------------------------------------------------------- 1 | import API from './api.js'; 2 | 3 | export default () => { 4 | if (localStorage.getItem('analytics_website_id') !== 'not tracked') { 5 | if (localStorage.getItem('analytics_website_id')) { 6 | const analyticsScript = document.createElement('script'); 7 | analyticsScript.setAttribute('data-website-id', localStorage.getItem('analytics_website_id')); 8 | analyticsScript.setAttribute('async', ''); 9 | analyticsScript.src = 'https://analytics.embernet.work/script.js'; 10 | document.head.insertBefore(analyticsScript, document.head.firstChild); 11 | 12 | analyticsScript.onload = () => window.analytics = window.umami; 13 | } else API.get(`/analytics/${window.location.hostname}`) 14 | .then(res => { 15 | if (typeof res === 'string') { 16 | localStorage.setItem('analytics_website_id', res); 17 | location.reload(); 18 | } else { 19 | localStorage.setItem('analytics_website_id', 'not tracked'); 20 | location.reload(); 21 | } 22 | }) 23 | .catch(e => { 24 | localStorage.setItem('analytics_website_id', 'not tracked'); 25 | location.reload(); 26 | }); 27 | } else window.analytics = { 28 | track: (...args) => {}, 29 | identify: (...args) => {} 30 | }; 31 | }; -------------------------------------------------------------------------------- /assets/js/api.js: -------------------------------------------------------------------------------- 1 | import cookie from '/assets/js/cookie.js'; 2 | 3 | class api_ { 4 | constructor() { 5 | try { 6 | this.servers = JSON.parse(localStorage.getItem('servers')); 7 | } catch (e) { 8 | console.error('Could not get serverlist'); 9 | } 10 | } 11 | 12 | /** 13 | * 14 | * @returns {Promise.} 15 | */ 16 | validSession = () => { 17 | return new Promise((resolve, reject) => { 18 | this.get('/me') 19 | .then((res) => { 20 | if (res.status !== 404) resolve(true); 21 | else resolve(false); 22 | }) 23 | .catch(() => resolve(false)); 24 | }); 25 | }; 26 | 27 | /** 28 | * 29 | * @returns {boolean} 30 | */ 31 | connected = () => { 32 | try { 33 | return Boolean(this.servers[0]); 34 | } catch (e) { 35 | return false; 36 | } 37 | }; 38 | 39 | /** 40 | * 41 | * @param {string} route 42 | * @returns {Promise.} 43 | */ 44 | get = async (route) => { 45 | if (route) { 46 | try { 47 | const response = await fetch(this.servers[0] + route, { 48 | credentials: 'include' 49 | }); 50 | 51 | var data = await response.text(); 52 | 53 | try { 54 | data = JSON.parse(data); 55 | } catch (e) { } 56 | 57 | return data; 58 | } catch (e) { 59 | throw new Error('Could not connect to the server'); 60 | } 61 | } else throw new Error('Missing parameters for API.get'); 62 | }; 63 | 64 | /** 65 | * 66 | * @param {string} route 67 | * @param {*} data 68 | * @param {boolean} encoded 69 | * @returns {Promise.} 70 | */ 71 | post = async (route, data, encoded) => { 72 | if (route && data) { 73 | try { 74 | var response; 75 | 76 | if (encoded) { 77 | if (typeof data == 'object') { 78 | const encodingData = await this.encrypt(JSON.stringify(data)); 79 | 80 | response = await fetch(`${this.servers[0]}${route}?hostname=${window.location.hostname}`, { 81 | method: 'POST', 82 | credentials: 'include', 83 | headers: { 84 | 'Content-Type': 'application/json', 85 | 'TokenID': encodingData.id 86 | }, 87 | body: JSON.stringify({ data: encodingData.data }) 88 | }); 89 | } else { 90 | const encodingData = await this.encrypt(data); 91 | 92 | response = await fetch(`${this.servers[0]}${route}?hostname=${window.location.hostname}`, { 93 | method: 'POST', 94 | credentials: 'include', 95 | headers: { 96 | 'Content-Type': 'application/json', 97 | 'TokenID': encodingData.id 98 | }, 99 | body: JSON.stringify({ data: encodingData.data }) 100 | }); 101 | } 102 | } else { 103 | if (typeof data == 'object') response = await fetch(`${this.servers[0]}${route}?hostname=${window.location.hostname}`, { 104 | method: 'POST', 105 | credentials: 'include', 106 | headers: { 107 | 'Content-Type': 'application/json' 108 | }, 109 | redirect: 'follow', 110 | referrerPolicy: 'no-referrer', 111 | body: JSON.stringify(data) 112 | }); 113 | else response = await fetch(`${this.servers[0]}${route}?hostname=${window.location.hostname}`, { 114 | method: 'POST', 115 | credentials: 'include', 116 | headers: { 117 | 'Content-Type': 'application/json' 118 | }, 119 | body: data 120 | }); 121 | } 122 | 123 | var responseData = await response.text(); 124 | 125 | try { 126 | responseData = JSON.parse(responseData); 127 | } catch (e) { } 128 | 129 | return responseData; 130 | } catch (e) { 131 | throw new Error('Could not connect to the server'); 132 | } 133 | } else throw new Error('Missing parameters for API.post'); 134 | }; 135 | 136 | encrypt = async (data) => { 137 | const token = await this.token(); 138 | 139 | return { 140 | data: CryptoJS.AES.encrypt(data, token.token).toString(), 141 | id: token.id 142 | }; 143 | }; 144 | 145 | token = async () => { 146 | const token = await this.get('/token'); 147 | return token; 148 | }; 149 | 150 | uuid = () => { 151 | return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); 152 | }; 153 | } 154 | 155 | const API = new api_(); 156 | 157 | fetch('/config.json') 158 | .then(res => res.json()) 159 | .then(config => { 160 | if (!localStorage.getItem('servers')) { 161 | if (config.server === 'auto') fetch('https://raw.githubusercontent.com/EmberNetwork/GameHub-Assets/main/servers.json') 162 | .then(res => res.json()) 163 | .then(servers => { 164 | if (servers) { 165 | const serverList = []; 166 | var serversProcessed = 0; 167 | 168 | servers.forEach(server => { 169 | fetch(server) 170 | .then(res => res.json()) 171 | .then(serverData => { 172 | serversProcessed += 1; 173 | 174 | if (serverData) { 175 | if (serverData.status == 'ready') serverList.push(server); 176 | } 177 | }).catch(() => serversProcessed += 1); 178 | }); 179 | 180 | const checker = setInterval(() => { 181 | if (serversProcessed === servers.length) { 182 | clearInterval(checker); 183 | 184 | if (serverList.length == 0) console.error('No available server was found'); 185 | else { 186 | localStorage.setItem('servers', JSON.stringify(serverList)); 187 | location.reload(); 188 | } 189 | } 190 | }, 1); 191 | } else console.error('Could not fetch server list'); 192 | }).catch(e => console.error('Could not fetch server list')); 193 | else fetch(config.server) 194 | .then(res => res.json()) 195 | .then(serverData => { 196 | if (serverData) { 197 | if (serverData.status == 'ready') { 198 | localStorage.setItem('servers', JSON.stringify(new Array(config.server))); 199 | location.reload(); 200 | } else console.error('Could not connect to default server'); 201 | } else console.error('Could not connect to default server'); 202 | }).catch(e => console.error('Could not connect to default server')); 203 | } 204 | }) 205 | 206 | if (!sessionStorage.getItem('session')) sessionStorage.setItem('session', API.uuid()); 207 | 208 | const session = sessionStorage.getItem('session'); 209 | 210 | export default API; -------------------------------------------------------------------------------- /assets/js/cookie.js: -------------------------------------------------------------------------------- 1 | const cookie = (this, function () { 2 | 'use strict'; 3 | var cookie = function () { 4 | return cookie.get.apply(cookie, arguments); 5 | }; 6 | 7 | var utils = cookie.utils = { 8 | isArray: Array.isArray || function (value) { 9 | return Object.prototype.toString.call(value) === '[object Array]'; 10 | }, 11 | 12 | isPlainObject: function (value) { 13 | return !!value && Object.prototype.toString.call(value) === '[object Object]'; 14 | }, 15 | 16 | toArray: function (value) { 17 | return Array.prototype.slice.call(value); 18 | }, 19 | 20 | getKeys: Object.keys || function (obj) { 21 | var keys = [], 22 | key = ''; 23 | for (key in obj) { 24 | if (obj.hasOwnProperty(key)) keys.push(key); 25 | } 26 | return keys; 27 | }, 28 | 29 | encode: (value) => { 30 | return btoa(value.toString()); 31 | }, 32 | 33 | decode: (value) => { 34 | try { 35 | return atob(value.toString()); 36 | } catch (e) { 37 | return value.toString(); 38 | } 39 | }, 40 | 41 | retrieve: function (value, fallback) { 42 | return value == null ? fallback : value; 43 | } 44 | 45 | }; 46 | 47 | cookie.defaults = {}; 48 | 49 | cookie.expiresMultiplier = 60 * 60 * 24; 50 | 51 | cookie.set = function (key, value, options) { 52 | if (utils.isPlainObject(key)) { 53 | 54 | for (var k in key) { 55 | if (key.hasOwnProperty(k)) this.set(k, key[k], value); 56 | } 57 | } else { 58 | options = utils.isPlainObject(options) ? options : { expires: options }; 59 | 60 | var expires = options.expires !== undefined ? options.expires : (this.defaults.expires || ''), 61 | expiresType = typeof (expires); 62 | 63 | if (expiresType === 'string' && expires !== '') expires = new Date(expires); 64 | else if (expiresType === 'number') expires = new Date(+new Date + 1000 * this.expiresMultiplier * expires); 65 | 66 | if (expires !== '' && 'toUTCString' in expires) expires = ';expires=' + expires.toUTCString(); 67 | 68 | var path = options.path || this.defaults.path; 69 | path = path ? ';path=' + path : ''; 70 | 71 | var domain = options.domain || this.defaults.domain; 72 | domain = domain ? ';domain=' + domain : ''; 73 | 74 | var secure = options.secure || this.defaults.secure ? ';secure' : ''; 75 | if (options.secure === false) secure = ''; 76 | 77 | var sameSite = options.sameSite || this.defaults.sameSite; 78 | sameSite = sameSite ? ';SameSite=' + sameSite : ''; 79 | if (options.sameSite === null) sameSite = ''; 80 | 81 | document.cookie = key + '=' + value + expires + path + domain + secure + sameSite; 82 | } 83 | 84 | return this; 85 | }; 86 | 87 | cookie.setDefault = function (key, value, options) { 88 | if (utils.isPlainObject(key)) { 89 | for (var k in key) { 90 | if (this.get(k) === undefined) this.set(k, key[k], value); 91 | } 92 | return cookie; 93 | } else { 94 | if (this.get(key) === undefined) return this.set.apply(this, arguments); 95 | } 96 | }, 97 | 98 | cookie.remove = function (keys) { 99 | keys = utils.isArray(keys) ? keys : utils.toArray(arguments); 100 | 101 | for (var i = 0, l = keys.length; i < l; i++) { 102 | this.set(keys[i], '', -1); 103 | } 104 | 105 | return this; 106 | }; 107 | 108 | cookie.removeSpecific = function (keys, options) { 109 | if (!options) return this.remove(keys); 110 | 111 | keys = utils.isArray(keys) ? keys : [keys]; 112 | options.expires = -1; 113 | 114 | for (var i = 0, l = keys.length; i < l; i++) { 115 | this.set(keys[i], '', options); 116 | } 117 | 118 | return this; 119 | }; 120 | 121 | cookie.empty = function () { 122 | return this.remove(utils.getKeys(this.all())); 123 | }; 124 | 125 | cookie.get = function (keys, fallback) { 126 | var cookies = this.all(); 127 | 128 | if (utils.isArray(keys)) { 129 | var result = {}; 130 | 131 | for (var i = 0, l = keys.length; i < l; i++) { 132 | var value = keys[i]; 133 | result[value] = utils.retrieve(cookies[value], fallback); 134 | } 135 | 136 | return result; 137 | 138 | } else return utils.retrieve(cookies[keys], fallback); 139 | }; 140 | 141 | cookie.all = function () { 142 | if (document.cookie === '') return {}; 143 | 144 | var cookies = document.cookie.split('; '), 145 | result = {}; 146 | 147 | for (var i = 0, l = cookies.length; i < l; i++) { 148 | var item = cookies[i].split('='); 149 | var key = item.shift(); 150 | var value = item.join('='); 151 | result[key] = value; 152 | } 153 | 154 | return result; 155 | }; 156 | 157 | cookie.enabled = function () { 158 | if (navigator.cookieEnabled) return true; 159 | 160 | var ret = cookie.set('_', '_').get('_') === '_'; 161 | cookie.remove('_'); 162 | return ret; 163 | }; 164 | 165 | return cookie; 166 | }); 167 | 168 | export default cookie(); -------------------------------------------------------------------------------- /assets/js/cropbox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var cropbox = function(options){ 3 | var el = document.querySelector(options.imageBox), 4 | obj = 5 | { 6 | state : {}, 7 | ratio : 1, 8 | options : options, 9 | imageBox : el, 10 | thumbBox : el.querySelector(options.thumbBox), 11 | spinner : el.querySelector(options.spinner), 12 | image : new Image(), 13 | getDataURL: function () 14 | { 15 | var width = this.thumbBox.clientWidth, 16 | height = this.thumbBox.clientHeight, 17 | canvas = document.createElement("canvas"), 18 | dim = el.style.backgroundPosition.split(' '), 19 | size = el.style.backgroundSize.split(' '), 20 | dx = parseInt(dim[0]) - el.clientWidth/2 + width/2, 21 | dy = parseInt(dim[1]) - el.clientHeight/2 + height/2, 22 | dw = parseInt(size[0]), 23 | dh = parseInt(size[1]), 24 | sh = parseInt(this.image.height), 25 | sw = parseInt(this.image.width); 26 | 27 | canvas.width = width; 28 | canvas.height = height; 29 | var context = canvas.getContext("2d"); 30 | context.drawImage(this.image, 0, 0, sw, sh, dx, dy, dw, dh); 31 | var imageData = canvas.toDataURL('image/png'); 32 | return imageData; 33 | }, 34 | getBlob: function() 35 | { 36 | var imageData = this.getDataURL(); 37 | var b64 = imageData.replace('data:image/png;base64,',''); 38 | var binary = atob(b64); 39 | var array = []; 40 | for (var i = 0; i < binary.length; i++) { 41 | array.push(binary.charCodeAt(i)); 42 | } 43 | return new Blob([new Uint8Array(array)], {type: 'image/png'}); 44 | }, 45 | zoomIn: function () 46 | { 47 | this.ratio*=1.1; 48 | setBackground(); 49 | }, 50 | zoomOut: function () 51 | { 52 | this.ratio*=0.9; 53 | setBackground(); 54 | } 55 | }, 56 | attachEvent = function(node, event, cb) 57 | { 58 | if (node.attachEvent) 59 | node.attachEvent('on'+event, cb); 60 | else if (node.addEventListener) 61 | node.addEventListener(event, cb); 62 | }, 63 | detachEvent = function(node, event, cb) 64 | { 65 | if(node.detachEvent) { 66 | node.detachEvent('on'+event, cb); 67 | } 68 | else if(node.removeEventListener) { 69 | node.removeEventListener(event, render); 70 | } 71 | }, 72 | stopEvent = function (e) { 73 | if(window.event) e.cancelBubble = true; 74 | else e.stopImmediatePropagation(); 75 | }, 76 | setBackground = function() 77 | { 78 | var w = parseInt(obj.image.width)*obj.ratio; 79 | var h = parseInt(obj.image.height)*obj.ratio; 80 | 81 | var pw = (el.clientWidth - w) / 2; 82 | var ph = (el.clientHeight - h) / 2; 83 | 84 | el.setAttribute('style', 85 | 'background-image: url(' + obj.image.src + '); ' + 86 | 'background-size: ' + w +'px ' + h + 'px; ' + 87 | 'background-position: ' + pw + 'px ' + ph + 'px; ' + 88 | 'background-repeat: no-repeat'); 89 | }, 90 | imgMouseDown = function(e) 91 | { 92 | stopEvent(e); 93 | 94 | obj.state.dragable = true; 95 | obj.state.mouseX = e.clientX; 96 | obj.state.mouseY = e.clientY; 97 | }, 98 | imgMouseMove = function(e) 99 | { 100 | stopEvent(e); 101 | 102 | if (obj.state.dragable) 103 | { 104 | var x = e.clientX - obj.state.mouseX; 105 | var y = e.clientY - obj.state.mouseY; 106 | 107 | var bg = el.style.backgroundPosition.split(' '); 108 | 109 | var bgX = x + parseInt(bg[0]); 110 | var bgY = y + parseInt(bg[1]); 111 | 112 | el.style.backgroundPosition = bgX +'px ' + bgY + 'px'; 113 | 114 | obj.state.mouseX = e.clientX; 115 | obj.state.mouseY = e.clientY; 116 | } 117 | }, 118 | imgMouseUp = function(e) 119 | { 120 | stopEvent(e); 121 | obj.state.dragable = false; 122 | }, 123 | zoomImage = function(e) 124 | { 125 | var evt=window.event || e; 126 | var delta=evt.detail? evt.detail*(-120) : evt.wheelDelta; 127 | delta > -120 ? obj.ratio*=1.1 : obj.ratio*=0.9; 128 | setBackground(); 129 | } 130 | 131 | obj.spinner.style.display = 'block'; 132 | obj.image.onload = function() { 133 | obj.spinner.style.display = 'none'; 134 | setBackground(); 135 | 136 | attachEvent(el, 'mousedown', imgMouseDown); 137 | attachEvent(el, 'mousemove', imgMouseMove); 138 | attachEvent(document.body, 'mouseup', imgMouseUp); 139 | var mousewheel = (/Firefox/i.test(navigator.userAgent))? 'DOMMouseScroll' : 'mousewheel'; 140 | attachEvent(el, mousewheel, zoomImage); 141 | }; 142 | obj.image.src = options.imgSrc; 143 | attachEvent(el, 'DOMNodeRemoved', function(){detachEvent(document.body, 'DOMNodeRemoved', imgMouseUp)}); 144 | 145 | return obj; 146 | }; -------------------------------------------------------------------------------- /assets/js/depend.js: -------------------------------------------------------------------------------- 1 | function tab_() { 2 | (this.reload = function () { 3 | location.reload(); 4 | 5 | }), 6 | (this.redirect = function (url) { 7 | window.location.href = url; 8 | 9 | }), 10 | (this.open = function (url, width, height) { 11 | if (url) { 12 | if (!width || !height) { 13 | return window.open(url); 14 | } else if (width && height) { 15 | return window.open(url, "popup", `width=${width}, height=${height}`); 16 | } 17 | } else { 18 | console.error('Target Url is not defined\n\npage.open("url")'); 19 | } 20 | }); 21 | } 22 | 23 | function localStorage_() { 24 | for (let [key, value] of Object.entries(localStorage)) { 25 | eval(`(this.${key.replace(/[^a-zA-Z ]/g, "_")} = ${value})`) 26 | } 27 | 28 | (this.clear = function () { 29 | localStorage.clear(); 30 | }), 31 | (this.remove = function (itemName) { 32 | if (itemName) { 33 | localStorage.remove(itemName); 34 | } else { 35 | console.error('item name is not defined\n\nls.remove(item name)') 36 | } 37 | }); 38 | } 39 | 40 | function audio_() { 41 | this.data; 42 | this.ytData; 43 | (this.load = function (url, loop) { 44 | if (url) { 45 | if (!this.data) { 46 | this.data = new Audio(url); 47 | if (loop) { 48 | this.data.loop = true; 49 | } 50 | return new Audio(url); 51 | } else { 52 | audio.pause(); 53 | this.data = new Audio(url); 54 | if (loop) { 55 | this.data.loop = true; 56 | } 57 | return new Audio(url); 58 | } 59 | } else { 60 | console.error('"audio url" is not defined\n\naudio.load("audio url")'); 61 | } 62 | }), 63 | (this.play = function (time) { 64 | if (this.data) { 65 | if (time) { 66 | this.data.currentTime = time; 67 | } 68 | this.data.play(); 69 | } else { 70 | console.error('"audio" is not defined\n\naudio.load("audio url")'); 71 | } 72 | }), 73 | (this.pause = function () { 74 | if (this.data) { 75 | if (this.data.paused === false) { 76 | this.data.pause(); 77 | } else { 78 | console.warn('"audio" is not playing\n\naudio.play()'); 79 | } 80 | } else { 81 | console.error('"audio" is not defined\n\naudio.load("audio url")'); 82 | } 83 | }), 84 | (this.clear = function () { 85 | if (this.data) { 86 | if (this.data.paused) { 87 | this.data = null; 88 | } else { 89 | audio.pause(); 90 | this.data = null; 91 | } 92 | } else { 93 | console.warn('"audio" cannot be cleared\nno data\n\naudio.load()'); 94 | } 95 | }), 96 | (this.getYt = function (videoId) { 97 | fetch(`https://pipedapi.adminforge.de/streams/${videoId}?hl=en®ion=us`) 98 | .then(function (resp) { 99 | return resp.json(); 100 | }).then(function (videoData) { 101 | audio.ytData = videoData.audioStreams[0].url; 102 | }); 103 | }) 104 | } 105 | 106 | function page_() { 107 | (this.el = function (element) { 108 | return document.querySelector(element); 109 | }), 110 | (this.create = function (data) { 111 | var el = document.createElement(data.type); 112 | el.innerHTML = data.html; 113 | el.style = data.style; 114 | return eval(data.parent + ".appendChild(el)"); 115 | }), 116 | (this.body = document.body), 117 | this.self = document.documentElement; 118 | } 119 | 120 | function say(text) { 121 | if (text) { 122 | alert(text); 123 | } else { 124 | console.error('"text" is not defined\n\nsay("text")'); 125 | } 126 | } 127 | 128 | function ask(text, type) { 129 | if (text) { 130 | if (type === "text") { 131 | return prompt(text); 132 | } else if (type === "yes/no") { 133 | return confirm(text); 134 | } else if (!type) { 135 | console.error('"type" is not defined\n\nask("text", "type")'); 136 | } else { 137 | console.error(`${type} is not a message type\n\nask("text", "type")`); 138 | } 139 | } else { 140 | console.error('"text" is not defined\n\nask("text")'); 141 | } 142 | } 143 | 144 | function log(text) { 145 | console.log('text'); 146 | } 147 | 148 | function warn(text) { 149 | console.warn('text'); 150 | } 151 | 152 | function err(text) { 153 | console.error(text); 154 | } 155 | 156 | const tab = new tab_(); 157 | const audio = new audio_(); 158 | const page = new page_(); 159 | const ls = new localStorage_(); 160 | 161 | export default { tab, audio, page, ls, log, warn, err, ask, say }; -------------------------------------------------------------------------------- /assets/js/gamemanger.js: -------------------------------------------------------------------------------- 1 | import { registerError } from './notification.js'; 2 | import API from './api.js'; 3 | 4 | const nav = document.querySelector('.navbar'); 5 | const searchContainer = document.querySelector('.database_nav'); 6 | 7 | window.onscroll = () => { 8 | if (window.pageYOffset > document.querySelector('.hero').offsetHeight) searchContainer.classList.add('shadowed'); 9 | else searchContainer.classList.remove('shadowed'); 10 | }; 11 | 12 | API.get('/games').then(games => { 13 | const searchBar = document.querySelector('[data-func="search"]'); 14 | 15 | searchBar.placeholder = `Search ${games.length} games`; 16 | 17 | searchBar.addEventListener('input', (e) => { 18 | if (searchBar.value) { 19 | var result = false; 20 | 21 | document.querySelectorAll('.game').forEach(game => { 22 | if (game.title.toLowerCase().includes(searchBar.value.toLowerCase())) { 23 | result = true; 24 | 25 | game.classList.remove('hidden'); 26 | } 27 | else game.classList.add('hidden'); 28 | }); 29 | 30 | if (result) document.querySelector('.searchErr').classList.add('hidden'); 31 | else document.querySelector('.searchErr').classList.remove('hidden'); 32 | } else { 33 | document.querySelectorAll('.game').forEach(game => game.classList.remove('hidden')); 34 | document.querySelector('.searchErr').classList.add('hidden'); 35 | } 36 | }); 37 | 38 | document.querySelector('.games').innerHTML = ''; 39 | 40 | games.forEach(game => { 41 | const gameEl = document.createElement('div'); 42 | gameEl.classList = 'game'; 43 | gameEl.title = game.name; 44 | gameEl.innerHTML = `

${game.name}

`; 45 | document.querySelector('.games').appendChild(gameEl); 46 | 47 | gameEl.querySelector('img').onerror = (e) => { 48 | registerError(`Could not load splash image for ${e.target.parentElement.title}`); 49 | 50 | e.target.parentElement.classList.add('failed'); 51 | e.target.src = '/assets/img/logo.png'; 52 | } 53 | 54 | gameEl.addEventListener('click', (e) => openGame(game.id)); 55 | }); 56 | 57 | searchBar.focus(); 58 | }).catch(e => registerError('Could not load games')); 59 | 60 | const openGame = (id) => { 61 | const gameFrame = document.querySelector('.gameFrame'); 62 | const gameDatabase = document.querySelector('.games'); 63 | 64 | gameFrame.classList.remove('hidden'); 65 | gameDatabase.classList.add('hidden'); 66 | document.querySelector('.database_nav').classList.add('hidden'); 67 | 68 | API.get(`/games/${id}`) 69 | .then(game => { 70 | nav.classList.add('hidden'); 71 | document.body.classList.add('noscroll'); 72 | document.documentElement.classList.add('noscroll'); 73 | 74 | if (game.use === 'emulator') game.url = `/assets/public/gs/emulator.html?file=${game.url}&core=${game.emulatorConfig.core}&id=${id}`; 75 | else if (game.use === 'gameframe') { 76 | 77 | } 78 | 79 | const gameEl = document.createElement('iframe'); 80 | gameEl.classList = 'innerGame'; 81 | gameEl.src = '/assets/public/gs/game.html'; 82 | gameEl.title = game.name; 83 | gameFrame.appendChild(gameEl); 84 | 85 | gameEl.scrollIntoView(); 86 | 87 | gameEl.onload = () => { 88 | const frame = gameEl.contentWindow.document; 89 | const commentContainer = frame.querySelector('.commentContainer'); 90 | const commentsToggle = frame.querySelector('[data-func="open-comments"]'); 91 | 92 | frame.querySelector('.mainGame').src = game.url; 93 | frame.querySelector('.mainGame').onload = () => setTimeout(() => { 94 | frame.querySelectorAll('.mainGame')[1].classList.add('hidden'); 95 | frame.querySelectorAll('.mainGame')[0].classList.remove('hidden'); 96 | }, 1000); 97 | frame.querySelector('.gameTitle').innerText = game.name; 98 | 99 | frame.querySelector('[data-attr="fullscreen"]').addEventListener('click', (e) => frame.querySelector('.mainGame').requestFullscreen()); 100 | 101 | /*const commentFrame = frame.createElement('iframe'); 102 | commentFrame.src = `/assets/public/pages/comments.html?id=${id}`; 103 | commentFrame.classList = 'commentFrame'; 104 | commentFrame.setAttribute('frameborder', '0'); 105 | commentContainer.appendChild(commentFrame); 106 | 107 | commentsToggle.addEventListener('click', (e) => commentContainer.classList.remove('hidden'));*/ 108 | 109 | API.get(`/games/${id}/recomended`) 110 | .then(recomendations => { 111 | const recomendedGames = frame.querySelectorAll('.gameThumb'); 112 | 113 | for (let i = 0; i < recomendations.length; i++) { 114 | recomendedGames[i].innerHTML = ``; 115 | 116 | recomendedGames[i].querySelector('img').onerror = (e) => { 117 | e.target.parentElement.classList.add('failed'); 118 | e.target.src = '/assets/img/logo.png'; 119 | } 120 | 121 | recomendedGames[i].addEventListener('click', (e) => { 122 | document.querySelector('.innerGame').remove(); 123 | 124 | openGame(recomendations[i].id); 125 | }); 126 | } 127 | }).catch(e => registerError('Could not load recomended games')); 128 | 129 | frame.querySelector('.logo').addEventListener('click', (e) => closeGame()); 130 | frame.querySelector('#back').addEventListener('click', (e) => closeGame()); 131 | } 132 | }).catch(e => registerError(`Could not load game #${id}`)); 133 | } 134 | 135 | const closeGame = () => { 136 | const gameFrame = document.querySelector('.gameFrame'); 137 | const gameDatabase = document.querySelector('.games'); 138 | 139 | nav.classList.remove('hidden'); 140 | document.body.classList.remove('noscroll'); 141 | document.documentElement.classList.remove('noscroll'); 142 | 143 | gameFrame.classList.add('hidden'); 144 | gameDatabase.classList.remove('hidden'); 145 | 146 | document.querySelector('.innerGame').remove(); 147 | document.querySelector('.database_nav').classList.remove('hidden'); 148 | 149 | gameDatabase.scrollIntoView(); 150 | } 151 | 152 | window.onresize = () => document.querySelector('.gameFrame').scrollIntoView(); 153 | -------------------------------------------------------------------------------- /assets/js/inject.js: -------------------------------------------------------------------------------- 1 | /*window.addEventListener('message', (e) => { 2 | var data; 3 | 4 | try { 5 | data = JSON.parse(e.data); 6 | } catch (e) { } 7 | });*/ 8 | 9 | const sendLog = (args, type) => { 10 | try { 11 | window.top.postMessage(JSON.stringify({ 12 | action: 'console', 13 | type: type, 14 | data: args.join(' ') 15 | }), { 16 | targetOrigin: window.top.location.origin 17 | }); 18 | } catch (e) { } 19 | } 20 | 21 | console.log = (...args) => sendLog(args, 'log'); 22 | console.warn = (...args) => sendLog(args, 'warn'); 23 | console.error = (...args) => sendLog(args, 'error'); 24 | console.info = (...args) => sendLog(args, 'info'); 25 | console.debug = (...args) => { }; 26 | 27 | if (window.top === window) document.documentElement.innerHTML = ''; 28 | 29 | document.title = 'GameHub'; 30 | const icon = document.createElement('link'); 31 | icon.rel = 'icon'; 32 | icon.type = 'image/x-icon'; 33 | icon.href = 'data:image/x-icon;base64,AAABAAMAEBAAAAEAIABoBAAANgAAACAgAAABACAAKBEAAJ4EAAAwMAAAAQAgAGgmAADGFQAAKAAAABAAAAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AIAK/ACQXv02p6L+WrfD/lq3w/02p6L8AJBe/wCACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8BpFY/W6/6P7X7f///////////////////////tju//1uwOj8NqhVAAAAAAAAAAAAAAAAAAAAAAAAAAD9GJt//rnh/v///////////////////////////////////////////tzw/v0YnH4AAAAAAAAAAAAAAAD8BpFY/rnh/v/////////////////////////////////////////////////////+3/H+/Fu2VAAAAAD/AIAK/W6/6P/////+5PP//pPQ//7R6////////////////////////s/q//6Ozv/+4vL///////1tv+j/AIAK/ACQXv7X7f/++fz//W/A//7q9f/9f8f//trv/////////////trv//1+x//+6fX//W2///73+//+1+3//ACQXv02p6L//////tLr//6x3f///////vH4//1iuv/+ltH//pbR//1iuv/+8vn///////6u2//+0Or///////00p6L8WrfD//////7l9P/+ndT////////////+8/r//ub0//7m9P/+9Pr////////////+mNL//uX0///////+WbfD/lq3w//////+/f7//YTJ//7P6v/+n9X///////////////////////6e1f/+0Or//YDI//79/v///////lm3w/02p6L///////////1/x//9dsP//WS7//7n9P/+nNT//p3U//7o9P/9ZLv//XbD//17xf////////////00p6L8AJBe/tft///////+odb//ZPQ//2d1P/9aL3//tvv//7a7//9ar7//Z/U//2Qzv/+oNX///////7X7f/8AJBe/wCACv1uv+j///////////7J5//+r9z//vj8/////////////vj7//6v3P/+yef////////////9bb/o/wCACgAAAAD/BpFY/r3i/v/////////////////////////////////////////////////////+uOD+/AORWAAAAAAAAAAAAAAAAP+Hy3v/yOf+///////////////////////////////////////////+uOD+/RibfwAAAAAAAAAAAAAAAAAAAAAAAAAA/EywVP6y3uX+2O7///////////////////////7X7f/9bb/o/AORWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AJkK/wCRX/w1p6P+WrfD/Fm3w/00p6L8AJBe/wCACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAACAAAABAAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAf8AkSz8AJFf+wCPgP0AkZD9AJGQ/QCPgPwAkV//AI4t/wAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wCTKP0AkJH9AJHm/Smi//1fuf/9gsn//pLQ//6S0P/9g8n//V+5//0qov/9AJHn/QCPkv8AjykAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wCOG/0AkKX9GZv9/YLI//7e8P////////////////////////////////////////////7f8f/9g8n//Rmb/f0AkKb/AJIcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AkVb9CJTw/YLJ//71+//////////////////////////////////////////////////////////////////+9fv//YPJ//0LlfD/4/FIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9AJF//SWh/v7R6////////////////////////////////////////////////////////////////////////////////////////vb7//6Rzvz9AJJ+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/QCRf/0zpv/+6/b//////////////////////////////////////////////////////////////////////////////////////////////////ur2//0zpv/9AJB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AkVb9JaH+/uv2/////////////////////////////////////////////////////////////////////////////////////////////////////////////ur2//6Tz/z///9IAAAAAAAAAAAAAAAAAAAAAAAAAAD/AI4b/QiU8P7R6/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////01p+3/AJIcAAAAAAAAAAAAAAAAAAAAAPwAkKX9gsj//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////YLI//0AkKYAAAAAAAAAAAAAAAD/AIwo/Rmb/f71+v////////////7+/v/9ldH//TCl//0hnv/9Ybr//uf0/////////////////////////////////////////////uX0//1cuP/9F5r//Sah//2Ozv/+/f7////////////+9fr//Rib/f8AjykAAAAAAAAAAP0AkJH9gsn//////////////////X3G//08qv/+y+j//t/x//14xP/9JKD//u73//////////////////////////////////7v+P/9I5///XfE//7f8f/+yuj//Tup//11w//////////////////9gcj//QCPkgAAAAD/AAAB/QCR5v7e8P////////////7n9P/9GJv//u74//////////////////1kvP/9f8f//////////////////////////////////XzG//1iu//////////////////+7vf//Raa//7g8f////////////7e8P/9AJHn/wAAAf8AkSz9KqL//////////////////qra//1duP///////////////////////snn//0moP/++/3///////////////////////77/f/9I5///s3p///////////////////////9W7f//qPX//////////////////0pov//AI4t/ACOX/1fuf/////////////////+otb//Wm+///////////////////////+/v7//Uqw//0enf/9LaT//S2k//0tpP/9LaT//R2d//1Nsf/+/v7///////////////////////1fuf/+n9X//////////////////V65//8AkV/9AI+A/YLJ//////////////////694v/9S7H////////////////////////////+/f7//tTs//7O6v/+zur//s7q//7O6v/+1ez//v3+/////////////////////////////UGs//694v/////////////////9gcj//QCPgP0AkY7+ks///////////////////tzw//0ro//////////////////////////////////////////////////////////////////////////////////////////////////9Ip///tzw//////////////////6Rz//9AJGQ/QCQj/6S0P/////////////////++fz//Q6W//7+/v///////////////////////////////////////////////////////////////////////////////////////vz9//0IlP/++fz//////////////////pHP//0AkZD9AJGA/YLJ///////////////////////9HZ3//ur1//73+//9SrD//QmU//11wv////////////////////////////////////////////10wv/9CZT//Uux//73+//+4fL//R6d///////////////////////9gcj//QCPgP8AkV/9X7n///////////////////////09qv/+yef//q3b//1BrP/+7vf//Raa//7l8//////////////////////////////////+5PP//RWa//7u9//9Qq3//q7b//7B5P/9Pqv///////////////////////1euf/8AJFf+QCOLf0pov///////////////////////W7A//2LzP/+0ev//Rqb//11w//9Gpv//vX6//7F5v/9ULP//SOf//0koP/9UrT//srn//71+v/9GZv//XXD//0Zm//+0uv//YLI//1uwP///////////////////////Sih//8AkSz/AAAB/QCR5/7f8P/////////////////+1Oz//Rqb//7h8v/+wOP//YLI//7Y7v/+qtn//Raa//2X0f/+1+3//tft//2V0f/9F5r//rDc//7Y7f/9gsj//sDj//7c7//9Fpr//tTs//////////////////7d8P/9AJDm/wAAAQAAAAD9AJGS/YLI///////////////////////+mdL//Ryc//2Qz//+r9z//W7A//0Wmv/+zOn///////////////////////7K6P/9F5r//XLB//6w3P/9js3//Rmb//6Y0v///////////////////////YHI//0AkJEAAAAAAAAAAP8Ajyn9GZv9/vX6///////////////////////+x+b//WK7//1JsP/9d8P//uPz//////////////////////////////////7j8v/9dsP//Umw//1iu//+x+b///////////////////////71+v/9F5r9/wCTKAAAAAAAAAAAAAAAAPwAkKb9gsj//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////YHI//0AkaUAAAAAAAAAAAAAAAAAAAAA/wCOG/0IlPD+0er///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////7Q6v/9B5Tw/wCOGwAAAAAAAAAAAAAAAAAAAAAAAAAA/wCQV/0lof7+/v7////////////////////////////////////////////////////////////////////////////////////////////////////////////+6vb//SSh/vwAkVYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/5PRdP++4/7+7Pb//////////////////////////////////////////////////////////////////////////////////////////////////ur2//0xpv/9AJF/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/QyVe/0yp/7///////////////////////////////////////////////////////////////////////////////////////////7Q6v/9JKD+/QCRfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/7beTf0gnu7+6fX///////////////////////////////////////////////////////////////////////71+v/9gMj//QeU8P8AkVYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/67cFv/M6pv9HZ39/YPJ//7g8f////////////////////////////////////////////7e8P/9gcj//Rea/fwAkKX/AI4bAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8Akir9AJGU/gCR6P0qov/9X7r//YLJ//6Sz//+kc///YHI//1euf/9KKL//QCR5v0AkJH/AIwoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AIAC/wCQLvwAj2D9AJCB/QCQj/sAkI/9AI+A/ACRX/kAji3/AAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAADAAAABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AlQz2AI4b/wCRJfoAjS/7AI1B/wCQTP8Ajk3/AI1B+gCPMP8AjSb/AI4b/wCJDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AgAj/AJI//QCPdPwAkKH+AJHH/gCQ5P0Bkfr9Dpb//RWZ//0Vmf/9Dpb//QKS+v0Akeb8AJDI/QCRo/0AkHf7AI9A/wCZCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AIsL/ACQZf4AkNH9AJDv/QGR+/0zpv/9e8X//bHc//3Y7v/+4vL//uTz//7k8//+4vL//tru//203f/9fMb//Teo//0Bkfz9AJDv/QCQ1foAj23/AIsLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wCPEP8AkUr9AJDP/Q+X/f0upP/9g8n//t/w//////////////////////////////////////////////////////////////////7k8//9hcr//TKm//0QmP79AJDR/wCPUv8AjhIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AgAL7AI85/QCRpf0Fk/j9TLH//aLW//7q9f///////////////////////////////////////////////////////////////////////////////////////uz2//2m2P/9TLH//QiU+P4Akaz/CpU1/wD/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAklT9AJDm/Raa/f2q2v/+8fn//v7+//////////////////////////////////////////////////////////////////////////////////////////////////7+/v/+8fn//q/c//0Zm/7+ab7d//H4SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AIkN/QCQd/0Ekv39SbD//tvv//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////71+//+8fj//Wy/+P0AkXv/AIkNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AgAj9AJCF/QSS9P1nvP/+4/L///7+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////++fz//XXD//0Ek/f9AJGG/wCJDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0Aj3b9BJP3/YfK//72+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////vb7//2Qzv/9BJP3/QCQegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AFUD/ACQV/0Dkv39br///vL5//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////72+//9dsP//XDA+P///0j///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7AI85/gCR3f1ArP/+4/L////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////++fz//vv9//6z3t36M6gyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8Aiwv+AJKq/Rea/f7i8f///v7///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////0wpf3+A5Kr/wCOEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPsAjkT9CJT4/aLW//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6t2//9CJT4/wCPUgAAAAAAAAAAAAAAAAAAAAAAAAAA/wCACv4AkMv9TLH//uv2/////////////////////////////vD4//654P/+ntX//qPX//7L6P/++vz////////////////////////////////////////////////////////////////////////////++Pz//sjn//6a0//+k9D//q7c//7q9f////////////////////////////7x+f/9S7H//gCR0OoAgAwAAAAAAAAAAAAAAAAAAAAA/ACQY/0Llf79ptj//v7+//////////////////7+/v/9wuT//TKm//0Pl//9CJT//QmU//0Vmf/9bL///u/4//////////////////////////////////////////////////////////////////7z+v/9Zrz//RSZ//0Hk//9BZP//QyW//0nof/9u+H//v7+//////////////////7+/v/9ptj//RCY/v0AkW0AAAAAAAAAAAAAAAD/AJkK/ACQxv0loP/+6/b///////////////////////7X7f/9KKH//Sag//2U0P/+0+v//sHk//1huv/9D5f//Vy3//70+v///////////////////////////////////////////////////////vX6//1huv/9Dpf//Vu3//6/4//+0uv//ZXQ//0nof/9I5///cfm///////////////////////+6/b//TGm//0AkNX/AJkKAAAAAAAAAAD/AJI//QCR5/16xf///////////////////////vv9//1Nsf/9Naf//tDq//////////////////74/P/9h8v//Q2W//2Iyv///v7//////////////////////////////////////////////v7//ZDO//0JlP/9hMn//vX6//////////////////7W7f/9Naf//Tyq//73+////////////////////////YPI//0Ake//AI9AAAAAAAAAAAD/AJF2/gGR+v7h8f///////////////////////sHk//0Llf/9nNT//v3+///////////////////////+9Pr//Tyq//0gnv/+6PX////////////////////////////////////////////+5fP//Suj//0vpf/+8/n///////////////////////79/v/9nNT//Q6W//2t2////////////////////////uLy//0Akfz9AJB3AAAAAP8Anwj9AJGi/TOm/////////////////////////////X7G//0Wmv/+1Oz//////////////////////////////////ZPP//0Nlv/9sd3//v7+//////////////////////////////////7+/v/9rtv//RKY//2Rz//////////////////////////////////+0+z//SCe//1mvP////////////////////////////02p//9AJGj/wCJDfEAjhL+AJHH/XLB/////////////////////////////WS7//0bnP/+6/b//////////////////////////////////uLy//0Umf/9S7H//aTX//6s2//+rNv//qzb//6s2//+rNv//qzb//2m2P/9SbD//Raa//7k8//////////////////////////////////+5PP//Sah//1Qs/////////////////////////////15xf/8AJDI/wCOG/8Ajxn9AJDm/afY/////////////////////////////Wy///0Zm//+4/P//////////////////////////////////v7+//2u2//9E5j//QqV//0Mlf/9DJX//QyV//0Mlf/9DJX//QyV//0Klf/9FJn//afY//7+/v/////////////////////////////////+2e7//SKf//1fuf////////////////////////////2z3f/+AJHm/wCNJv8AjyD9ApH6/cro/////////////////////////////YvM//0Umf/+z+r///////////////////////////////////////7+/v/+3/H//rPe//6w3P/+sNz//rDc//6w3P/+sNz//rDc//6y3f/+4PH//v7+///////////////////////////////////////+xeX//Ruc//2CyP////////////////////////////7Y7f/9ApL6+gCPMP8AjzL9Dpb//tTs/////////////////////////////q3b//0Pl//+uuH////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+sN3//ROY//6n2P////////////////////////////7i8v/9DZb/+wCNQf8AkTr9FZn//tbt/////////////////////////////tDq//0KlP/+pNf////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+m9P//QuV//7N6f////////////////////////////7k8//9FJn//ACOTfsAjzv9FJn//tbt/////////////////////////////vD4//0Gk//9j87//v7+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////7+/v/9hsr//QWS//7v9/////////////////////////////7j8//9E5j//wCQTP8AkTP9Dpb//tTs//////////////////////////////7+//0Xmv/9esX//vz9///////+8/r//q/c//6Fyf/+pNf//vD4//////////////////////////////////////////////////////////////////70+v/+pNf//oXK//6r2v/+9Pr///////77/f/9csH//RaZ///+//////////////////////////////7i8v/9DZX//wCNQfcAjyD9AZH6/crn//////////////////////////////////09qv/9Zbz//vT6//77/f/9hsr//Q+X//0Ckf/9CpT//V+5//////////////////////////////////////////////////////////////////1lvP/9CpT//QKR//0Nlv/9h8r//vr8//72+//9XLj//Tmp//////////////////////////////////3X7f/9AZH6+gCNL/8Ajxn9AJHl/aTX//////////////////////////////////1huv/9T7P//uz3//7s9v/9Naf//U+y//7u9//9eMT//QWS//7b7////////////////////////////////////////////////////////t3v//0Dkv/9dML//u/3//1Ytf/9Naf//uPy//7w+P/9R6///Vy3//////////////////////////////////2v3P/+AJDk+ACNJv8AjhL8AJDI/XLB//////////////////////////////////2MzP/9M6b//uLy//7q9v/9LaT//W7A//75/P/9isz//QmU//7Q6v////////////7e8P/+sNz//qDV//6g1f/+st3//t7w/////////////tLr//0Gk//9i83//vj8//12w//9LqT//uDx//7p9f/9K6P//YPJ//////////////////////////////////14xP/8AJDI9gCOG/8AgAj9AJCh/TCk//////////////////////////////////3Q6v/9DJX//bvh//74/P/9bL///QuV//0kn//9D5f//T2q//76/f/+7vf//XDB//0Ym//9DZb//QmU//0JlP/9DZb//Rib//1xwP/+8fj//vv9//1ArP/9D5f//SKf//0Llf/9bL///vX6//2+4v/9CZT//cLk//////////////////////////////////0xpf/9AJCh/wCVDAAAAAD9AJB1/QGR+v3c7/////////////////////////////7v+P/9Q63//Tio//7r9v/+6fX//XTC//0ypf/9Yrr//uLy//7s9v/9V7X//Q6W//1ArP/9nNP//sTl//7F5f/9m9P//UOt//0Olv/9X7n//ur1//7p9P/9Ybr//TKm//1tv//+6fX//un1//0xpf/9Q63//uf0/////////////////////////////tzv//0Akfv9AI90AAAAAAAAAAD7AI9A/QCR5/16xf////////////////////////////79/v/+rNr//RSZ//1Ss//+1Oz//v3+//79/v/+/v7//tju//1lvP/9Fpr//YnM//71+v///////////////////////vX6//2Pzv/9F5r//WW8//7Z7v/+/v7//v3+//79/v/+0Or//VCz//0Olv/+q9r//vz+/////////////////////////////YHI//0AkO//AJI/AAAAAAAAAAD/AIAI/gCQxP0hn//+6vX////////////////////////////++v3//qjY//0JlP/9Gpv//Ve1//15xP/9UrT//Ruc//0Klf/9jc3//vj7//////////////////////////////////75/P/9isz//Q6W//0anP/9VrX//XjE//1Xtf/9GJr//QmU//6i1v/++vz////////////////////////////+6fX//S2k//4AkNH/AIAIAAAAAAAAAAAAAAAA/ACOX/0Klf39otb//v3+//////////////////////////////////7O6f/9S7H//SKf//0bnP/9IZ7//T+r//2z3v/+/f7////////////////////////////////////////////+/f7//bbf//1ArP/9IZ7//Ruc//0hnv/9S7H//sfm//////////////////////////////////7+/v/9oNX//Q6X/f8AkWQAAAAAAAAAAAAAAAAAAAAA6ACLC/0AkM39TLH//uv2///////////////////////////////////////+9/v//tjt//7G5v/+1u3//vj7//////////////////////////////////////////////////////////////////75/P/+1u3//sbm//7W7f/+9/v///////////////////////////////////////7x+f/9SrD//gCRz/8AiwsAAAAAAAAAAAAAAAAAAAAAAAAAAP8AkD79BZP4/Z7U//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////2n2P/9BZP4/wCRSgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8Aiwv9AJCm/RSZ/P3Y7v///v7//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////v7//trv//0Umf39AJGl/wCPEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7AI06/QCR3v0/q//+4/L////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+4vL//Uev//0AkOb7AI85AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AIAC/wCPUP0Dkv39icv//v7+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////72+//9Zbz//QOS/fwAklSAAIACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPxCrGX/zOnw/97x/v7y+f///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////vL5//2Dyf/9A5L0/QCQeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wb9r9t5/Syj8v1pvv/+5fT////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+/v/+4vL//Wy///0Ek/f9AJCF/wCJDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/JJIH/wCRb/0Hk/3+nNT///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////7g8f/9Pqv//QOS/f0AkXb/AIAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPtkuUX9gsjX/SGf/P7g8f////////////////////////////////////////////////////////////////////////////////////////////////////////////7+/v/+6vb//aDV//0Umf3+AJHd/ACQVwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH6J6I0/SyjnP7h8fP+td///qjZ//7r9v///////////////////////////////////////////////////////////////////////////////////////uv2//2k1//9SrD//QeU+PwAkav/AI85/wBVAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/7/fCP/X6zP+NajE/QqV/v0in//9fcb//d7w//////////////////////////////////////////////////////////////////7f8P/9ecT//SSg//0Klf7+AJDL+wCORP8AiwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AIsL/ACPYvwAkcf9AJDo/QGR+v0xpf/9c8L//aXY//3K5//+1Oz//tbt//7W7f/+0+z//cnn//2l2P/9ccH//TGl//0Akfr9AJHn/gCQxvwAj2T/AIAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8Ajgn/AI9C/QCQd/0AkKP8AJHJ/QCQ5v0Bkfv9Dpb//RSZ//0Umf/9DZb//QKS+v0AkOb+AJHH/ACQo/0AkHf/AJI//wCZCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8Anwj/AI4S/wCPGf8AjyD6AI40+wCPO/sAjzv6AIwz9wCLIf8AjxnxAI4S/wCfCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='; 34 | 35 | document.head.appendChild(icon); -------------------------------------------------------------------------------- /assets/js/main.js: -------------------------------------------------------------------------------- 1 | import { registerSiteNotification, registerError } from './notification.js'; 2 | import loadPageScripts from './page.js'; 3 | import analytics from './analytics.js'; 4 | import tab from './tab.js'; 5 | 6 | const scripts = { 7 | 'cryptojs': 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.js' 8 | }; 9 | const authNeeded = [ 10 | '/app' 11 | ]; 12 | const urlParams = new URLSearchParams(window.location.search); 13 | const hash = window.location.hash.replace('#', ''); 14 | 15 | loadPageScripts(urlParams, hash); 16 | analytics(); 17 | 18 | window.history.replaceState({}, '', window.location.pathname); 19 | 20 | const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); 21 | $navbarBurgers.forEach(el => { 22 | el.addEventListener('click', () => { 23 | const target = el.dataset.target; 24 | const $target = document.getElementById(target); 25 | 26 | el.classList.toggle('is-active'); 27 | $target.classList.toggle('is-active'); 28 | }); 29 | }); 30 | 31 | window.onerror = (e) => registerError(e); 32 | window.console.error = (e) => registerError(e); 33 | window.onmessageerror = (e) => registerError(e); 34 | 35 | Object.keys(scripts).forEach(key => { 36 | const script = document.createElement('script'); 37 | script.src = scripts[key]; 38 | document.body.appendChild(script); 39 | }); 40 | 41 | if (authNeeded.includes(window.location.pathname)) { 42 | const navmang = document.createElement('script'); 43 | navmang.src = '/assets/js/nav.js'; 44 | document.body.appendChild(navmang); 45 | 46 | setTimeout(() => { 47 | document.querySelector('#app').classList.remove('hidden'); 48 | document.querySelector('#loader').classList.add('hidden'); 49 | document.documentElement.classList.remove('noscroll'); 50 | 51 | registerSiteNotification('GameHub is still in public beta, please report all bugs to our discord.'); 52 | }, 1000); 53 | } else setTimeout(() => { 54 | document.querySelector('#app').classList.remove('hidden'); 55 | document.querySelector('#loader').classList.add('hidden'); 56 | document.documentElement.classList.remove('noscroll'); 57 | 58 | registerSiteNotification('GameHub is still in public beta, please report all bugs to our discord.'); 59 | if (urlParams.get('message') && urlParams.get('type')) registerSiteNotification(urlParams.get('message'), urlParams.get('type')); 60 | }, 1000); 61 | -------------------------------------------------------------------------------- /assets/js/nav.js: -------------------------------------------------------------------------------- 1 | const dropdownTabs = document.querySelectorAll('.is-dropdown'); 2 | const dropdown = document.querySelector('.navbar-item.has-dropdown'); 3 | dropdown.style.color = '#fff'; 4 | 5 | dropdown.addEventListener('mouseover', (e) => { 6 | dropdown.style.color = '#fff'; 7 | dropdown.style.backgroundColor = '#AC3CDE' 8 | }); 9 | dropdown.addEventListener('mouseout', (e) => { 10 | dropdown.style.color = '#fff'; 11 | dropdown.style.backgroundColor = 'transparent'; 12 | }); 13 | 14 | 15 | for (let i = 0; i < dropdownTabs.length; i++) { 16 | dropdownTabs[i].style.color = '#000'; 17 | dropdownTabs[i].addEventListener('mouseover', (e) => { 18 | dropdownTabs[i].style.color = '#fff'; 19 | }); 20 | dropdownTabs[i].addEventListener('mouseout', (e) => { 21 | dropdownTabs[i].style.color = '#000'; 22 | }); 23 | }; -------------------------------------------------------------------------------- /assets/js/notification.js: -------------------------------------------------------------------------------- 1 | const registerError = (e) => { 2 | registerSiteNotification((e.message ? e.message.toString() : e.toString()), 'danger'); 3 | 4 | if (e.stack) console.log('An error occurred:\n\n' + e.stack); 5 | else console.log('An error occurred:\n\n' + e); 6 | } 7 | 8 | const registerSiteNotification = (notification, type) => { 9 | var notificationContainer = document.querySelector('.notifications'); 10 | 11 | if (!notificationContainer) { 12 | notificationContainer = document.createElement('div'); 13 | notificationContainer.classList = 'notifications'; 14 | document.body.appendChild(notificationContainer); 15 | } 16 | 17 | const notificationEl = document.createElement('div'); 18 | notificationEl.classList = `notification is-${type || 'info'}`; 19 | notificationEl.innerHTML = `${notification}`; 20 | notificationContainer.appendChild(notificationEl); 21 | 22 | notificationEl.onclick = () => { 23 | notificationEl.style.height = '0px'; 24 | notificationEl.style.opacity = 0; 25 | notificationEl.style.padding = '0px'; 26 | notificationEl.firstElementChild.style.fontSize = '0px'; 27 | 28 | setTimeout(() => { 29 | notificationEl.remove(); 30 | }, 500); 31 | } 32 | 33 | setTimeout(() => { 34 | notificationEl.style.height = '0px'; 35 | notificationEl.style.opacity = 0; 36 | notificationEl.style.padding = '0px'; 37 | notificationEl.firstElementChild.style.fontSize = '0px'; 38 | 39 | setTimeout(() => { 40 | notificationEl.remove(); 41 | }, 500); 42 | }, 8000); 43 | } 44 | 45 | export { registerError, registerSiteNotification }; -------------------------------------------------------------------------------- /assets/js/page.js: -------------------------------------------------------------------------------- 1 | import API from './api.js'; 2 | 3 | const path = window.location.pathname; 4 | 5 | const loadPageScripts = async (urlParams, hash) => { 6 | if (path === '/') { 7 | if (await API.validSession()) { 8 | document.querySelector('.navbar-end').innerHTML = ` 9 | Open GameHub 10 | `; 11 | 12 | document.querySelector('#launchbtn').textContent = 'Browse Games'; 13 | document.querySelector('#launchbtn').href = '/app'; 14 | } 15 | } else if (path === '/app') { 16 | if (urlParams.get('newuser') == 'true') { 17 | const welcomeModal = document.createElement('div'); 18 | welcomeModal.classList = 'modal is-active'; 19 | welcomeModal.innerHTML = ` 20 | 21 | `; 39 | document.querySelector('.hidden').appendChild(welcomeModal); 40 | document.body.classList.add('noscroll'); 41 | document.documentElement.classList.add('noscroll'); 42 | 43 | setTimeout(() => { 44 | document.querySelector('#agreetoterms').addEventListener('change', (e) => { 45 | if (document.querySelector('#agreetoterms').checked) { 46 | document.querySelector('#closemodal').disabled = false; 47 | } else { 48 | document.querySelector('#closemodal').disabled = true; 49 | } 50 | }) 51 | 52 | document.querySelector('#closemodal').addEventListener('click', (e) => { 53 | welcomeModal.remove(); 54 | document.body.classList.remove('noscroll'); 55 | document.documentElement.classList.remove('noscroll'); 56 | }) 57 | }, 3000); 58 | } 59 | 60 | if (hash === 'profile') setTimeout(() => document.querySelector('[data-action="open_profile"]').click(), 500); 61 | 62 | const profileTriggers = document.querySelectorAll('[data-action="open_profile"]'); 63 | 64 | profileTriggers.forEach(profileTrigger => profileTrigger.addEventListener('click', (e) => { 65 | window.history.pushState({}, '', '#profile'); 66 | const modalEl = document.createElement('div'); 67 | 68 | modalEl.innerHTML = ` 69 | `; 78 | document.querySelector('#app').appendChild(modalEl); 79 | })); 80 | } 81 | } 82 | 83 | export default loadPageScripts; -------------------------------------------------------------------------------- /assets/js/profile.js: -------------------------------------------------------------------------------- 1 | import { registerError } from './notification.js'; 2 | import loadPageScripts from './page.js'; 3 | import API from './api.js'; 4 | 5 | if (window.location.pathname === '/assets/pages/profile.html') { 6 | if (!window.frameElement) window.location.replace('/app#profile'); 7 | 8 | loadPageScripts(new URLSearchParams(window.location.search), window.location.hash.replace('#', '')); 9 | } 10 | 11 | const usernameDisplay = document.querySelector('#username'); 12 | 13 | API.get(`/me`) 14 | .then(user => { 15 | if (!user.error) { 16 | if (window.location.pathname !== '/assets/pages/profile.html') { 17 | document.querySelector('.avatar-small').style.backgroundImage = `url('${API.servers[0]}${user.avatar}')`; 18 | 19 | usernameDisplay.innerText = user.username; 20 | } else { 21 | window.parent.document.body.classList.add('noscroll'); 22 | window.parent.document.documentElement.classList.add('noscroll'); 23 | const overlay = document.querySelector('.uploadIcon'); 24 | const inputBtn = document.querySelector('.button.is-rounded.is-danger.has-right-sharp'); 25 | document.querySelector('.avatar').style.backgroundImage = `url("${API.servers[0]}${user.avatar}")`; 26 | document.querySelector('[data-attr="username"]').value = user.username; 27 | document.querySelector('.userid').innerText = '#' + user.id; 28 | 29 | document.querySelector('#app').classList.remove('hidden'); 30 | document.querySelector('#loader').classList.add('hidden'); 31 | 32 | window.parent.document.querySelector('.modal-close').addEventListener('click', (event) => { 33 | window.parent.document.documentElement.classList.remove('noscroll'); 34 | window.parent.document.body.classList.remove('noscroll'); 35 | window.parent.history.pushState({}, '', window.parent.location.pathname); 36 | window.parent.document.querySelector('.modal.is-active').remove(); 37 | parentstyles.remove(); 38 | }); 39 | 40 | window.parent.document.querySelector('.modal-background').addEventListener('click', (event) => { 41 | window.parent.document.documentElement.classList.remove('noscroll'); 42 | window.parent.document.body.classList.remove('noscroll'); 43 | window.parent.history.pushState({}, '', window.parent.location.pathname); 44 | window.parent.document.querySelector('.modal.is-active').remove(); 45 | parentstyles.remove(); 46 | }); 47 | 48 | /*usernameInput.addEventListener('click', (event) => { 49 | usernameInput.value = user.username; 50 | 51 | document.querySelector('.usernameForm').classList.remove('hidden'); 52 | edit.classList.add('hidden'); 53 | username.classList.add('hidden'); 54 | idDisplay.classList.add('hidden'); 55 | 56 | usernameInput.focus(); 57 | }); 58 | 59 | document.querySelector('.usernameForm').addEventListener('submit', (event) => { 60 | event.preventDefault(); 61 | if (inputBtn.accountsset.val === '1') { 62 | let tempDB = accounts; 63 | if (usernameInput) { 64 | let usernames = []; 65 | 66 | for (let i = 0; i < accounts.length; i++) { 67 | usernames.push(accounts[i].username) 68 | } 69 | 70 | if (usernameInput.value == accounts[userId - 1].username) { 71 | alert('This is already your username') 72 | } else { 73 | if (usernames.includes(usernameInput.value) === false) { 74 | tempDB[userId - 1].username = usernameInput.value; 75 | } else { 76 | alert('This username is already taken.'); 77 | usernameInput.value = accounts[userId - 1].username; 78 | } 79 | } 80 | } else { 81 | alert('Please enter a username'); 82 | } 83 | } else if (inputBtn.accountsset.val === '0') { 84 | inputBtn.innerText = 'Done'; 85 | inputBtn.accountsset.val = '1'; 86 | usernameInput.readOnly = false; 87 | usernameInput.focus(); 88 | } 89 | }); 90 | 91 | avatar.addEventListener('mouseover', (event) => { 92 | overlay.classList.remove('hidden'); 93 | }); 94 | 95 | avatar.addEventListener('mouseout', (event) => { 96 | overlay.classList.add('hidden'); 97 | }); 98 | 99 | avatar.addEventListener('click', (event) => { 100 | content.classList.add('hidden'); 101 | 102 | const frame = document.createElement('iframe'); 103 | frame.src = '/assets/pages/cropper.html'; 104 | frame.classList = 'frame_500x500'; 105 | frame.setAttribute('scrolling', 'no'); 106 | document.body.appendChild(frame); 107 | 108 | window.onmessage = (e) => { 109 | if (e.data) { 110 | frame.remove(); 111 | content.classList.remove('hidden'); 112 | 113 | content.innerHTML = `

Please Confirm Your Password

`; 114 | 115 | const confirm = content.querySelector('#confirm_password'); 116 | confirm.focus(); 117 | 118 | confirm.addEventListener('keydown', (event) => { 119 | if (event.key === 'Enter') { 120 | API.post(`/users/${userId}/change/profile`, { username: account.username, password: confirm.value, picture: e.data }) 121 | .then(res => { 122 | alert(res); 123 | }).catch(e => { 124 | 125 | }) 126 | } 127 | }); 128 | } else { 129 | alert('Could not proccess your request. Please try again later.') 130 | } 131 | } 132 | });*/ 133 | } 134 | } else registerError('Could not load profile data'); 135 | }).catch(e => registerError('Could not load profile data')); -------------------------------------------------------------------------------- /assets/js/tab.js: -------------------------------------------------------------------------------- 1 | import API from './api.js'; 2 | 3 | const filename = location.pathname.substring(location.pathname.lastIndexOf('/') + 1); 4 | const isIndex = filename === 'index.html' || filename === 'index'; 5 | const isHTML = filename.includes('.html'); 6 | 7 | if (isIndex) window.location.href = window.location.href.replace('/' + filename, ''); 8 | else if (isHTML) window.location.href = window.location.href.replace('.html', ''); 9 | 10 | const showError = (errCode) => { 11 | fetch('/assets/JSON/errors.json') 12 | .then(resp => resp.json()) 13 | .then(errs => { 14 | if (errs.includes(errCode)) fetch(`/assets/error/${errCode}.html`) 15 | .then(obj => obj.text()) 16 | .then(error => { 17 | document.documentElement.innerHTML = error; 18 | }); 19 | else fetch('/assets/error/unknown.html') 20 | .then(obj => obj.text()) 21 | .then(error => { 22 | document.documentElement.innerHTML = error; 23 | }); 24 | }); 25 | } 26 | 27 | const authNeeded = [ 28 | '/app' 29 | ]; 30 | 31 | if (authNeeded.includes(location.pathname) && !await API.validSession()) window.location.href = '/auth'; 32 | 33 | export default {}; -------------------------------------------------------------------------------- /assets/pages/cropper.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 40 | 41 | 42 | 43 | 44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 | 60 |
61 | 62 | 63 | 64 |
65 |
66 |
67 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /assets/pages/featured.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 76 | 77 | 78 | 79 |
80 |
81 |
82 | 83 |
Caption Text
84 |
85 | 86 |
87 | 88 |
Caption Two
89 |
90 | 91 |
92 | 93 |
Caption Three
94 |
95 |
96 | 97 |
98 | 99 | 100 | 101 |
102 |
103 | 104 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /assets/pages/profile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 37 | 38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /assets/pages/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/public/gs/emulator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 |
22 | 23 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /assets/public/gs/game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 |

Loading...

23 |
24 |
25 | 28 | 29 |
30 | 31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 |

Recomended

39 |
40 |
41 |
42 | 43 |
44 | 48 |
49 |
50 | 51 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /assets/public/gs/gameframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | 22 | 23 | 24 | 25 | 26 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /assets/public/pages/comments.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 92 | 93 |
94 |
95 |
96 |
97 |
98 | 99 | 100 | -------------------------------------------------------------------------------- /auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Authorize | GameHub 14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "auto", 3 | "serverConfig": { 4 | "port": 8080, 5 | "ignoredPaths": [ 6 | "/node_modules/*", 7 | "/development/*", 8 | "/server/index.js", 9 | "/README.md", 10 | "/.gitignore", 11 | "/LICENSE", 12 | "/package.json", 13 | "/package-lock.json" 14 | ], 15 | "mirror": { 16 | "enabled": true, 17 | "config": { 18 | "path": "/mirror/", 19 | "map": "https://raw.githubusercontent.com/EmberNetwork/Mirror/main/server/map.json", 20 | "package": "https://raw.githubusercontent.com/EmberNetwork/Mirror/main/package.json", 21 | "repo": { 22 | "owner": "EmberNetwork", 23 | "name": "Mirror" 24 | }, 25 | "netplay": { 26 | "enabled": false, 27 | "TWILIO_ACCOUNT_SID": "", 28 | "TWILIO_AUTH_TOKEN": "" 29 | } 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /development/contribute.md: -------------------------------------------------------------------------------- 1 | ## Contribute 2 | 3 | This page is still being worked on. -------------------------------------------------------------------------------- /favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/favicon-16x16.png -------------------------------------------------------------------------------- /favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/favicon-32x32.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embr-dev/GameHub/ae3b8eace8fbbef234d4d3dcc99898c11d97f0bb/favicon.ico -------------------------------------------------------------------------------- /home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Redirecting... 13 | 14 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GameHub 15 | 16 | 17 | 18 | 99 | 100 |
101 |
102 |
103 |
104 |
105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /logout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Logout | GameHub 14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gamehub", 3 | "version": "1.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "gamehub", 9 | "version": "1.1.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "mime": "^3.0.0", 13 | "socket.io": "^4.7.2", 14 | "twilio": "^4.23.0" 15 | } 16 | }, 17 | "node_modules/@socket.io/component-emitter": { 18 | "version": "3.1.2", 19 | "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", 20 | "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" 21 | }, 22 | "node_modules/@types/cookie": { 23 | "version": "0.4.1", 24 | "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", 25 | "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" 26 | }, 27 | "node_modules/@types/cors": { 28 | "version": "2.8.17", 29 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", 30 | "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", 31 | "dependencies": { 32 | "@types/node": "*" 33 | } 34 | }, 35 | "node_modules/@types/node": { 36 | "version": "20.14.5", 37 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.5.tgz", 38 | "integrity": "sha512-aoRR+fJkZT2l0aGOJhuA8frnCSoNX6W7U2mpNq63+BxBIj5BQFt8rHy627kijCmm63ijdSdwvGgpUsU6MBsZZA==", 39 | "dependencies": { 40 | "undici-types": "~5.26.4" 41 | } 42 | }, 43 | "node_modules/accepts": { 44 | "version": "1.3.8", 45 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 46 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 47 | "dependencies": { 48 | "mime-types": "~2.1.34", 49 | "negotiator": "0.6.3" 50 | }, 51 | "engines": { 52 | "node": ">= 0.6" 53 | } 54 | }, 55 | "node_modules/agent-base": { 56 | "version": "6.0.2", 57 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 58 | "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 59 | "dependencies": { 60 | "debug": "4" 61 | }, 62 | "engines": { 63 | "node": ">= 6.0.0" 64 | } 65 | }, 66 | "node_modules/asynckit": { 67 | "version": "0.4.0", 68 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 69 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 70 | }, 71 | "node_modules/axios": { 72 | "version": "1.7.2", 73 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", 74 | "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", 75 | "dependencies": { 76 | "follow-redirects": "^1.15.6", 77 | "form-data": "^4.0.0", 78 | "proxy-from-env": "^1.1.0" 79 | } 80 | }, 81 | "node_modules/base64id": { 82 | "version": "2.0.0", 83 | "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", 84 | "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", 85 | "engines": { 86 | "node": "^4.5.0 || >= 5.9" 87 | } 88 | }, 89 | "node_modules/buffer-equal-constant-time": { 90 | "version": "1.0.1", 91 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 92 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" 93 | }, 94 | "node_modules/call-bind": { 95 | "version": "1.0.7", 96 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 97 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 98 | "dependencies": { 99 | "es-define-property": "^1.0.0", 100 | "es-errors": "^1.3.0", 101 | "function-bind": "^1.1.2", 102 | "get-intrinsic": "^1.2.4", 103 | "set-function-length": "^1.2.1" 104 | }, 105 | "engines": { 106 | "node": ">= 0.4" 107 | }, 108 | "funding": { 109 | "url": "https://github.com/sponsors/ljharb" 110 | } 111 | }, 112 | "node_modules/combined-stream": { 113 | "version": "1.0.8", 114 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 115 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 116 | "dependencies": { 117 | "delayed-stream": "~1.0.0" 118 | }, 119 | "engines": { 120 | "node": ">= 0.8" 121 | } 122 | }, 123 | "node_modules/cookie": { 124 | "version": "0.4.2", 125 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", 126 | "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", 127 | "engines": { 128 | "node": ">= 0.6" 129 | } 130 | }, 131 | "node_modules/cors": { 132 | "version": "2.8.5", 133 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 134 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 135 | "dependencies": { 136 | "object-assign": "^4", 137 | "vary": "^1" 138 | }, 139 | "engines": { 140 | "node": ">= 0.10" 141 | } 142 | }, 143 | "node_modules/dayjs": { 144 | "version": "1.11.11", 145 | "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", 146 | "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" 147 | }, 148 | "node_modules/debug": { 149 | "version": "4.3.5", 150 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", 151 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", 152 | "dependencies": { 153 | "ms": "2.1.2" 154 | }, 155 | "engines": { 156 | "node": ">=6.0" 157 | }, 158 | "peerDependenciesMeta": { 159 | "supports-color": { 160 | "optional": true 161 | } 162 | } 163 | }, 164 | "node_modules/define-data-property": { 165 | "version": "1.1.4", 166 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 167 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 168 | "dependencies": { 169 | "es-define-property": "^1.0.0", 170 | "es-errors": "^1.3.0", 171 | "gopd": "^1.0.1" 172 | }, 173 | "engines": { 174 | "node": ">= 0.4" 175 | }, 176 | "funding": { 177 | "url": "https://github.com/sponsors/ljharb" 178 | } 179 | }, 180 | "node_modules/delayed-stream": { 181 | "version": "1.0.0", 182 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 183 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 184 | "engines": { 185 | "node": ">=0.4.0" 186 | } 187 | }, 188 | "node_modules/ecdsa-sig-formatter": { 189 | "version": "1.0.11", 190 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 191 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 192 | "dependencies": { 193 | "safe-buffer": "^5.0.1" 194 | } 195 | }, 196 | "node_modules/engine.io": { 197 | "version": "6.5.5", 198 | "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", 199 | "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", 200 | "dependencies": { 201 | "@types/cookie": "^0.4.1", 202 | "@types/cors": "^2.8.12", 203 | "@types/node": ">=10.0.0", 204 | "accepts": "~1.3.4", 205 | "base64id": "2.0.0", 206 | "cookie": "~0.4.1", 207 | "cors": "~2.8.5", 208 | "debug": "~4.3.1", 209 | "engine.io-parser": "~5.2.1", 210 | "ws": "~8.17.1" 211 | }, 212 | "engines": { 213 | "node": ">=10.2.0" 214 | } 215 | }, 216 | "node_modules/engine.io-parser": { 217 | "version": "5.2.2", 218 | "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", 219 | "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", 220 | "engines": { 221 | "node": ">=10.0.0" 222 | } 223 | }, 224 | "node_modules/es-define-property": { 225 | "version": "1.0.0", 226 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 227 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 228 | "dependencies": { 229 | "get-intrinsic": "^1.2.4" 230 | }, 231 | "engines": { 232 | "node": ">= 0.4" 233 | } 234 | }, 235 | "node_modules/es-errors": { 236 | "version": "1.3.0", 237 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 238 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 239 | "engines": { 240 | "node": ">= 0.4" 241 | } 242 | }, 243 | "node_modules/follow-redirects": { 244 | "version": "1.15.6", 245 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", 246 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", 247 | "funding": [ 248 | { 249 | "type": "individual", 250 | "url": "https://github.com/sponsors/RubenVerborgh" 251 | } 252 | ], 253 | "engines": { 254 | "node": ">=4.0" 255 | }, 256 | "peerDependenciesMeta": { 257 | "debug": { 258 | "optional": true 259 | } 260 | } 261 | }, 262 | "node_modules/form-data": { 263 | "version": "4.0.0", 264 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 265 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 266 | "dependencies": { 267 | "asynckit": "^0.4.0", 268 | "combined-stream": "^1.0.8", 269 | "mime-types": "^2.1.12" 270 | }, 271 | "engines": { 272 | "node": ">= 6" 273 | } 274 | }, 275 | "node_modules/function-bind": { 276 | "version": "1.1.2", 277 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 278 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 279 | "funding": { 280 | "url": "https://github.com/sponsors/ljharb" 281 | } 282 | }, 283 | "node_modules/get-intrinsic": { 284 | "version": "1.2.4", 285 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 286 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 287 | "dependencies": { 288 | "es-errors": "^1.3.0", 289 | "function-bind": "^1.1.2", 290 | "has-proto": "^1.0.1", 291 | "has-symbols": "^1.0.3", 292 | "hasown": "^2.0.0" 293 | }, 294 | "engines": { 295 | "node": ">= 0.4" 296 | }, 297 | "funding": { 298 | "url": "https://github.com/sponsors/ljharb" 299 | } 300 | }, 301 | "node_modules/gopd": { 302 | "version": "1.0.1", 303 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 304 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 305 | "dependencies": { 306 | "get-intrinsic": "^1.1.3" 307 | }, 308 | "funding": { 309 | "url": "https://github.com/sponsors/ljharb" 310 | } 311 | }, 312 | "node_modules/has-property-descriptors": { 313 | "version": "1.0.2", 314 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 315 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 316 | "dependencies": { 317 | "es-define-property": "^1.0.0" 318 | }, 319 | "funding": { 320 | "url": "https://github.com/sponsors/ljharb" 321 | } 322 | }, 323 | "node_modules/has-proto": { 324 | "version": "1.0.3", 325 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 326 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 327 | "engines": { 328 | "node": ">= 0.4" 329 | }, 330 | "funding": { 331 | "url": "https://github.com/sponsors/ljharb" 332 | } 333 | }, 334 | "node_modules/has-symbols": { 335 | "version": "1.0.3", 336 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 337 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 338 | "engines": { 339 | "node": ">= 0.4" 340 | }, 341 | "funding": { 342 | "url": "https://github.com/sponsors/ljharb" 343 | } 344 | }, 345 | "node_modules/hasown": { 346 | "version": "2.0.2", 347 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 348 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 349 | "dependencies": { 350 | "function-bind": "^1.1.2" 351 | }, 352 | "engines": { 353 | "node": ">= 0.4" 354 | } 355 | }, 356 | "node_modules/https-proxy-agent": { 357 | "version": "5.0.1", 358 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", 359 | "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", 360 | "dependencies": { 361 | "agent-base": "6", 362 | "debug": "4" 363 | }, 364 | "engines": { 365 | "node": ">= 6" 366 | } 367 | }, 368 | "node_modules/jsonwebtoken": { 369 | "version": "9.0.2", 370 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", 371 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", 372 | "dependencies": { 373 | "jws": "^3.2.2", 374 | "lodash.includes": "^4.3.0", 375 | "lodash.isboolean": "^3.0.3", 376 | "lodash.isinteger": "^4.0.4", 377 | "lodash.isnumber": "^3.0.3", 378 | "lodash.isplainobject": "^4.0.6", 379 | "lodash.isstring": "^4.0.1", 380 | "lodash.once": "^4.0.0", 381 | "ms": "^2.1.1", 382 | "semver": "^7.5.4" 383 | }, 384 | "engines": { 385 | "node": ">=12", 386 | "npm": ">=6" 387 | } 388 | }, 389 | "node_modules/jwa": { 390 | "version": "1.4.1", 391 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 392 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 393 | "dependencies": { 394 | "buffer-equal-constant-time": "1.0.1", 395 | "ecdsa-sig-formatter": "1.0.11", 396 | "safe-buffer": "^5.0.1" 397 | } 398 | }, 399 | "node_modules/jws": { 400 | "version": "3.2.2", 401 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 402 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 403 | "dependencies": { 404 | "jwa": "^1.4.1", 405 | "safe-buffer": "^5.0.1" 406 | } 407 | }, 408 | "node_modules/lodash.includes": { 409 | "version": "4.3.0", 410 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 411 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" 412 | }, 413 | "node_modules/lodash.isboolean": { 414 | "version": "3.0.3", 415 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 416 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" 417 | }, 418 | "node_modules/lodash.isinteger": { 419 | "version": "4.0.4", 420 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 421 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" 422 | }, 423 | "node_modules/lodash.isnumber": { 424 | "version": "3.0.3", 425 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 426 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" 427 | }, 428 | "node_modules/lodash.isplainobject": { 429 | "version": "4.0.6", 430 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 431 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" 432 | }, 433 | "node_modules/lodash.isstring": { 434 | "version": "4.0.1", 435 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 436 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" 437 | }, 438 | "node_modules/lodash.once": { 439 | "version": "4.1.1", 440 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 441 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" 442 | }, 443 | "node_modules/mime": { 444 | "version": "3.0.0", 445 | "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", 446 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 447 | "bin": { 448 | "mime": "cli.js" 449 | }, 450 | "engines": { 451 | "node": ">=10.0.0" 452 | } 453 | }, 454 | "node_modules/mime-db": { 455 | "version": "1.52.0", 456 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 457 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 458 | "engines": { 459 | "node": ">= 0.6" 460 | } 461 | }, 462 | "node_modules/mime-types": { 463 | "version": "2.1.35", 464 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 465 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 466 | "dependencies": { 467 | "mime-db": "1.52.0" 468 | }, 469 | "engines": { 470 | "node": ">= 0.6" 471 | } 472 | }, 473 | "node_modules/ms": { 474 | "version": "2.1.2", 475 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 476 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 477 | }, 478 | "node_modules/negotiator": { 479 | "version": "0.6.3", 480 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 481 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 482 | "engines": { 483 | "node": ">= 0.6" 484 | } 485 | }, 486 | "node_modules/object-assign": { 487 | "version": "4.1.1", 488 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 489 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 490 | "engines": { 491 | "node": ">=0.10.0" 492 | } 493 | }, 494 | "node_modules/object-inspect": { 495 | "version": "1.13.1", 496 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 497 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 498 | "funding": { 499 | "url": "https://github.com/sponsors/ljharb" 500 | } 501 | }, 502 | "node_modules/proxy-from-env": { 503 | "version": "1.1.0", 504 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 505 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 506 | }, 507 | "node_modules/qs": { 508 | "version": "6.12.1", 509 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", 510 | "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", 511 | "dependencies": { 512 | "side-channel": "^1.0.6" 513 | }, 514 | "engines": { 515 | "node": ">=0.6" 516 | }, 517 | "funding": { 518 | "url": "https://github.com/sponsors/ljharb" 519 | } 520 | }, 521 | "node_modules/querystringify": { 522 | "version": "2.2.0", 523 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 524 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" 525 | }, 526 | "node_modules/requires-port": { 527 | "version": "1.0.0", 528 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 529 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" 530 | }, 531 | "node_modules/safe-buffer": { 532 | "version": "5.2.1", 533 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 534 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 535 | "funding": [ 536 | { 537 | "type": "github", 538 | "url": "https://github.com/sponsors/feross" 539 | }, 540 | { 541 | "type": "patreon", 542 | "url": "https://www.patreon.com/feross" 543 | }, 544 | { 545 | "type": "consulting", 546 | "url": "https://feross.org/support" 547 | } 548 | ] 549 | }, 550 | "node_modules/scmp": { 551 | "version": "2.1.0", 552 | "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", 553 | "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" 554 | }, 555 | "node_modules/semver": { 556 | "version": "7.6.2", 557 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", 558 | "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", 559 | "bin": { 560 | "semver": "bin/semver.js" 561 | }, 562 | "engines": { 563 | "node": ">=10" 564 | } 565 | }, 566 | "node_modules/set-function-length": { 567 | "version": "1.2.2", 568 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 569 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 570 | "dependencies": { 571 | "define-data-property": "^1.1.4", 572 | "es-errors": "^1.3.0", 573 | "function-bind": "^1.1.2", 574 | "get-intrinsic": "^1.2.4", 575 | "gopd": "^1.0.1", 576 | "has-property-descriptors": "^1.0.2" 577 | }, 578 | "engines": { 579 | "node": ">= 0.4" 580 | } 581 | }, 582 | "node_modules/side-channel": { 583 | "version": "1.0.6", 584 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 585 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 586 | "dependencies": { 587 | "call-bind": "^1.0.7", 588 | "es-errors": "^1.3.0", 589 | "get-intrinsic": "^1.2.4", 590 | "object-inspect": "^1.13.1" 591 | }, 592 | "engines": { 593 | "node": ">= 0.4" 594 | }, 595 | "funding": { 596 | "url": "https://github.com/sponsors/ljharb" 597 | } 598 | }, 599 | "node_modules/socket.io": { 600 | "version": "4.7.5", 601 | "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", 602 | "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", 603 | "dependencies": { 604 | "accepts": "~1.3.4", 605 | "base64id": "~2.0.0", 606 | "cors": "~2.8.5", 607 | "debug": "~4.3.2", 608 | "engine.io": "~6.5.2", 609 | "socket.io-adapter": "~2.5.2", 610 | "socket.io-parser": "~4.2.4" 611 | }, 612 | "engines": { 613 | "node": ">=10.2.0" 614 | } 615 | }, 616 | "node_modules/socket.io-adapter": { 617 | "version": "2.5.5", 618 | "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", 619 | "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", 620 | "dependencies": { 621 | "debug": "~4.3.4", 622 | "ws": "~8.17.1" 623 | } 624 | }, 625 | "node_modules/socket.io-parser": { 626 | "version": "4.2.4", 627 | "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", 628 | "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", 629 | "dependencies": { 630 | "@socket.io/component-emitter": "~3.1.0", 631 | "debug": "~4.3.1" 632 | }, 633 | "engines": { 634 | "node": ">=10.0.0" 635 | } 636 | }, 637 | "node_modules/twilio": { 638 | "version": "4.23.0", 639 | "resolved": "https://registry.npmjs.org/twilio/-/twilio-4.23.0.tgz", 640 | "integrity": "sha512-LdNBQfOe0dY2oJH2sAsrxazpgfFQo5yXGxe96QA8UWB5uu+433PrUbkv8gQ5RmrRCqUTPQ0aOrIyAdBr1aB03Q==", 641 | "dependencies": { 642 | "axios": "^1.6.0", 643 | "dayjs": "^1.11.9", 644 | "https-proxy-agent": "^5.0.0", 645 | "jsonwebtoken": "^9.0.0", 646 | "qs": "^6.9.4", 647 | "scmp": "^2.1.0", 648 | "url-parse": "^1.5.9", 649 | "xmlbuilder": "^13.0.2" 650 | }, 651 | "engines": { 652 | "node": ">=14.0" 653 | } 654 | }, 655 | "node_modules/undici-types": { 656 | "version": "5.26.5", 657 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 658 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" 659 | }, 660 | "node_modules/url-parse": { 661 | "version": "1.5.10", 662 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 663 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 664 | "dependencies": { 665 | "querystringify": "^2.1.1", 666 | "requires-port": "^1.0.0" 667 | } 668 | }, 669 | "node_modules/vary": { 670 | "version": "1.1.2", 671 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 672 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 673 | "engines": { 674 | "node": ">= 0.8" 675 | } 676 | }, 677 | "node_modules/ws": { 678 | "version": "8.17.1", 679 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", 680 | "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", 681 | "engines": { 682 | "node": ">=10.0.0" 683 | }, 684 | "peerDependencies": { 685 | "bufferutil": "^4.0.1", 686 | "utf-8-validate": ">=5.0.2" 687 | }, 688 | "peerDependenciesMeta": { 689 | "bufferutil": { 690 | "optional": true 691 | }, 692 | "utf-8-validate": { 693 | "optional": true 694 | } 695 | } 696 | }, 697 | "node_modules/xmlbuilder": { 698 | "version": "13.0.2", 699 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", 700 | "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", 701 | "engines": { 702 | "node": ">=6.0" 703 | } 704 | } 705 | } 706 | } 707 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gamehub", 3 | "version": "1.1.0", 4 | "description": "An open source, privacy respecting gaming website", 5 | "main": "server/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node server/index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/EmberNetwork/GameHub.git" 13 | }, 14 | "author": "EmberNetwork", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/EmberNetwork/GameHub/issues" 18 | }, 19 | "homepage": "https://github.com/EmberNetwork/GameHub#readme", 20 | "dependencies": { 21 | "mime": "^3.0.0", 22 | "socket.io": "^4.7.2", 23 | "twilio": "^4.23.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | import mime from 'mime'; 2 | 3 | import Mirror from './mirror.js'; 4 | 5 | import childProcess from 'node:child_process'; 6 | import http from 'node:http'; 7 | import path from 'node:path'; 8 | import url from 'node:url'; 9 | import fs from 'node:fs'; 10 | 11 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 12 | const server = http.createServer(); 13 | const packageFile = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'))); 14 | const config = JSON.parse(fs.readFileSync(path.join(__dirname, '../config.json'))).serverConfig; 15 | 16 | /** 17 | * @type {Mirror} 18 | */ 19 | const mirrorServer = (config.mirror.enabled ? new Mirror(config.mirror.config, packageFile, server) : {}); 20 | 21 | const pathToFile = (url = '', folderPath) => { 22 | if (url.endsWith('/')) url = url + 'index.html'; 23 | else if (url.split(/[#?]/)[0].split('.').pop().trim() === url) { 24 | if (!fs.existsSync(path.join(folderPath, url))) url = url + '.html'; 25 | } 26 | 27 | return { 28 | exists: fs.existsSync(path.join(folderPath, url)), 29 | path: path.join(folderPath, url) 30 | }; 31 | }; 32 | 33 | server.on('request', (req, res) => { 34 | req.path = new URL('http://localhost' + req.url).pathname; 35 | var ignoredPath; 36 | 37 | config.ignoredPaths.forEach(path => { 38 | if (path.endsWith('/*') && req.path.startsWith(path.slice(-2))) ignoredPath = true; 39 | }); 40 | 41 | if (req.path === '/config.json') { 42 | const rewrittenConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '../config.json'))); 43 | rewrittenConfig.serverConfig = undefined; 44 | 45 | res.setHeader('content-type', 'application/json'); 46 | res.end(JSON.stringify(rewrittenConfig)); 47 | } else if (config.ignoredPaths.includes(req.path) || ignoredPath) { 48 | res.statusCode = 404; 49 | res.setHeader('content-type', 'text/html'); 50 | res.end(fs.readFileSync(path.join(__dirname, '../', '404.html'))); 51 | } else if (!req.path.startsWith(config.mirror.config.path)) { 52 | const file = pathToFile(req.path, path.join(__dirname, '../')); 53 | 54 | if (file.exists) { 55 | res.setHeader('content-type', mime.getType(file.path)); 56 | res.end(fs.readFileSync(file.path)); 57 | } else { 58 | res.statusCode = 404; 59 | res.setHeader('content-type', 'text/html'); 60 | res.end(fs.readFileSync(path.join(__dirname, '../', '404.html'))); 61 | } 62 | } 63 | }); 64 | 65 | server.on('listening', () => console.log(`GameHub server listening\n\nPort: ${server.address().port}\nVersion: ${packageFile.version} ${childProcess.execSync('git rev-parse HEAD') .toString().trim().slice(0, 7)}${config.mirror.enabled ? `\nMirror Server Version: ${mirrorServer.package.version} ${mirrorServer.latestCommit.sha.slice(0, 7)}` : ''}`)); 66 | 67 | if (config.mirror.enabled) mirrorServer.on('ready', () => server.listen(config.port || process.env.PORT || 8080)); 68 | else server.listen(config.port || process.env.PORT || 8080); -------------------------------------------------------------------------------- /server/mirror.js: -------------------------------------------------------------------------------- 1 | import childProcess from 'node:child_process'; 2 | import EventEmitter from 'node:events'; 3 | import path from 'node:path'; 4 | import url from 'node:url'; 5 | import fs from 'node:fs'; 6 | 7 | const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); 8 | 9 | class Mirror extends EventEmitter { 10 | constructor(config, packageFile, server) { 11 | super(); 12 | 13 | this.server = server; 14 | this.config = config; 15 | this.packageFile = packageFile; 16 | this.mapPath = this.config.map.split('/').slice(0, this.config.map.split('/').length - 1).join('/') + '/'; 17 | 18 | this.init() 19 | .then(() => this.emit('ready')) 20 | .catch(e => this.emit('error', e)); 21 | } 22 | 23 | init = () => { 24 | return new Promise(async (resolve, reject) => { 25 | this.commits = await (await fetch(`https://api.github.com/repos/${this.config.repo.owner}/${this.config.repo.name}/commits`)).json(); 26 | this.package = await (await fetch(this.config.package)).json(); 27 | this.map = await (await fetch(this.config.map)).json(); 28 | 29 | this.latestCommit = this.commits[0]; 30 | 31 | if (!this.latestCommit) console.warn('[Warn] Failed to get latest commit data. Mirror server may be out of date.'); 32 | 33 | await this.install(); 34 | if (this.latestCommit) await this.cloneFiles(); 35 | else this.latestCommit = { 36 | sha: JSON.parse(fs.readFileSync(path.join(__dirname, 'mirror/update.json'))).commit.sha, 37 | commit: { 38 | message: JSON.parse(fs.readFileSync(path.join(__dirname, 'mirror/update.json'))).commit.message 39 | } 40 | }; 41 | this.mirrorServer = (await import('./mirror/index.js')).default.attachToServer(this.server); 42 | 43 | resolve(); 44 | }); 45 | } 46 | 47 | install = () => { 48 | return new Promise((resolve, reject) => { 49 | const packages = []; 50 | 51 | Object.keys(this.package.dependencies).forEach(key => { 52 | if (!Object.keys(this.packageFile.dependencies).includes(key)) packages.push(key); 53 | }); 54 | 55 | if (packages.length > 0) { 56 | const installer = childProcess.exec('npm install ' + packages.join(' ')); 57 | 58 | installer.on('close', () => resolve()); 59 | } else resolve(); 60 | }); 61 | } 62 | 63 | cloneFiles = () => { 64 | return new Promise(async (resolve, reject) => { 65 | if (!fs.existsSync(path.join(__dirname, 'mirror/'))) await fs.promises.mkdir(path.join(__dirname, 'mirror/')); 66 | 67 | if (!fs.existsSync(path.join(__dirname, 'mirror/update.json'))) { 68 | this.map.forEach(async file => { 69 | if (file.includes('/')) await fs.promises.mkdir(path.join(__dirname, '/mirror/', file.split('/').slice(0, file.split('/').length - 1).join('/')), { 70 | recursive: true 71 | }); 72 | 73 | await fs.promises.writeFile(path.join(__dirname, '/mirror/', file), Buffer.from(await (await fetch(this.mapPath + file)).arrayBuffer())); 74 | }); 75 | 76 | await fs.promises.writeFile(path.join(__dirname, 'mirror/update.json'), JSON.stringify({ 77 | commit: { 78 | sha: this.latestCommit.sha, 79 | message: this.latestCommit.commit.message 80 | }, 81 | version: this.package.version, 82 | updated: new Date().toISOString(), 83 | package: this.package 84 | })); 85 | 86 | resolve(); 87 | } else { 88 | const updateFile = JSON.parse(fs.readFileSync(path.join(__dirname, 'mirror/update.json'))); 89 | const fileMap = []; 90 | 91 | fs.readdirSync(path.join(__dirname, 'mirror/'), { 92 | recursive: true 93 | }).forEach(file => { 94 | if (fs.lstatSync(path.join(__dirname, 'mirror/', file)).isFile()) fileMap.push(file); 95 | }); 96 | 97 | if (this.latestCommit.sha !== updateFile.commit.sha || this.package.version !== updateFile.version || fileMap !== this.map) { 98 | this.map.forEach(async file => { 99 | if (file.includes('/')) fs.promises.mkdir(path.join(__dirname, '/mirror/', file.split('/').slice(0, file.split('/').length - 1).join('/')), { 100 | recursive: true 101 | }); 102 | 103 | await fs.promises.writeFile(path.join(__dirname, '/mirror/', file), Buffer.from(await (await fetch(this.mapPath + file)).arrayBuffer())); 104 | }); 105 | 106 | await fs.promises.writeFile(path.join(__dirname, 'mirror/update.json'), JSON.stringify({ 107 | commit: { 108 | sha: this.latestCommit.sha, 109 | message: this.latestCommit.commit.message 110 | }, 111 | version: this.package.version, 112 | updated: new Date().toISOString(), 113 | package: this.package 114 | })); 115 | 116 | resolve(); 117 | } else resolve(); 118 | } 119 | }); 120 | } 121 | } 122 | 123 | export default Mirror; -------------------------------------------------------------------------------- /site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} --------------------------------------------------------------------------------