Compare commits

..

112 commits

Author SHA1 Message Date
rifting
77dbccfc7e
Merge pull request #301 from Avad3/patch-1
Major bug fix
2025-02-07 16:33:41 -06:00
Avad3
b80f4bd053
Major bug fix 2025-02-07 17:01:54 -05:00
MotorTruck1221
14e1f1f935
Fix the .gitignore 2025-01-15 02:25:34 -07:00
MotorTruck1221
527221462b
Apparently the gruvbox image wasn't there 2025-01-15 02:22:40 -07:00
MotorTruck1221
009836647b
Remove the random testing plugin 2025-01-15 02:15:58 -07:00
MotorTruck1221
8b25af88e0
CI: add submodule cloning 2025-01-15 00:30:15 -07:00
MotorTruck1221
de2cde4541
Bump version to 9.1.0 2025-01-15 00:27:27 -07:00
MotorTruck1221
8240668a5c
Revert "."
This reverts commit 8a79e063e8.
2025-01-15 00:22:08 -07:00
MotorTruck1221
8a79e063e8
. 2025-01-15 00:21:06 -07:00
MotorTruck1221
fdc0246c0a
Fix 2025-01-15 00:07:43 -07:00
MotorTruck1221
4cb20539fd
Revert "."
This reverts commit 06d02c42f1.
2025-01-15 00:04:22 -07:00
MotorTruck1221
06d02c42f1
. 2025-01-15 00:03:51 -07:00
MotorTruck1221
660e4c34d9
Merge branch 'logic-rewrite' 2025-01-15 00:01:33 -07:00
MotorTruck1221
d17d008d7e
Add an actual LICENSE to the repo 2025-01-15 00:00:36 -07:00
MotorTruck1221
c5799fb986
Revert "Stupid types"
This reverts commit 2c4ea3ebad.
2025-01-14 23:56:30 -07:00
MotorTruck1221
39f49aa54c
Revert "Main should build now"
This reverts commit b462e42d33.
2025-01-14 23:56:01 -07:00
MotorTruck1221
abd87fafd2
Settings: add the ability to reset your instance 2025-01-14 23:52:11 -07:00
MotorTruck1221
b462e42d33
Main should build now 2025-01-14 16:32:54 -07:00
MotorTruck1221
2c4ea3ebad
Stupid types 2025-01-14 16:31:29 -07:00
MotorTruck1221
389ad9e5ec
Merge pull request #298 from NebulaServices/main
Config: seo disabled by default
2025-01-14 15:10:05 -08:00
MotorTruck1221
0d8fc89bf5
Config: seo disabled by default 2025-01-14 16:09:36 -07:00
MotorTruck1221
ce0781536c
Merge pull request #297 from NebulaServices/main
Config: make sure the SEO field is there
2025-01-14 15:00:09 -08:00
MotorTruck1221
58a6de3a5d
Config: make sure the SEO field is there 2025-01-14 15:58:55 -07:00
MotorTruck1221
eb3a44a759
Regen pnpm-lock 2025-01-14 05:43:13 -07:00
MotorTruck1221
b0f7eb1922
Bye bye nanostores 2025-01-14 05:42:49 -07:00
MotorTruck1221
1a6a5828dc
I was right, the store isn't needed for mobile 2025-01-14 05:37:06 -07:00
MotorTruck1221
76375ad02a
MP, settings an other things are now done. 2025-01-14 04:30:12 -07:00
MotorTruck1221
ddca0f3038
InstalledPlugins 2025-01-12 02:00:11 -07:00
MotorTruck1221
4fe4efd95a
InstalledTheme is done 2025-01-12 01:22:03 -07:00
MotorTruck1221
833b323267
It's closeee... 2025-01-11 06:34:19 -07:00
MotorTruck1221
c1b1a9de4c
Allow the user to reset their instance if they are having troubles 2025-01-10 04:55:37 -07:00
MotorTruck1221
72b0d4bbac
(most of) The settings are done! 2025-01-10 04:23:58 -07:00
MotorTruck1221
91199d3460
Tab settings 2025-01-10 02:20:43 -07:00
MotorTruck1221
c04c83c22a
Functionality is all done! 2025-01-10 01:34:13 -07:00
MotorTruck1221
82e169150e
No more silly globals 2025-01-10 01:27:39 -07:00
MotorTruck1221
1bd272ec8d
No more silly globals 2025-01-10 01:27:22 -07:00
MotorTruck1221
1616fa21f2
A whole bunch of shit 2025-01-07 03:19:52 -07:00
MotorTruck1221
920d9f198f
Games are back! 2025-01-07 00:40:30 -07:00
MotorTruck1221
f82c2de301
. 2025-01-07 00:05:55 -07:00
MotorTruck1221
1e0508ac85
Index page is back 2025-01-07 00:05:21 -07:00
MotorTruck1221
84f8605269
Index page is almost back : D 2025-01-06 05:58:22 -07:00
MotorTruck1221
ef0f7055a0
Ok cool, now time to rewrite the settings (I am dreading this) 2025-01-04 02:46:58 -07:00
MotorTruck1221
5b578806c7
Settings init is close to being ready to do 2025-01-04 02:33:46 -07:00
MotorTruck1221
b06c338c20
This already looks a number times better 2025-01-04 01:17:53 -07:00
MotorTruck1221
9c3ffcc58e
SW stuff is done for now 2025-01-04 00:06:01 -07:00
MotorTruck1221
3ca47ce108
I like .bind() more 2025-01-04 00:05:32 -07:00
MotorTruck1221
702bc64bc3
I like .bind() more 2025-01-04 00:04:59 -07:00
MotorTruck1221
b34b24e783
More changes? 2025-01-03 12:52:58 -07:00
MotorTruck1221
5bd87033e2
Start the change to the new stuff 2025-01-03 05:17:35 -07:00
MotorTruck1221
927b68dab7
Change the way EventHandler works 2025-01-03 05:04:44 -07:00
MotorTruck1221
5aa0169e81
Remove extra file 2025-01-03 04:46:38 -07:00
MotorTruck1221
9ff01d1d7d
SW shit 2025-01-03 04:45:42 -07:00
MotorTruck1221
16f1976e28
Man, do I love JSDoc 2025-01-01 05:23:10 -07:00
MotorTruck1221
3d4a8f43aa
Events stuff 2025-01-01 05:15:29 -07:00
MotorTruck1221
5b335d2bff
Start the rewrite (it's already SO much better) 2025-01-01 04:48:46 -07:00
MotorTruck1221
8736491818
Version bump 2024-12-30 00:56:20 -07:00
MotorTruck1221
037a2bf8c3
Scramjet 🚀 2024-12-30 00:55:14 -07:00
MotorTruck1221
fcbf8d2e3a
Remove unneeded dep 2024-12-29 22:46:38 -07:00
MotorTruck1221
cc8a8bf801
Make CatalogCard not use Svelte 2024-12-29 21:35:29 -07:00
MotorTruck1221
bab09be364
Unpin Epoxy 2024-12-28 04:05:09 -07:00
MotorTruck1221
7a0167f31b
Bump deps 2024-12-28 04:02:13 -07:00
MotorTruck1221
47fb761c71
Version bump 2024-12-28 03:58:15 -07:00
MotorTruck1221
18980488c6
Bugfix: themes with caps don't uninstall properly 2024-12-28 03:56:49 -07:00
MotorTruck1221
3e844696b0
That component is no longer needed 2024-12-28 03:49:18 -07:00
MotorTruck1221
4bbe97f887
Installed plugins 2024-12-28 03:47:38 -07:00
MotorTruck1221
a9b94c27d4
Don't fetch the data unless on the page 2024-12-28 03:41:11 -07:00
MotorTruck1221
d2b05a1b1b
Remove the svelte component from InstalledThemes 2024-12-28 03:25:12 -07:00
MotorTruck1221
a03f7d111b
Form data isn't needed like at all 2024-12-04 15:16:06 -07:00
MotorTruck1221
87ce59dab6
Why do I have ora as a package? 2024-12-04 15:14:55 -07:00
MotorTruck1221
77ec7e5a56
Remove concurrently 2024-12-04 15:14:33 -07:00
MotorTruck1221
6de99225c4
More SEO garbage 2024-12-04 05:14:54 -07:00
MotorTruck1221
6c0cbf2737
Fix the Svlete stuff, time to 'trim the fat' 2024-12-04 05:06:45 -07:00
MotorTruck1221
b0fca23a20
Ok, fixed that. NOW I need to update all of the effing svelte components 2024-12-04 04:32:55 -07:00
MotorTruck1221
96a904db0d
robots.txt, sitemap 2024-12-04 03:04:45 -07:00
MotorTruck1221
41c109416f
Some SEO stuff 2024-12-04 02:20:39 -07:00
rifting
208b37ce75
this should not be enabled by default 2024-12-02 09:02:33 -06:00
rifting
9bd33020ae
Merge pull request #294 from MadjikDotPng/patch-2
Make the favicon ourple Saturn.
2024-11-21 14:48:20 -06:00
Madjik
e506f5e37e
Make the favicon ourple Saturn. 2024-11-21 14:45:06 -06:00
rifting
89179de230
Merge pull request #292 from PrimeKidMN/main
Update README.md
2024-11-20 18:27:28 -06:00
Possibly Mason N.
d235862eb2
Update README.md 2024-11-20 09:05:36 -05:00
MotorTruck1221
2226483ed3
Deps bump 2024-11-16 02:14:35 -07:00
MotorTruck1221
ed7455985a
Remove the open in settings from the proxy page 2024-11-16 02:13:12 -07:00
rifting
42a9018e87
Update [...packageName].astro 2024-11-12 17:29:26 -06:00
rifting
2e9584e39e
Update Layout.astro 2024-11-12 17:22:33 -06:00
rift
4ab3de409e theme image embed 2024-11-10 22:25:59 -06:00
rift
aab156560f Add embed theme support 2024-11-10 15:53:14 -06:00
rifting
3e09ab61a5
Update README.md 2024-11-09 14:55:21 -06:00
MotorTruck1221
cad7ad2ceb
Version bump 2024-11-09 01:31:46 -07:00
MotorTruck1221
af5352eba2
WHOOPS 2024-11-09 01:28:25 -07:00
MotorTruck1221
20cb82ec7b
Rift, try this 2024-11-09 01:22:03 -07:00
MotorTruck1221
c9e92d92f8
Feat: custom wisp server urls 2024-11-09 01:18:58 -07:00
rifting
9bc00baa38
increase file size limit for marketplace to 25 MB 2024-11-08 17:11:08 -06:00
MotorTruck1221
0d1d6e5490
Switch to bare client for omnibox, remove libcurl as a dep 2024-11-08 03:10:16 -07:00
MotorTruck1221
32a7cc3b7c
Catalog: better mobile support... 2 2024-11-08 03:10:16 -07:00
MotorTruck1221
35f212056f
Catalog: better mobile support 2024-11-08 03:10:15 -07:00
MotorTruck1221
70bca2737c
Fix firefox, thanks @ading2210 2024-11-08 03:10:15 -07:00
rifting
3d57ffe3c2
Merge pull request #283 from 3kho/main
Grammar fixes and stuff
2024-11-07 08:11:14 -06:00
Echo
267432bb28
Fix naming 2024-11-07 09:03:31 -05:00
Echo
9a97b7937e
Grammar fixes 2024-11-07 09:02:52 -05:00
MotorTruck1221
0c2011f070
Bump version to 9.0.1 2024-11-06 21:08:45 -07:00
MotorTruck1221
d662a38ac7
It was fucking ASTROS fault 2024-11-06 21:04:30 -07:00
MotorTruck1221
826d37a374
AUGHGHG 2024-11-06 20:47:45 -07:00
MotorTruck1221
c1b9432a2b
Ok, let's seee 2024-11-06 20:42:19 -07:00
MotorTruck1221
366eca019d
HMMM 2024-11-06 20:36:39 -07:00
MotorTruck1221
fcf0bbb1d9
Yeahh I think I know what's going on... 2024-11-06 20:32:25 -07:00
MotorTruck1221
2519ed3d8c
OH MY HELLL 2024-11-06 20:05:32 -07:00
MotorTruck1221
feb8f6d0f5
Could CI just like... work? 2024-11-06 20:04:50 -07:00
MotorTruck1221
8a1a8abae7
MORE CI 2024-11-06 20:03:22 -07:00
MotorTruck1221
105b58a5c4
Hell 2024-11-06 19:59:53 -07:00
MotorTruck1221
382d5d0030
Whats breaking here? 2024-11-06 19:56:53 -07:00
rift
a62534618b Basic tab UI (NOT MOBILE READY) 2024-01-12 13:51:14 -06:00
rift
b38a597b7e
Merge pull request #209 from NebulaServices/main
q
2024-01-12 12:22:15 -06:00
141 changed files with 14019 additions and 6097 deletions

12
.changeset/config.json Normal file
View file

@ -0,0 +1,12 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "nebulaservices/nebula" }],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"privatePackages": { "version": true, "tag": true }
}

View file

@ -1,12 +0,0 @@
FROM node:18
# Install basic development tools
RUN apt update && apt install -y less man-db sudo
# Ensure default `node` user has access to `sudo`
ARG USERNAME=node
RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
# Set `DEVCONTAINER` environment variable to help with orientation
ENV DEVCONTAINER=true

View file

@ -1,9 +0,0 @@
// See https://containers.dev/implementors/json_reference/ for configuration reference
{
"name": "Nebula",
"build": {
"dockerfile": "Dockerfile"
},
"remoteUser": "node",
"postCreateCommand": "npm install"
}

17
.dockerignore Normal file
View file

@ -0,0 +1,17 @@
node_modules/
.vscode
npm-debug.log
yarn-error.log
.github/
.env.example
.env
dist/
.git/
.astro/
~/
.gitignore
biome.json
docker-compose.yml
Dockerfile
README.md
db/

4
.github/FUNDING.yml vendored
View file

@ -1,4 +0,0 @@
# These are supported funding model platforms
patreon: nebuladevs
ko_fi: nebulaa

View file

@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

50
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,50 @@
name: Build Docker image
on:
push:
tags:
- v*
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
name: Build and push Docker image to registry
runs-on: ubuntu-latest
if: github.repository_owner == 'nebulaservices'
permissions:
contents: write
packages: write
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
submodules: true
- name: Setup docker buildx
uses: docker/setup-buildx-action@v3
- name: Login To registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/nebulaservice/nebula
- name: Build and push
id: build-and-push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
file: ./Dockerfile
name: nebula
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

50
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,50 @@
name: Release
on:
push:
branches:
- main
workflow_dispatch:
defaults:
run:
shell: bash
env:
FORCE_COLOR: true
jobs:
changelog:
name: Release TAG
if: ${{ github.repository_owner == 'nebulaservices' }}
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Setup PNPM
uses: pnpm/action-setup@v3
with:
version: 9.1.1
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Create Release Pull Request or Publish
id: changesets
uses: changesets/action@v1
with:
version: pnpm run version
publish: pnpm exec changeset publish
commit: "[ci] release"
title: "[ci] release"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

45
.gitignore vendored
View file

@ -1,10 +1,43 @@
# build output
dist/
server/*.js
# generated types
.astro/
# dependencies # dependencies
/node_modules node_modules/
package-lock.json
# System Files #external assets
database_assets/*
!database_assets/com.nebula.gruvbox/
!database_assets/com.nebula.lightTheme/
!database_assets/com.nebula.oled/
!database_assets/com.nebula.retro/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store .DS_Store
Thumbs.db
.breakpoints
memory.txt # jetbrains setting folder
old.app.js .idea/
# nebula catalog database
database.sqlite
# YOUR config
config.toml
# Goofy PNPM problem
~/

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "workerware"]
path = workerware
url = https://github.com/mercuryworkshop/workerware

View file

@ -1 +0,0 @@
node_modules

View file

@ -1,6 +0,0 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": false,
"trailingComma": "none"
}

4
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

10
.vscode/settings.json vendored
View file

@ -1,10 +0,0 @@
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
}
}

36
CHANGELOG.md Normal file
View file

@ -0,0 +1,36 @@
# v9.0.0
- The first release of Nebula V9! And with it bring a whole host of changes:
- More stable the V8
- Adds a Marketplace where users can create their own themes & plugins
- Switches to Astro for speed
- Other general bug fixes
# v9.0.1
- Bumps dependencies
- Fixes bugs
# 9.0.2
- Adds the ability for a custom wisp server back
- Increases upload fileSize capacity in hopes that bigger files can now be uploaded
# 9.0.3
- Bugfix: themes with caps don't uninstall - fixed
- Rewrite: InstalledThemes and InstalledPlugins are now in Astro instead of Svelte
# 9.0.4
- General Bugfixes
- Removes Svelte except for 2 componenents
- Scramjet :rocket:
# 9.1.0
- Rewrites everything in the [utils/](./src/utils) folder
- Bugfixes
- Better logging
- Mobile nav jank is gone
- Component cleanup

View file

@ -1,7 +1,19 @@
FROM node:18 FROM node:22-alpine
WORKDIR /usr/src/app
WORKDIR /app
COPY package*.json .
COPY . . COPY . .
RUN npm install
RUN npm ci RUN apk update
EXPOSE 3000 RUN apk add python3 py3-pip alpine-sdk openssl-dev build-base python3-dev
CMD ["npm", "start"] RUN python3 -m pip install setuptools --break-system-packages
RUN cp -n config.example.toml config.toml
RUN npm i -g pnpm
RUN pnpm install
RUN pnpm run build
RUN export TERM=xterm-256color
VOLUME /app
EXPOSE 8080
ENTRYPOINT ["pnpm"]
CMD ["start", "--color"]

View file

@ -1,7 +1,7 @@
GNU AFFERO GENERAL PUBLIC LICENSE GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007 Version 3, 19 November 2007
Copyright (C) 2022 Nebula Services Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
@ -658,4 +658,4 @@ specific requirements.
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.

537
README.md
View file

@ -1,214 +1,371 @@
# Nebula <div align="center">
<img src="https://socialify.git.ci/nebulaservices/nebula/image?description=1&font=Inter&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Dark" alt="ruby" width="640" height="320" />
NebulaWeb is an official flagship of Nebula Services and Nebula Developer Labs. NebulaWeb is a stunning, sleek, and functional web-proxy with support for thousands of popular sites. With NebulaWeb, the sky is the limit. <img alt="repo size" src="https://img.shields.io/github/repo-size/nebulaservices/nebula?style=for-the-badge"></img>
<img alt="website status" src="https://img.shields.io/website?url=https%3A%2F%2Fnebulaproxy.io&style=for-the-badge"></img>
<img alt="commit a week" src="https://img.shields.io/github/commit-activity/w/nebulaservices/nebula?style=for-the-badge"></img>
Quick note: If you are a system administrator, and have questions, our email is always open `chloe@nebulaproxy.io` </div>
<div align="center">
<h2>Get Started</h2>
<a>To get started, press one of the buttons below to deploy Nebula</a>
<br />
<br />
<a href="#terminal">
<img src="https://img.shields.io/badge/terminal-%23121011.svg?style=for-the-badge&logo=gnu-bash&logoColor=white" alt="Terminal">
</img>
</a>
<a href="#docker">
<img src="https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white" alt="Docker">
</img>
</a>
</div>
## NOTE:
![license](https://img.shields.io/badge/License-GNU%20AGPL%20v3-blue) - This will **NOT** deploy on GitHub Pages, Netlify, Vercel, Gitlab Pages, or any other _static_ host
- This will **NOT** work on Render
---
![chat](https://img.shields.io/badge/chat-1139%20online-brightgreen) ## How to get links
[![Nebula Services Discord](https://invidget.switchblade.xyz/unblocker?theme=darl)](https://discord.gg/unblocker)
[![Titanium Network Discord](https://invidget.switchblade.xyz/unblock?theme=dark)](https://discord.gg/unblock)
---
## Features ## Features
- Stunning and highly functional UI with multiple themes - Multiple Proxy "Backends":
- XOR/b64 encoding all traffic - [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)
- Hides your IP from sites - [RammerHead](https://github.com/binary-person/rammerhead)
- [List of officially supported sites](https://github.com/NebulaServices/Nebula/blob/dev/docs/officially-supported-sites.md)
- _limited_ mobile support
- Stealth Mode (buffed `about:blank` cloaking)
- **NEW** Clickoff cloaking
- **NEW** Email OTP verification
# Deployment
Table of contents
- Quick & easy deployment
- Deployment configuration explaination
- how to use email OTP Verification mode
- Advanced Deployment
- Filesystem
## Quick & Easy Deployment Options
## PLEASE READ THIS NOTICE: IF YOU ARE USING NODE VERSION 20 (HEROKU, RAILWAY), YOU WILL NEED TO TEMPORARILY DOWNGRADE TO 19 OR EARLIER.
[![Deploy to IBM Cloud](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/master/buttons/remade/ibmcloud.svg)](https://cloud.ibm.com/devops/setup/deploy?repository=https://github.com/NebulaServices/Nebula)
<br>
[![Deploy to Amplify Console](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/master/buttons/remade/amplifyconsole.svg)](https://console.aws.amazon.com/amplify/home#/deploy?repo=https://github.com/NebulaServices/Nebula)
<br>
[![Run on Google Cloud](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/master/buttons/remade/googlecloud.svg)](https://deploy.cloud.run/?git_repo=https://github.com/NebulaServices/Nebula)
<br>
[![Deploy on Railway](https://binbashbanana.github.io/deploy-buttons/buttons/remade/railway.svg)](https://railway.app/new/template/pBzeiN)
<br>
[![Deploy To Koyeb](https://binbashbanana.github.io/deploy-buttons/buttons/remade/koyeb.svg)](https://app.koyeb.com/deploy?type=git&repository=github.com/NebulaServices/Nebula&branch=main&name=NebulaProxy)
--- ---
## Deployment Configuration Guide ## Contributors
(Example configuration with none-json notes)
```json
{
"sendgrid_verification": false,
"sendgrid_options": {
"api_key": "YOUR_SENDGRID_API_KEY",
"sendFromEmail": "THE EMAIL THE CODES WILL BE SENT FROM (MUST BE VERIFIED IN SENDGRID)",
"to_email": "THE EMAIL YOU WANT THE CODES SENT TO"
},
"discord_verification": false,
"webhook_url": "YOUR DISCORD WEBHOOK URL",
"smtp_verification": false,
"smtp_options": {
"to_email": "THE EMAIL YOU WANT THE CODES SENT TO",
"sendFromEmail": "THE EMAIL THE CODES ARE SENT FROM",
"host": "YOUR SMTP HOST",
"port": 465,
"auth": {
"user": "SMTP USER",
"pass": "YOUR PASSWORD"
}
}
}
```
## Email Verification OTP
### What is this?
Email verification is a new and unique feature that we've implemented in the event that someone wants to keep their deployment of Nebula private and secure.
### What does it do
When a user tries to access the website, before allowed access they will be asked for a One time password sent to an email set in the deployment configuration. Once verified, they will have 15 day access to the site.
#### SendGrid Setup Instructions
- Firstly, We need to enable verification within the deployment configuration
- change `"sendgrid_verification":false,` to `"sendgrid_verification":true,` above the SendGrid Section
- _Note: You have to reboot the node app for any changes to take place._
- Now, we need to use an api to send a message
- Make an account at Sendgrid (https://app.sendgrid.com/)
- _Note: It is likely that other versions of Nebula will use a different package to send emails._
- Verify the email you want to recieve emails from (Create a sender identity)
- Go to settings -> Sender authentication and click Verify a Single Sender
- Now, We need to get the API key to connect to the API
- Go to settings -> API Keys -> and make an API key.
- Complete the information in the deployment config `deployment.config.json` under the `sendgrid_options` section such as: sendFromEmail, to_email and api_key
#### Discord Webhook Setup Instructions
- Set discord_verification to true in the deployment configuration.
- Create a channel in a discord server you have admin in.
- Click the Edit Channel button.
- Click Integrations
- Click create web hook and copy the URL.
- Paste it under the `webhook_url` section in the deployment configuration.
#### SMTP Setup Instructions
- Set `smtp_verification` to true.
- Change `to_email` to the email address you want the codes to be sent to.
- Change `sendFromEmail to the email address that is going to send the codes.
- Get the host and port from your email provider's documentation.
- Fill in your username and password under the `user` and `pass` section under auth.
## Advanced Deployment
### Initial configuration
credits to @ProgrammerIn-wonderland for writing this wonderful tutorial (which can also be found in the docs :)
- Create an account at https://www.cloudflare.com/
- Create an account at https://www.freenom.com/ (or any registrars)
- Find a free domain name at Freenom
- Click checkout
- Select (12 Months @ FREE)
- Select "Use DNS"
- Select Use your own DNS
- Go to cloudflare, click add new site, and enter the free domain name
- Select "Free Plan"
- Click continue, ignore DNS
- Copy the name servers cloudflare gives you
- Go back to your Freenom tab, enter in the name servers which cloudflare gave you
- You can keep IP blank
- Click continue
- Click complete order
- Go back to cloudflare tab, click "Check Nameservers"
- Select DNS on your right bar
- Enter in the IP of the server which will be hosting Nebula
- Target will be `@`
- Click Enable proxy (little gray cloud icon, if active its orange)
- Select SSL/TLS in your right bar
- Click "Flexible"
- [Rifting](https://github.com/rifting) - Owner & Maintainer
- [MotorTruck1221](https://motortruck1221.com) - Maintainer
--- ---
### Server configuration
- SSH into the server you'll be using, I'll assume its running Ubuntu 22.04 (though the commands are the same for debian 10+ versions, and Ubuntu versions 20.04+)
- run
```
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - \ &&
sudo apt-get install -y nodejs npm
git clone https://github.com/NebulaServices/Nebula.git
cd Nebula
npm i
npm ci
sudo nohup PORT=80 node . &
```
**Make sure your firewall is configured to let through port 80 traffic!** \
_Note: Server will need to run` cd Nebula && sudo nohup PORT=80 node . &` on reboot_
## File Structure
| **File** | Purpose | |
| -------------------------------- | -------------------------------------------------------------------------------------------------------- | --- |
| `src/index.html` | The main frontend visuals for NebulaWEB. | |
| `src/unv.html` | The verification-required frontend/visuals. | |
| `src/options.html` | The frontend for Nebula's options, settings, and preferences. | |
| `public/resources/v.js` | Client verification system for the OTP system. | |
| `public/resources/nebulamain.js` | All of the DOM/client code for NebulaWEB. Includes options, themeSystem, cloak, stealthengine, and more. | |
| `app.js` | The backend server for Nebula. Contains Nodestatic, Bare, HTTP, and more. | |
## Tech Stack ## Tech Stack
- HTML, JS, CSS - [Astro](https://astro.build)
- Partical.JS (Specifically v4, 5, 6.1 &< only) - [Fastify](https://fastify.dev)
- Ultraviolet (proxy) - [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)
- Osana (proxy) - [RammerHead](https://github.com/binary-person/rammerhead)
- TompHTTP Bare Server Node - [Epoxy](https://github.com/mercuryworkshop/epoxy-tls)
- ExpressJS - [Libcurl.js](https://github.com/ading2210/libcurl.js)
- HTML, CSS, and JavaScript (DUH)
---
## Support ## Catalog/Marketplace
For support, email chloe@nebula.bio or join our discord: discord.gg/unblocker - By default, the marketplace is enabled and uses SQLite
- If you would like to disable the catalog, see [#config](#config)
- For big production instances, I recommend using PostgreSQL rather than SQLite. To do this see [#config](#config)
- To use PostgreSQL via the provided docker-compose files, see [#docker](#docker)
## Demo ### How to make a theme
[Click here to see a demo of Nebula](https://nebulaproxy.io/) - Themes allow you to customize Nebula's *look*.
## Acknowledgements #### Prerequisites:
- Make sure you have our [Discord server](https://discord.gg/unblocker) so you can submit your theme
- [UV (one of the proxies use)](https://github.com/titaniumnetwork-dev/Ultraviolet) ##### Making the themes:
- [Osana (one of the proxies we use)](https://github.com/NebulaServices/Osana)
- [Bare Server Node](https://github.com/tomphttp/bare-server-node)
- [Partical.JS (v4, 5, 6.1 &< only)](https://github.com/VincentGarreau/particles.js)
## License 1. Firstly, copy the CSS vars:
```css
:root {
--background-primary: /*Your stuff here */;
--background-lighter: ;
--navbar-color: ;
--navbar-text-color: ;
--navbar-link-color: ;
--navbar-link-hover-color: ;
--input-text-color: ;
--input-placeholder-color: ;
--input-background-color: ;
--input-border-color: ;
--tab-color: ;
--border-color: ;
}
```
(Nebula's license is now GNU AGPL V3 as of v7.10) > [!NOTE]
Copyright Nebula Services 2021 - Present >
<br> > You can add a custom font as well! To do so, add this to your `:root`
This project uses the AGLP GNU V3 license. >
> ```css
> --font-family: /* Font family name */;
> ```
>
> And this to the bottom of your CSS file/submission:
> ```css
> @font-face {
> font-family: /* Name */;
> src: url(/* Where the font is located! Local or external work! */);
> }
> ```
>
> A good example of using a custom font is the built-in `retro` theme [here](./database_assets/com.nebula.retro)
2. Add your colors and test! (Either with a self-hosted version of Nebula OR via a live preview (no clue when this will happen)
3. Once you're satisfied with the colors, submit your theme to the [Discord Server](https://discord.gg/unblocker)!
---
### How to make a plugin
- Plugins extend the functionality of either the proxied page(s) or the service worker.
- This guide provides an incredibly basic example of how to make either.
#### Prerequisites:
- Make sure you have joined our [Discord server](https://discord.gg/unblocker) so you can submit your plugin.
- Some knowledge of JS/TS
##### Serviceworker plugin:
- These plugins are handled by Workerware see [here](https://github.com/mercuryworkshop/workerware) for docs.
1. Create an index.js (or other file name) file:
```bash
touch index.js
```
2. Edit that file to include either of these:
- Code encased in a string:
```js
function setup() {
// This function MUST return the following attributes:
return {
function: `console.log('Example code.')`,
name: 'com.example', // Technically, it could be named anything. It is recommended to use the same name for everything (name when submitting and this)
events: ['fetch'] // See: https://github.com/mercuryworkshop/workerware for the event types you can use. (Also typed if you are using typescript)
}
}
//This can be named anything. However, it's recommended to use `entryFunc` (with types, the naming IS enforced)
self.entryFunc = setup; //DO NOT call the function here. Only assign the reference otherwise, it will error.
```
- Code in an arrow function:
```js
const example = () => {
console.log('Example code')
}
function setup() {
//This function MUST return the following attributes:
return {
function: example, //Do not call the function, only assign the reference to the function.
name: 'com.example', // Technicall could be name anything. Recommended to use the same name for everything (name when submitting and this)
event: ['fetch'] // Se https://github.com/mercuryworkshop/workerware for the event types you can use. (Also typed if using typescript)
}
}
//This can be named anything. However, it's recommended to use `entryFunc` (with types, the naming IS enforced)
self.entryFunc = setup; //DO NOT call the function here. Only assign the reference; otherwise, it will result in an error.
```
> [!WARNING]
> The only *allowed* way to pass code to the `function` param is either a string or an arrow function. Named functions ***WILL NOT WORK***.
>
> Example of a named function: `function example() {/* Some form of code */}`.
>
> If a named function is used where it shouldn't be, your plugin will not be approved, nor will it work properly.
3. Submit your plugin in the [Discord](https://discord.gg/unblocker)!
##### Proxied page plugins
- They allow modification of websites that UV proxies, (EX: you could add Vencord to Discord with this)
1. Create an index.js file (or another file name)
```bash
touch index.js
```
2. Edit that file with your code and the following:
```js
//Name this whatever.
function example() {
//You MUST return the following
return {
host: "example.com", //The host to match (so if the user visits example.com it will inject the html below.
html: "<script>console.log('Example')</script>", //Must return a string (and be valid HTML or your plugin will break). How you get that string is up to you
injectTo: "head" // Can be "head" or "body"
}
}
// Technically, this could be named anything, it is recommended to call it `entryFunc`
self.entryFunc = example; //DO NOT run the function here. That will cause errors. Only assign the reference to the function here.
```
3. Submit it in our [Discord](https://discord.gg/unblocker)!
---
## Deployment
### Terminal
Prerequisites:
- Node & npm
- Git
1. Clone the repo:
```bash
git clone https://github.com/nebulaservices/nebula --recursive && cd nebula
```
2. Install all of the dependencies:
```bash
npm i
```
3. Create a `config.toml` file
```bash
cp config.example.toml config.toml
```
4. Modify the `config.toml` file to your liking (docs [here](#environment))
```
nano config.toml
```
5. Build the front end & server:
```bash
npm run build
```
6. Start the server
```bash
npm start
```
> [!NOTE]
> You can run `npm run bstart` to build and start together
---
### Docker
- There are two ways to deploy with docker:
- [Normal docker](#normal-docker)
- [Docker Compose](#docker-compose)
#### Normal Docker
Prerequisites:
- Git
- Docker
1. Clone the repo (skip if using a prebuilt image):
```bash
git clone https://github.com/nebulaservices/nebula --recursive && cd nebula
```
2. Create an `config.toml` file (if using prebuilt image, copy the example from the repo):
```bash
cp config.example.toml config.toml
```
3. Modify the `config.toml` file to your liking (docs [here](#environment))
```bash
nano config.toml
```
4. Build the docker image (skip if using prebuilt):
```bash
docker build nebula:latest
```
5. Run the docker images:
- Prebuilt:
```bash
docker run -v ./config.toml:/app/config.toml ghcr.io/nebulaservices/nebula:latest
```
- Image you built yourself:
```bash
docker run -v ./config.toml:/app/config.toml nebula:latest
```
#### Docker Compose
Prerequisites:
- Git
- Docker w/compose
1. Clone the repo (skip if using a prebuilt image):
```bash
git clone https://github.com/nebulaservices/nebula --recursive
```
2. Create an `config.toml` file (if using prebuilt image, copy the example from the repo):
```bash
cp config.example.toml config.toml
```
3. Modify the `config.toml` file to your liking (docs on that [here](#environment)]
```bash
nano config.toml
```
4. Build the docker image (skip if using prebuilt):
```bash
docker compose -f ./docker-compose.build.yml build
```
5. Run the docker image:
- Prebuilt:
```bash
docker compose up
```
- Image you built yourself:
```bash
docker compose -f ./docker-compose.build.yml up
```
#### Extra (Postgres)
- To use Postgres over SQLite, uncomment the DB section in the `docker-compose` file (or use your own Postgres DB!). Then, modify the `config.toml` (See: [#config](#config) for knowledge on how to do this)
- To use Postgres over SQLite in a normal docker environment (no compose), you'll have to set one up and then modify the `config.toml` to use it. (See: [#config](#config) for knowledge on how to do this)
---
## Config
There are a couple of configuration options for Nebula. The defaults are fine most of the time, but there are instances where you may not want certain options enabled or certain things running.
- An example config file is located [here](./config.example.toml).
- Config format is in TOML
| Variable | Description | Type | Default |
|:----------:|:-------------:|:------:|:---------:|
| `marketplace` | The options below are for the marketplace section | `object` | N/A |
| `enabled` | Enable marketplace functionality | `boolean` | `true` |
| `psk` | The password and authentication key for the marketplace. ***CHANGE FROM DEFAULT*** | `string` | `CHANGEME` |
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
| `db` | The below options are for the db (database) section | `object` | N/A |
| `name` | The database name to use | `string` | `database` |
| `username` | The username for the DB | `string` | `username` |
| `password` | The database password. ***CHANGE FROM DEFAULT VALUE*** | `string` | `password` |
| `postgres` | Whether to use postgres over sqlite *(recommended for large production instances)* | `boolean` | `false` |
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
| `postgres` | The below options are for the postgres section. (Only worry about this if you enabled postgres in the db section.) | `object` | N/A |
| `domain` | Either the TLD or the IP address of your postgres server. | `string` | `''` |
| `port` | The port your postgres server is listening on | `number` | `5432` |
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
| `server.server` | The below options are to configure the server. | `object` | N/A |
| `port` | What port the server should listen on. *(Note: Can also be configured via environment variable `PORT`)* | `number` | `8080` |
| `wisp` | Whether the server should use the inbuilt wisp server. (Disabled if your using an external wisp server) | `boolean` | `true` |
| `logging` | Whether or not to enable logging. *Note: Logs are massive* | `boolean` | `true` |
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
## Deploying
### Koyeb
- First setup the config.toml file with the docker-compose instructions!
- Fork this repo
- Create new koyeb service, and select webservice
- Select import from github and import your forked repo
- Change package to dockerfile and press deploy!

249
app.js
View file

@ -1,249 +0,0 @@
import express from "express";
import cookieParser from "cookie-parser";
import http from "node:http";
import createBareServer from "@tomphttp/bare-server-node";
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
import path from "node:path";
import config from "./deployment.config.json" assert { type: "json" };
import sgMail from "@sendgrid/mail";
import nodemailer from "nodemailer";
import * as uuid from "uuid";
import fs from "node:fs";
import bcrypt from "bcrypt";
// fx
const PORT = process.env.PORT || 3000;
const __dirname = process.cwd();
const ACTIVE_CODES = new Set();
if (!fs.existsSync("./tmp/memory.txt")) {
fs.writeFileSync("./tmp/memory.txt", "", "utf-8");
}
let TOKENS = fs
.readFileSync("./tmp/memory.txt", "utf-8")
.trim()
.split("\n")
.map((token) => {
const parts = token.split(":");
return {
id: parts[0],
token: parts[1],
expiration: parts[2]
};
});
const server = http.createServer();
const app = express(server);
const bareServer = createBareServer("/bare/");
// Middleware
app.use(cookieParser());
app.use(express.json());
app.use(
express.urlencoded({
extended: true
})
);
// Verification
app.patch("/generate-otp", async (req, res) => {
if (
config.sendgrid_verification ||
config.discord_verification ||
config.smtp_verificaton
) {
const OTP = generateCode();
ACTIVE_CODES.add(OTP);
setTimeout(() => {
ACTIVE_CODES.delete(OTP);
}, 1000 * 60 * 5);
let email = {
to: "",
from: "",
subject: `NebulaWEB personal access code ${OTP}`,
text: `
####### ACCESS CODE (OTP) ${OTP} #######
####### DO NOT SHARE THIS CODE! #######
(this message is automated)`
};
if (config.sendgrid_verification) {
sgMail.setApiKey(config.sendgrid_options.api_key);
email.to = config.sendgrid_options.to_email;
email.from = config.sendgrid_options.sendFromEmail;
try {
await sgMail.send(msg);
} catch {
return res.status(504).end();
}
}
if (config.smtp_verification) {
const smtpMailerAgent = nodemailer.createTransport(config.smtp_options);
email.to = config.smtp_options.to_email;
email.from = config.smtp_options.sendFromEmail;
try {
smtpMailerAgent.sendMail(email);
} catch {
return res.status(504).end();
}
}
if (config.discord_verification) {
try {
await fetch(config.webhook_url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
content: `Your NebulaWEB access code is \`${OTP}\``
})
});
} catch {
return res.status(500).end();
}
}
res.status(200).end();
} else {
res.status(404).end();
}
});
function generateCode() {
const code = Math.floor(Math.random() * 1000000);
return code.toString().padStart(6, "0");
}
app.post("/validate-otp", (req, res) => {
if (
config.sendgrid_verification ||
config.discord_verification ||
config.smtp_verificaton
) {
const OTP = req.body.otp;
if (ACTIVE_CODES.has(OTP)) {
ACTIVE_CODES.delete(OTP);
const token = uuid.v4();
TOKENS.push({
id: OTP,
token: hash(token),
expiration: Date.now() + 1000 * 60 * 60 * 24 * 30
});
fs.writeFileSync(
"./tmp/memory.txt",
TOKENS.map((token) => {
return `${token.id}:${token.token}:${token.expiration}`;
}).join("\n"),
"utf-8"
);
res.status(200).json({
success: true,
validation: `${OTP}:${token}`
});
} else {
res.status(401).json({
success: false
});
}
} else {
res.status(404).end();
}
});
// Static files
app.use(express.static(path.join(__dirname, "public")));
app.use("/uv/", express.static(uvPath));
// Login route
app.get("/login", (req, res) => {
if (
config.sendgrid_verification ||
config.discord_verification ||
config.smtp_verificaton
) {
res.sendFile(path.join(__dirname, "src", "unv.html"));
} else {
res.redirect("/");
}
});
// General Routes
app.use((req, res, next) => {
if (
config.sendgrid_verification ||
config.discord_verification ||
config.smtp_verificaton
) {
const verification = req.cookies["validation"];
if (!verification || !validateToken(verification)) {
return res.redirect("/login");
}
}
next();
});
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "src", "index.html"));
});
app.get("/options", (req, res) => {
res.sendFile(path.join(__dirname, "src", "options.html"));
});
app.get("/privacy", (req, res) => {
res.sendFile(path.join(__dirname, "src", "privacy.html"));
});
// Bare Server
server.on("request", (req, res) => {
if (bareServer.shouldRoute(req)) {
bareServer.routeRequest(req, res);
} else {
app(req, res);
}
});
server.on("upgrade", (req, socket, head) => {
if (bareServer.shouldRoute(req)) {
bareServer.routeUpgrade(req, socket, head);
} else {
socket.end();
}
});
server.on("listening", () => {
console.log(`Server running at http://localhost:${PORT}/.`);
});
server.listen({
port: PORT
});
function hash(token) {
const salt = bcrypt.genSaltSync(10);
return bcrypt.hashSync(token, salt);
}
function validateToken(verification) {
const [id, token] = verification.split(":");
const tokenData = TOKENS.find((token) => token.id == id);
if (!tokenData) {
return false;
}
if (tokenData.expiration < Date.now()) {
return false;
}
return bcrypt.compareSync(token, tokenData.token);
}

View file

@ -1,7 +0,0 @@
{
"name": "NebulaWEB",
"description": "Explore the web. Freely. ",
"repository": "https://github.com/NebulaServices/Nebula",
"logo": "https://avatars.githubusercontent.com/u/86420004?v=4",
"keywords": ["educational", "science", "math"]
}

133
astro.config.ts Normal file
View file

@ -0,0 +1,133 @@
import { fileURLToPath } from "node:url";
import node from "@astrojs/node";
import svelte from "@astrojs/svelte";
import tailwind from "@astrojs/tailwind";
import { baremuxPath } from "@mercuryworkshop/bare-mux/node";
import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
import playformCompress from "@playform/compress";
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
import { scramjetPath } from "@mercuryworkshop/scramjet";
import icon from "astro-icon";
import { defineConfig, envField } from "astro/config";
import { viteStaticCopy } from "vite-plugin-static-copy";
import { version } from "./package.json";
import { parsedDoc } from "./server/config.js";
const workerwarePath = fileURLToPath(new URL("./workerware/src", import.meta.url));
export default defineConfig({
site: parsedDoc.seo.enabled ? parsedDoc.seo.domain || process.env.SITE : 'http://localhost:4321',
env: {
schema: {
VERSION: envField.string({
context: "client",
access: "public",
optional: true,
default: version
}),
MARKETPLACE_ENABLED: envField.boolean({
context: "client",
access: "public",
optional: true,
default: parsedDoc.marketplace.enabled
}),
SEO: envField.string({
context: "client",
access: "public",
optional: true,
default: JSON.stringify({
enabled: parsedDoc.seo.enabled,
domain: new URL(parsedDoc.seo.domain).host
})
})
}
},
integrations: [
tailwind(),
//sitemap(),
icon(),
svelte(),
playformCompress({
CSS: false,
HTML: true,
Image: true,
JavaScript: true,
SVG: true
})
],
vite: {
plugins: [
viteStaticCopy({
targets: [
{
src: `${uvPath}/**/*`.replace(/\\/g, "/"),
dest: "uv",
overwrite: false
},
{
src: `${epoxyPath}/**/*`.replace(/\\/g, "/"),
dest: "epoxy",
overwrite: false
},
{
src: `${libcurlPath}/**/*`.replace(/\\/g, "/"),
dest: "libcurl",
overwrite: false
},
{
src: `${scramjetPath}/**/*`.replace(/\\/g, "/"),
dest: "scram",
overwrite: false
},
{
src: `${baremuxPath}/**/*`.replace(/\\/g, "/"),
dest: "baremux",
overwrite: false
},
{
src: `${workerwarePath}/**/*`.replace(/\\/g, "/"),
dest: "workerware",
overwrite: false
}
]
})
],
server: {
proxy: {
"/api/catalog-stats": {
target: "http://localhost:8080/api/catalog-stats",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/catalog-stats/, "")
},
"/api/catalog-assets": {
target: "http://localhost:8080/api/catalog-assets",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/catalog-assets/, "")
},
"/api/packages": {
target: "http://localhost:8080/api/packages",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/packages/, "")
},
"/packages": {
target: "http://localhost:8080",
changeOrigin: true
},
"/wisp/": {
target: "ws://localhost:8080/wisp/",
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(/^\/wisp\//, "")
},
"/styles": {
target: "http://localhost:8080",
changeOrigin: true
}
}
}
},
output: "server",
adapter: node({
mode: "middleware"
})
});

32
biome.json Normal file
View file

@ -0,0 +1,32 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
"files": {
"ignore": ["~/", "**/dist/**", ".github/**"],
"include": ["**/**", "server/**"]
},
"formatter": {
"indentStyle": "space",
"indentWidth": 4,
"lineWidth": 100,
"ignore": ["pnpm-lock.yaml", "package.json"]
},
"organizeImports": { "enabled": true },
"linter": { "enabled": false },
"javascript": {
"formatter": {
"trailingCommas": "none",
"quoteStyle": "double",
"semicolons": "always"
}
},
"json": {
"parser": {
"allowComments": true,
"allowTrailingCommas": true
},
"formatter": {
"indentStyle": "space",
"trailingCommas": "none"
}
}
}

23
config.example.toml Normal file
View file

@ -0,0 +1,23 @@
[marketplace]
enabled = false # Turn on or off the marketplace entirely
psk = "CHANGEME" # Change this to something more secure.
level = 1
[seo]
enabled = false
domain = "https://nebulaproxy.io"
[db]
name = "database" # Your database name
username = "username" # The username of your DB (SQLITE just ignores this)
password = "password" # The password to your DB (SQLITE ignores this)
postgres = false # Enable to use postgres over sqlite (recommended for large prod instances)
[postgres] # Set the "domain" to either and ip address or a actual domain
domain = ""
port = 5432
[server.server]
port = 8080
wisp = true
logging = true # Disable for the tons & tons of logs to go away (useful for debugging but otherwise eh)

View file

@ -0,0 +1,22 @@
:root {
--background-primary: #282828;
--background-lighter: #3c3836;
--navbar-color: #504945;
--navbar-height: 60px;
--navbar-text-color: #fbf1c7;
--navbar-link-color: #ebdbb2;
--navbar-link-hover-color: #fabd2f;
--navbar-font: "Roboto", sans-serif;
--input-text-color: #b8bb26;
--input-placeholder-color: #928374;
--input-background-color: #1d2021;
--input-border-color: #b8bb26;
--input-border-size: 1.3px;
--navbar-logo-filter: none;
--dropdown-option-hover-color: #665c54;
--tab-color: #1d2021;
--border-color: #b8bb26;
--highlight-color: #fe8019;
--accent-color: #83a598;
--secondary-text-color: #d3869b;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View file

@ -0,0 +1,18 @@
:root {
--background-primary: hsl(310 50% 90%);
--background-lighter: hsl(310 50% 90%);
--navbar-color: hsl(310 50% 100%);
--navbar-height: 60px;
--navbar-text-color: hsl(310 50% 15%);
--navbar-link-color: hsl(310 50% 15%);
--navbar-link-hover-color: hsl(310 50% 90%);
--navbar-font: "Roboto";
--input-text-color: hsl(310 50% 15%);
--input-placeholder-color: white;
--input-background-color: hsl(310 50% 100%);
--input-border-color: hsl(310 50% 25%);
--input-border-size: 1.3px;
--navbar-logo-filter: none;
--tab-color: var(--black);
--border-color: hsl(310 50% 25%);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -0,0 +1,19 @@
:root {
--background-primary: #000000;
--background-lighter: #000000;
--navbar-color: #00000f;
--navbar-height: 60px;
--navbar-text-color: #4763ff;
--navbar-link-color: #4763ff;
--navbar-link-hover-color: gray;
--navbar-font: "Roboto";
--input-text-color: #4763ff;
--input-placeholder-color: white;
--input-background-color: #000000;
--input-border-color: #4763ff;
--input-border-size: 1.3px;
--navbar-logo-filter: none;
--dropdown-option-hover-color: #000000;
--tab-color: #000000;
--border-color: #4763ff;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,24 @@
:root {
--background-primary: #000000;
--background-lighter: #000000;
--navbar-color: #020805;
--navbar-height: 60px;
--navbar-text-color: #3cb371;
--navbar-link-color: #3cb371;
--navbar-link-hover-color: white;
--input-text-color: #3cb371;
--input-placeholder-color: white;
--input-background-color: #000000;
--input-border-color: #3cb371;
--input-border-size: 1.3px;
--navbar-logo-filter: none;
--dropdown-option-hover-color: #000000;
--tab-color: #000000;
--border-color: #3cb371;
--font-family: "terminal";
}
@font-face {
font-family: terminal;
src: url("./terminal.ttf");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

View file

@ -1,23 +0,0 @@
{
"sendgrid_verification": false,
"sendgrid_options": {
"api_key": "YOUR_SENDGRID_API_KEY",
"sendFromEmail": "THE EMAIL THE CODES WILL BE SENT FROM (MUST BE VERIFIED IN SENDGRID)",
"to_email": "THE EMAIL YOU WANT THE CODES SENT TO"
},
"discord_verification": false,
"webhook_url": "YOUR DISCORD WEBHOOK URL",
"smtp_verification": false,
"smtp_options": {
"to_email": "THE EMAIL YOU WANT THE CODES SENT TO",
"sendFromEmail": "THE EMAIL THE CODES ARE SENT FROM",
"host": "YOUR SMTP HOST",
"port": 465,
"auth": {
"user": "SMTP USER",
"pass": "YOUR PASSWORD"
}
}
}

21
docker-compose.build.yml Normal file
View file

@ -0,0 +1,21 @@
services:
nebula:
image: ghcr.io/nebulaservices/nebula:latest
container_name: nebula
build: .
restart: unless-stopped
ports:
# HOST:CONTAINER (DO NOT CHANGE THE CONTAINER PORT, UNLESS EDITED IN THE config.toml FILE)
- 8080:8080
volumes:
- ./config.toml:/app/config.toml
# Uncomment the the below stuff to use POSTGRES!
# db:
# image: postgres
# restart: unless-stopped
# environment:
# POSTGRES_PASSWORD: password #CHANGE THIS
# POSTGRES_USER: username
# POSTGRES_DB: db
# volumes:
# - ./db:/var/lib/postgresql/data

View file

@ -1,10 +1,20 @@
version: "3"
services: services:
nebula: nebula:
image: nebula:latest image: ghcr.io/nebulaservices/nebula:latest
build: .
container_name: nebula container_name: nebula
restart: unless-stopped restart: unless-stopped
ports: ports:
# DO NOT CHANGE 3000! # HOST:CONTAINER (DO NOT CHANGE THE CONTAINER PORT, UNLESS EDITED IN THE config.toml FILE)
- your port here:3000 - 8080:8080
volumes:
- ./config.toml:/app/config.toml
# Uncomment the the below stuff to use POSTGRES!
# db:
# image: postgres
# restart: unless-stopped
# environment:
# POSTGRES_PASSWORD: password #CHANGE THIS
# POSTGRES_USER: username
# POSTGRES_DB: db
# volumes:
# - ./db:/var/lib/postgresql/data

View file

@ -1,8 +0,0 @@
module.exports = {
apps: [
{
name: "Site",
script: "proxysocks node app.js"
}
]
};

2551
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,28 +1,66 @@
{ {
"name": "nebula-web", "name": "nebula",
"version": "7.11.6",
"description": "Explore the web. Freely.",
"type": "module", "type": "module",
"main": "app.js", "version": "9.1.0",
"private": true,
"scripts": { "scripts": {
"start": "node app.js" "dev": "astro dev --host 0.0.0.0 & tsx --watch server/server.ts",
"start": "node server/server.js",
"build:server": "tsc -p server",
"build:client": "astro check && astro build",
"build": "npm run build:server & npm run build:client",
"bstart": "npm run build && npm run start",
"preview": "astro preview",
"astro": "astro",
"format:code": "biome format . --write",
"format:imports": "biome check . --write",
"format": "npm run format:code && npm run format:imports",
"version": "changeset version"
}, },
"keywords": [
"proxy"
],
"author": "Nebula Services",
"license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@sendgrid/mail": "^7.7.0", "@astrojs/check": "^0.9.4",
"@tomphttp/bare-server-node": "^1.2.5", "@astrojs/node": "^9.0.0",
"@titaniumnetwork-dev/ultraviolet": "^1.0.11", "@astrojs/sitemap": "^3.2.1",
"bcrypt": "^5.1.0", "@astrojs/svelte": "^7.0.2",
"cookie-parser": "^1.4.6", "@astrojs/tailwind": "^5.1.4",
"express": "^4.18.2", "@fastify/compress": "^8.0.1",
"nodemailer": "^6.9.1", "@fastify/helmet": "^13.0.0",
"uuid": "^9.0.0" "@fastify/middie": "^9.0.2",
"@fastify/multipart": "^9.0.1",
"@fastify/static": "^8.0.3",
"@iconify-json/ph": "^1.2.2",
"@mercuryworkshop/bare-mux": "^2.1.7",
"@mercuryworkshop/epoxy-transport": "^2.1.27",
"@mercuryworkshop/libcurl-transport": "^1.3.15",
"@playform/compress": "^0.1.6",
"@titaniumnetwork-dev/ultraviolet": "^3.2.10",
"@mercuryworkshop/scramjet": "https://github.com/MercuryWorkshop/scramjet/releases/download/latest/mercuryworkshop-scramjet-1.0.2-dev.tgz",
"@types/node": "^22.10.2",
"@types/sequelize": "^4.28.20",
"astro": "^5.1.1",
"astro-icon": "^1.1.5",
"astro-seo": "^0.8.4",
"chalk": "^5.4.1",
"fastify": "^5.2.0",
"gradient-string": "^3.0.0",
"pg": "^8.13.1",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.5",
"smol-toml": "^1.3.1",
"sqlite3": "^5.1.7",
"svelte": "^5.16.0",
"svelte-french-toast": "^1.2.0",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.2",
"vite-plugin-static-copy": "^2.2.0",
"wisp-server-node": "^1.1.7"
}, },
"engines": { "devDependencies": {
"node": "19.x" "@biomejs/biome": "^1.9.4",
"@changesets/cli": "^2.27.11",
"bufferutil": "^4.0.9",
"ora": "^8.1.1",
"sharp": "^0.33.5",
"tsx": "^4.19.2"
} }
} }

8870
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

BIN
public/classic_theme.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
public/cloaks/canvas.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
public/cloaks/classroom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
public/cloaks/google.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

BIN
public/cloaks/ps.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/cloaks/wikipedia.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

16
public/favicon.svg Normal file
View file

@ -0,0 +1,16 @@
<svg
version="1.2"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 400"
width="400"
height="400"
fill="#7967dd"
style="width: 100%; height: 100%;"
><title>nebula</title><g id="svgg"
><path
id="path0"
fill="#7967dd"
d="m213.6 84c1 0.3 3.4 0.7 5.1 1 1.8 0.2 4.1 0.7 5.2 1 13.2 4.1 20.3 6.8 24.5 9.1 0.6 0.3 2.3 1.2 3.8 2 2.8 1.4 13.1 8 14.4 9.2 0.5 0.3 2.3 1.9 4.2 3.5 6.7 5.5 15.5 14.9 19.2 20.4 1 1.4 2 2.7 2.2 2.8 0.3 0.1 0.5 0.5 0.5 0.8 0 0.3 1.2 2.2 2.5 4.3 2.3 3.4 7.8 14.3 9.8 19.3 0.8 2.1 0.9 2.2 10 4.9 5.6 1.6 11.1 3.4 11.7 3.8 0.3 0.2 2.4 1.1 4.7 1.9 11.1 4.1 23 12.5 27.3 19.4 5.5 8.7 3.6 20.5-4.5 28.5-3.1 3-7.5 6.4-8.4 6.4-0.3 0-0.7 0.2-0.8 0.5-1.1 2.3-23.3 11.2-35.9 14.3-3.2 0.9-3.5 1.2-5.7 6.8-7.5 19-25.5 40.6-42.3 51.1-1.6 1-3.1 2-3.3 2.2-0.1 0.2-0.9 0.7-1.7 1.2-0.8 0.4-2.2 1.2-2.9 1.6-0.8 0.4-1.6 1-1.8 1.1-0.5 0.5-4.1 2.2-8.1 3.9-1.8 0.8-3.8 1.6-4.5 2-0.7 0.3-3.1 1.1-5.2 1.8-8.3 2.6-9.8 3-21 4.9-6.4 1.1-25.3 1.3-30.5 0.3-1.9-0.3-5.8-1.1-8.6-1.6-6.8-1.3-12.7-3-20-5.7-3.3-1.2-18-8.8-19.7-10.1-0.9-0.7-4.1-3.1-7.1-5.2-5.7-4.1-17.9-15.9-20.7-20-0.9-1.2-2-2.7-2.5-3.2-3.2-3.3-13.7-21.7-13.7-24.1 0-0.6-0.2-1.2-0.6-1.4-0.3-0.2-0.8-1.2-1-2.3-0.4-1.9-1.7-2.7-6.5-3.8-23.2-5.6-43.1-17.2-48.6-28.5-7.1-14.4 4.5-31.3 27.3-39.7 1.8-0.7 4.1-1.6 5.2-2 3.7-1.5 8-2.9 19.5-6.2 1.6-0.5 2.8-1.2 2.8-1.7 0-2.4 9.8-21.6 13.1-25.7 0.2-0.4 1.4-1.8 2.5-3.2 13.8-18.1 30.2-30.6 50.6-38.8 4.3-1.7 6-2.3 14.3-4.5 5.5-1.6 11.2-2.4 18.4-2.9 7.9-0.5 24-0.2 26.8 0.6zm-29.7 17.3c-0.2 0.1-7.3 1.7-12.9 2.7-1.7 0.4-4.3 1.2-5.8 1.9-1.5 0.6-3.9 1.5-5.5 2-1.5 0.4-3.3 1.3-4 2-0.7 0.7-1.7 1.2-2.3 1.2-1.2 0-9.5 4.5-9.8 5.3-0.2 0.3-0.5 0.6-0.9 0.6-1.9 0-19.6 16-23.8 21.6-9.3 12.2-16.1 27.4-19.2 42.7-2 10.1-1.1 37.5 1.4 41.4 0 0.1 3.7 0.9 8.1 1.8 9.5 1.9 12.8 2.4 34.6 4.9 38.5 4.5 107.9 2.2 138.3-4.5 1.4-0.3 4.1-0.9 6.1-1.3 4.2-0.8 3.4 0.2 4.9-7.1 1.6-8.3 1.7-27 0.1-34.6-1.5-7.1-3.2-13.4-3.7-14.2-0.3-0.4-0.7-1.5-0.9-2.6-2.8-12.1-19.2-34.1-33-44.3-2.9-2.1-5.5-4-5.8-4.3-2.8-2.2-4.9-3.1-7.2-3.1-2.1 0-2.6-0.2-2.7-1.3-0.2-1.5-5.7-4.5-6.3-3.5-0.7 1.1-2.4 0.6-2.7-0.7-0.4-1.3-1.2-1.6-5.8-2.1-1.6-0.2-4-0.9-5.5-1.6-3.9-1.8-5.3-2.2-10.2-2.6-4.6-0.4-25.2-0.7-25.5-0.3zm74.3 42.2c7.4 9.8 4.8 23.5-4.6 24.9-6.9 1-20.9-5.8-21-10.2 0-0.2-0.3-0.8-0.8-1.3-6.4-6.8-5-20.8 2.4-24.1 6.7-2.9 17.2 1.8 24 10.7zm-176.4 36.4c-0.1-0.1-4.6 1.1-5.9 1.6-0.7 0.3-3 1.2-5.1 2-9.9 3.8-15.1 6.8-19.6 11.5-3.4 3.5-3.3 4.5 0.5 8.7 1 1 11.3 7.6 12 7.6 0.2 0 1.7 0.6 3.4 1.3 1.6 0.8 3.6 1.6 4.3 1.9 1.8 0.8 9.3 3.3 9.9 3.3 0.3 0 0.3-2 0-4.4-0.6-5.6-0.6-24.5 0.1-29.6 0.3-2.1 0.5-3.9 0.4-3.9zm229.3-0.3c-0.2 0 0 1 0.2 2.1 0.6 2.8 0.6 31.3 0 34-0.5 2.2-0.4 2.2 1.3 1.8 3.1-0.7 12.9-4.5 18.3-7 8.5-4 14.3-10.1 12.6-13.3-1.1-2.1-6.7-7.2-7.9-7.2-0.4 0-0.9-0.2-1-0.5-0.4-1.1-11.8-6.1-19.2-8.5-2.3-0.7-4.2-1.4-4.3-1.4zm-199.4 63.4l-3.1-0.4 1.8 3.2c0.9 1.8 1.9 3.4 2.2 3.5 0.3 0.1 0.5 0.6 0.5 1.1 0 0.4 0.6 1.5 1.3 2.3 0.7 0.9 1.5 1.9 1.8 2.2 0.3 0.4 0.8 1.2 1.1 1.7 6.2 10.7 35.6 33.5 43.3 33.5 0.2 0 1.3 0.4 2.5 0.9 2.5 1.2 10.6 3.4 15.3 4.2 9.5 1.8 11.6 2.1 17.4 2.1 6.6 0 16.4-1.3 22.9-3 2.2-0.5 5.2-1.3 6.8-1.7 1.6-0.3 3.2-0.9 3.5-1.2 0.4-0.3 1.1-0.6 1.6-0.6 2.3 0 22-10.6 24-12.9 0.2-0.2 2.2-1.9 4.5-3.7 5.7-4.5 11.8-11 17.1-18.4 1.6-2.3 3.2-4.5 3.6-4.9 0.4-0.4 0.7-1 0.7-1.2 0-0.2 0.8-1.9 1.9-3.6 1.1-1.7 1.9-3.2 1.9-3.4 0-0.2-3.8 0.4-11 1.6-31.7 5.4-85.1 6.7-126.9 3.1-9.6-0.8-23.1-2.3-27.8-3.2-2.2-0.4-5.3-0.9-6.9-1.2z"
class="s0"></path></g
></svg
>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

19
public/nebula.css Normal file
View file

@ -0,0 +1,19 @@
:root {
--background-primary: #191724;
--background-lighter: #16121f;
--navbar-color: #26233a;
--navbar-height: 60px;
--navbar-text-color: #7967dd;
--navbar-link-color: #e0def4;
--navbar-link-hover-color: gray;
--navbar-font: "Roboto";
--input-text-color: #e0def4;
--input-placeholder-color: white;
--input-background-color: #1f1d2e;
--input-border-color: #eb6f92;
--input-border-size: 1.3px;
--navbar-logo-filter: none;
--dropdown-option-hover-color: #312a49;
--tab-color: var(--black);
--border-color: #16121f;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
(()=>{"use strict";self.__osana$config={bare:`${location.origin}/bare/`,prefix:"/service/~osana/",codec:self.__osana$bundle.codecs.none,files:{config:"/osana/osana.config.js",client:"/osana/osana.client.js",bundle:"/osana/osana.bundle.js",worker:"/osana/osana.worker.js"},blacklist:[/^(www\.)?netflix\.com/,/^accounts\.google\.com/]}})();
//# sourceMappingURL=osana.config.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"osana.config.js","mappings":"mBAAAA,KAAKC,eAAiB,CAClBC,KAAM,GAAGC,SAASC,eAClBC,OAAQ,WACRC,MAAON,KAAKO,eAAeC,OAAOC,KAClCC,MAAO,CACHC,OAAQ,mBACRC,OAAQ,mBACRC,OAAQ,mBACRC,OAAQ,oBAEZC,UAAW,CACP,wBACA,0B","sources":["webpack://osana/./src/config.ts"],"sourcesContent":["self.__osana$config = {\n bare: `${location.origin}/bare/`,\n prefix: \"/~osana/\",\n codec: self.__osana$bundle.codecs.none,\n files: {\n config: \"/osana.config.js\",\n client: \"/osana.client.js\",\n bundle: \"/osana.bundle.js\",\n worker: \"/osana.worker.js\"\n },\n blacklist: [\n /^(www\\.)?netflix\\.com/,\n /^accounts\\.google\\.com/,\n ]\n};\nexport {};\n"],"names":["self","__osana$config","bare","location","origin","prefix","codec","__osana$bundle","codecs","none","files","config","client","bundle","worker","blacklist"],"sourceRoot":""}

View file

@ -1,2 +0,0 @@
(()=>{"use strict";importScripts("/osana/osana.bundle.js?1"),importScripts("/osana/osana.config.js?1"),self.OsanaServiceWorker=class{constructor(){this.config=self.__osana$config,this.bundle=self.__osana$bundle,this.bareClient=new this.bundle.BareClient(this.config.bare)}fetch(t){return s=this,n=void 0,r=function*(){const s=this.config.codec.decode(new URL(t.request.url).pathname.replace(this.config.prefix,""))+new URL(t.request.url).search;if(!/^https?:\/\//.test(s))return fetch(t.request.url);const n=new URL(s),i=this.bundle.rewrite.headers.request(Object.fromEntries(t.request.headers.entries()),n);if(this.config.blacklist&&this.config.blacklist.some((e=>e.test(n.host))))return new e;const r={method:t.request.method,headers:i};["GET","HEAD"].includes(t.request.method)||(r.body=yield t.request.blob());const o=yield this.bareClient.fetch(n,r);let a=o.rawResponse.status;const c=this.bundle.rewrite.headers.response(o.rawHeaders,n);let l="";return/text\/html/.test(c["Content-Type"])?(l=`<head><script src="${this.config.files.bundle}?1"><\/script><script src="${this.config.files.config}?1"><\/script><script src="${this.config.files.client}?1"><\/script><link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGBkZAAAAAoAAx9k7/gAAAAASUVORK5CYIIA"><link rel="icon" href="${n.origin}/favicon.ico">`+(301===a&&c.location?`<meta http-equiv="refresh" content="0; url=${this.bundle.rewrite.url(c.location)}">`:"")+"</head>",l+=this.bundle.rewrite.html(yield o.text(),n.origin+n.pathname)):l=/text\/css/.test(c["Content-Type"])||"style"===t.request.destination?this.bundle.rewrite.css(yield o.text(),n.origin+n.pathname):/application\/javascript/.test(c["Content-Type"])||"script"===t.request.destination?this.bundle.rewrite.js(yield o.text()):yield o.arrayBuffer(),new Response(l,{status:o.rawResponse.status,statusText:o.rawResponse.statusText,headers:c})},new((i=void 0)||(i=Promise))((function(e,t){function o(e){try{c(r.next(e))}catch(e){t(e)}}function a(e){try{c(r.throw(e))}catch(e){t(e)}}function c(t){var s;t.done?e(t.value):(s=t.value,s instanceof i?s:new i((function(e){e(s)}))).then(o,a)}c((r=r.apply(s,n||[])).next())}));var s,n,i,r}};class e extends Response{constructor(){super("Forbidden",{status:403,statusText:"Forbidden",headers:{"Content-Type":"text/plain"}})}}})();
//# sourceMappingURL=osana.worker.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,154 +0,0 @@
{
"adjectives": [
"admiring",
"adoring",
"affectionate",
"agitated",
"amazing",
"angry",
"awesome",
"beautiful",
"blissful",
"bold",
"boring",
"brave",
"busy",
"charming",
"clever",
"cool",
"compassionate",
"competent",
"condescending",
"confident",
"cranky",
"crazy",
"dazzling",
"determined",
"distracted",
"dreamy",
"eager",
"ecstatic",
"elastic",
"elated",
"elegant",
"eloquent",
"epic",
"exciting",
"fervent",
"festive",
"flamboyant",
"focused",
"friendly",
"frosty",
"funny",
"gallant",
"gifted",
"goofy",
"gracious",
"great",
"happy",
"hardcore",
"heuristic",
"hopeful",
"hungry",
"infallible",
"inspiring",
"interesting",
"intelligent",
"jolly",
"jovial",
"keen",
"kind",
"laughing",
"loving",
"lucid",
"magical",
"mystifying",
"modest",
"musing",
"naughty",
"nervous",
"nice",
"nifty",
"nostalgic",
"objective",
"optimistic",
"peaceful",
"pedantic",
"pensive",
"practical",
"priceless",
"quirky",
"quizzical",
"recursing",
"relaxed",
"reverent",
"romantic",
"sad",
"serene",
"sharp",
"silly",
"sleepy",
"stoic",
"strange",
"stupefied",
"suspicious",
"sweet",
"tender",
"thirsty",
"trusting",
"unruffled",
"upbeat",
"vibrant",
"vigilant",
"vigorous",
"wizardly",
"wonderful",
"xenodochial",
"youthful",
"zealous",
"zen"
],
"surnames": [
"albattani",
"allen",
"almeida",
"antonelli",
"agnesi",
"archimedes",
"ardinghelli",
"aryabhata",
"austin",
"babbage",
"banach",
"banzai",
"bardeen",
"bartik",
"bassi",
"beaver",
"bell",
"benz",
"bhabha",
"bhaskara",
"black",
"blackburn",
"blackwell",
"bohr",
"booth",
"borg",
"bose",
"bouman",
"boyd",
"brahmagupta",
"brattain",
"brown",
"buck",
"burnell",
"cannon",
"carson",
"cartwright",
"carver",
"cerf",
"chandrasekhar"
]
}

View file

@ -1,46 +0,0 @@
// Get's the current day using the Date function built in.
// A dependency for displaying time - displayTime(void)
function getDayName(dateStr, locale) {
var date = new Date(dateStr);
return date.toLocaleDateString(locale, { weekday: "long" });
}
// The main function to show the time on the main page
// needs to be initialized by a call (only one)
// Dependent on getDayName function
function displayTime() {
var date = new Date();
var h = date.getHours(); // 0 - 23
var m = date.getMinutes(); // 0 - 59
var s = date.getSeconds(); // 0 - 59
var session = "AM";
h = h == 12 ? 24 : h;
if (h == 0) {
h = 12;
} else if (h >= 12) {
h = h - 12;
session = "PM";
}
h = h < 10 ? "0" + h : h;
m = m < 10 ? "0" + m : m;
s = s < 10 ? "0" + s : s;
// Repeat itself every second
setTimeout(displayTime, 1000);
// Get today's date
var today = new Date();
var dd = String(today.getDate()).padStart(2, "0");
var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
var yyyy = today.getFullYear();
today = mm + "/" + dd + "/" + yyyy;
var time = h + "<span style='opacity:100%;' class='clockColon'>:</span>" + m;
try {
document.getElementById("digitalClock").innerHTML =
getDayName(today, "us-US") + ", " + time + " " + session + ".";
} catch {}
return time;
}
// initialize the time function
displayTime();

View file

@ -1,359 +0,0 @@
// Welcome to the main Nebula script
// This script handles all the tasks neccesary for a proxy.
// What this doesn't include is the actual proxies, just the neccesary tasks in order for the proxies to be able to preform, such as registering the service worker required by Interception proxies.
// Documentation Writers/Contributors:
// GreenWorld#0001 (Discord) / GreenyDev (Github)
// If you would like to contribute, feel free to open a pull request.
// These docs are not finished
// Navigation controls for smaller devices
// Executed in the inline HTML
function openNav() {
document.getElementById("sidenav").style.width = "260px";
}
function closeNav() {
document.getElementById("sidenav").style.width = "0px";
}
function setLoaderText() {
document.getElementById("connectorText").textContent =
"connecting to service";
const loader = document.getElementById("lpoader");
const loadConstructer = loader.style;
loadConstructer.display = "flex";
loadConstructer.justifyContent = "center";
// Changing the text over multiple periods of time creates character, and aliveness (is that even a word?)
setTimeout(() => {
document.getElementById("connectorText").style.fontSize = "12px";
document.getElementById("connectorText").textContent =
"Due to high server load, this may take a while.";
}, 3200);
setTimeout(() => {
document.getElementById("connectorText").style.fontSize = "14px";
document.getElementById("connectorText").textContent =
"Hmmm.. Something isn't right..";
}, 17000);
}
window.stealthEngineLoaded = false;
window.addEventListener("load", () => {
// Register the service workers for Osana and Ultraviolet proxy protocols
// This is a better method than registering onsubmit because this allows the ability to use proxied links on the main page.
navigator.serviceWorker.register("./sw.js", {
scope: "/service/"
});
// Link evaluation
// This functions' purpose is to check a string of text (the argument)
// it recognizes whether a string is a URL or not, and it returns a true or false value
function isUrl(val = "") {
if (
/^http(s?):\/\//.test(val) ||
(val.includes(".") && val.substr(0, 1) !== " ")
)
return true;
return false;
}
const proxy = localStorage.getItem("proxy") || "uv";
const inpbox = document.querySelector("form");
// Display the "loading" indicators on the main page, looks much better than a static/still screen.
const hasLoadedElement = document.createElement("div");
hasLoadedElement.id = "hasLoaded";
hasLoadedElement.style.display = "none";
document.body.appendChild(hasLoadedElement);
inpbox.addEventListener("submit", (event) => {
// Prevents the default event tasks
event.preventDefault();
console.log("Connecting to service -> loading");
setLoaderText();
});
// Form submission
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
event.preventDefault();
// Check if the service worker (commonly called SW) is registered
if (typeof navigator.serviceWorker === "undefined")
alert(
"An error occured registering your service worker. Please contact support - discord.gg/unblocker"
);
//
if (proxy === "uv" || proxy === "osana") {
// Re-register the service worker incase it failed to onload
navigator.serviceWorker
.register("./sw.js", {
scope: "/service/"
})
.then(() => {
const value = event.target.firstElementChild.value;
let url = value.trim();
if (!isUrl(url)) url = "https://www.google.com/search?q=" + url;
if (!(url.startsWith("https://") || url.startsWith("http://")))
url = "http://" + url;
// encode the URL for UltraViolet
let redirectTo =
proxy === "uv"
? __uv$config.prefix + __uv$config.encodeUrl(url)
: __osana$config.prefix + __osana$config.codec.encode(url);
const option = localStorage.getItem("nogg");
if (option === "on") {
if (window.stealthEngineLoaded !== false) {
stealthEngine(redirectTo);
} else {
console.error(
"Stealth Engine failed to load! Please contact support - discord.gg/unblocker"
);
}
} else {
setTimeout(() => {
// If StealthMode is off, this is the enabled option.
const _popout = window.open("/blob", "_self");
const blob = _popout.document;
// Write all of the neccesary page elements, and the Options including the cloak (if enabled)
// The blob writing is just the background elements, like the "Nebula is loading your content, please wait" screen. It does not carry proxied content, or even the iframe.
blob.write(`
<script>
function handleTabLeave(activeInfo) {
var link = document.querySelector("link[rel~='icon']");
if (localStorage.getItem('ADVcloak') == "on") {
if (document.title == "Nebula") {
document.title = "Google"
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.getElementsByTagName('head')[0].appendChild(link);
}
link.href = 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQo7AE3IF34XPGyseQjkXIOsWXpkZiLlMjSAwySjcJSPAwlv3hnGKi1&usqp=CAU';
document.title = "Google"
} else if (document.title == "Google") {
document.title = "Nebula"
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.getElementsByTagName('head')[0].appendChild(link);
}
} else {
return false;
}
}
}
document.addEventListener("visibilitychange", handleTabLeave)
</script>
<style>@import "https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap"; body{background:#191724;color:#fff}div{margin-top:30px;font-size:100px;text-align:center;font-family:"Roboto";font-weight:700}.loader .b1{left:42%}.loader .b2{left:50%;animation-delay:100ms}.loader .b3{left:58%;animation-delay:200ms;color:#eb6f92}.loader .b1,.loader .b2,.loader .b3{width:10px;height:30px;position:absolute;top:50%;transform:rotate(0);animation-name:spinify;animation-duration:1600ms;animation-iteration-count:infinite;color:#eb6f92;background-color:#eb6f92}@keyframes spinify{0%{transform:translate(0px,0px)}33%{transform:translate(0px,24px);border-radius:100%;width:10px;height:10px}66%{transform:translate(0px,-16px)}88%{transform:translate(0px,4px)}100%{transform:translate(0px,0px)}}</style>
<div class="loader">
<div>Nebula is loading your content!</div>
<div style='font-size:35px;'>Please wait</div>
<div class="b1"></div>
<div class="b2"></div>
<div class="b3"></div>
</div>
`);
// inside of the blob, create and append the Iframe element which WILL carry the proxied content.
const iframe = blob.createElement("iframe");
const style = iframe.style;
const img = blob.createElement("link");
const link = location.href;
// We attach ARC because it supports us, keeping our arc link there would be greatly appreciated :)
const arcSrc = blob.createElement("script");
arcSrc.setAttribute(
"src",
"https://arc.io/widget.min.js#BgaWcYfi"
);
// Arc requires the Async attribute
// Async means not running parallel to other tasks, so it loads seperately to everything else (in a sense)
// Aysnchronous and Synchronous are somewhat difficult topics, so we recommend you
arcSrc.setAttribute("async", "");
blob.head.appendChild(arcSrc);
img.rel = "icon";
img.href =
"https://static.nebulacdn.xyz/content/images/nebula_logo_619x619.png";
blob.title = getRandomName();
// slice the link like some nice fruit :)
// Removing the '/' from 'whateverthislinkis.gay/'
// ^
var currentLink = link.slice(0, link.length - 1);
// To attribute the iframe to a source, we need to + the current link (post-slice) to the requested website, which is passed through the functions argument
iframe.src = currentLink + redirectTo;
// Style the Iframe to fill the entire screen and remove the bessels.
style.position = "fixed";
style.top = style.bottom = style.left = style.right = 0;
style.border = style.outline = "none";
style.width = style.height = "100%";
// finally, append the iframe to the blob's (window) body
blob.body.appendChild(iframe);
}, 1000);
}
});
}
});
});
// Set the option
var option = localStorage.getItem("nogg");
if (localStorage.getItem("theme") == null) {
localStorage.setItem("theme", "dark");
}
// Extra logging for support
function log() {
setTimeout(
console.log.bind(
console,
"%cWelcome To Nebula!",
"background: #3F51B5;color:#FFF;padding:5px;border-radius: 5px;line-height: 26px; font-size:24px;"
)
);
setTimeout(
console.log.bind(
console,
"%cIf you are seeing this, Nebula's main script has succesfully loaded!",
"background: green;color:#FFF;padding:5px;border-radius: 5px;line-height: 26px; font-size:16px;"
)
);
setTimeout(
console.log.bind(
console,
"%cIf you encounter an error, contact our support team on discord. Copy and paste the information below and send it in the ticket",
"background: red;color:#FFF;padding:5px;border-radius: 5px;line-height: 26px; font-size:12px;"
)
);
let online = navigator.onLine;
let userAgent = navigator.userAgent;
let browserName;
let diagnosticDomain = window.location.href;
if (userAgent.match(/chrome|chromium|crios/i)) {
browserName = "Chrome";
} else if (userAgent.match(/firefox|fxios/i)) {
browserName = "Firefox";
} else if (userAgent.match(/safari/i)) {
browserName = "Safari";
} else if (userAgent.match(/opr\//i)) {
browserName = "Opera";
} else if (userAgent.match(/edg/i)) {
browserName = "Edge";
} else {
browserName = "Browser not detected!";
}
setTimeout(
console.log.bind(
console,
`%c Information: \n Online: ${online} \n URL: ${diagnosticDomain} \n Browser: ${browserName} \n UA: ${userAgent}`,
"background: gray;color:#FFF;padding:5px;line-height: 26px; font-size:14px;"
)
)
}
log();
// Adjectives and surnames for a more advanced stealth engine.
// Used together to generate random names for the tab name
let adjectives;
let surnames;
async function surnameAdjectivesData() {
await fetch("/resources/adjectives_surnames.json")
.then((response) => response.json())
.then((data) => {
adjectives = data.adjectives;
surnames = data.surnames;
});
}
surnameAdjectivesData();
// Random number generator
// Dependency of getRandomName function
function getRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// Random name generator
function getRandomName() {
const random1 = getRandomNumber(0, adjectives.length);
const random2 = getRandomNumber(0, surnames.length);
const adjective = adjectives[random1];
const surname = surnames[random2];
// Connect the adjective and surname together to create a random name
const randomName = adjective + "-" + surname;
return randomName;
}
// Clickoff cloaking
// This is used to cloak the tab when it is not active
function handleTabLeave() {
var link = document.querySelector("link[rel~='icon']");
if (localStorage.getItem("ADVcloak") == "on") {
if (document.title == "Nebula") {
if (!link) {
link = document.createElement("link");
link.rel = "icon";
document.getElementsByTagName("head")[0].appendChild(link);
}
link.href = "https://www.google.com/favicon.ico";
document.title = "Google";
} else if (document.title == "Google") {
document.title = "Nebula";
if (!link) {
link = document.createElement("link");
link.rel = "icon";
document.getElementsByTagName("head")[0].appendChild(link);
}
link.href =
"https://camo.githubusercontent.com/b565ae2e136e0ac6023e7099288a62382de7c2b8cdce86a8b90449b86649434c/68747470733a2f2f6e6562756c6170726f78792e6e6562756c612e62696f2f696d616765732f6c6f676f2e706e67";
} else {
return false;
}
}
}
// Create and Add the event listener
document.addEventListener("visibilitychange", handleTabLeave);
const stealthStored = localStorage.getItem("nogg");
function link(_link) {
if (stealthStored == "on") {
let inFrame;
try {
inFrame = window !== top;
} catch (e) {
inFrame = true;
}
setTimeout(() => {
if (!inFrame && !navigator.userAgent.includes("Firefox")) {
const popup = open("about:blank", "_blank");
if (!popup || popup.closed) {
alert("Popups are disabled!");
} else {
const doc = popup.document;
const iframe = doc.createElement("iframe");
const style = iframe.style;
const img = doc.createElement("link");
img.rel = "icon";
img.href =
"https://ssl.gstatic.com/images/branding/product/1x/drive_2020q4_32dp.png";
doc.title = getRandomName();
var currentLink = _link.slice(0, _link.length - 1);
iframe.src =
location.origin +
"/service/go/" +
__uv$config.encodeUrl(currentLink);
style.position = "fixed";
style.top = style.bottom = style.left = style.right = 0;
style.border = style.outline = "none";
style.width = style.height = "100%";
doc.body.appendChild(iframe);
}
}
}, 200);
} else {
location.href =
"service/go/" + __uv$config.encodeUrl("https://radon.games/");
}
}

View file

@ -1,124 +0,0 @@
// OPTIONS
const storedSetTheme = localStorage.getItem("theme");
function switchProxy() {
var selecter = document.getElementById("proxySwitcher");
var selectedOption = selecter.value;
localStorage.setItem("proxy", selectedOption);
var storedChoice = localStorage.getItem("proxy");
}
function setDefaultsIfUndefined() {
if (localStorage.ADVcloak == undefined) localStorage.setItem("ADVcloak", "off")
if (localStorage.nogg == undefined) localStorage.setItem("nogg", "off")
if (localStorage.ABtitle == undefined) localStorage.setItem("ABtitle", "")
if (localStorage.ABfaviconURL == undefined) localStorage.setItem("ABfaviconURL", "")
if (localStorage.theme == undefined) localStorage.setItem("theme", "dark")
if (localStorage.proxy == undefined) localStorage.setItem("proxy", "uv")
}
setDefaultsIfUndefined();
function resetViews() {
changeCSS("--background-primary", "#191724", true);
changeCSS("--navbar-color", "#26233a", true);
changeCSS("--navbar-height", "60px", true);
changeCSS("--navbar-text-color", "rgb(121 103 221)", true);
changeCSS("--navbar-link-color", "#e0def4", true);
changeCSS("--navbar-font", '"Roboto"', true);
changeCSS("--input-text-color", "#e0def4", true);
changeCSS("--input-placeholder-color", "#6e6a86", true);
changeCSS("--input-background-color", "#1f1d2e", true);
changeCSS("--input-placeholder-color", "white", true);
changeCSS("--input-border-color", "#eb6f92", true);
changeCSS("--input-border-size", "1.3px", true);
return "All views reset";
}
function saveIc() {
var notification = `
<div class="notification-container" id="notification-container">
<div class="notification notification-success">
<strong>Success!</strong> Your settings have been saved!
</div>
</div>
`;
document.getElementById("notifhere").innerHTML = notification;
setTimeout(() => {
var NotificationOBJ = document.getElementById("notifhere");
}, 2000);
}
function unsavedChanges() {
var notification = `
<div class="notification-container" id="notification-container">
<div class="notification notification-danger" id="notification-container">
<strong>Danger!</strong> You have unsaved changes!
</div>
</div>
`;
document.getElementById("notifhere").innerHTML = notification;
setTimeout(() => {
var NotificationOBJ = document.getElementById("notifhere");
}, 2000);
}
var option = localStorage.getItem("nogg");
function toggleNoGG() {
if (option === "on") {
option = "off";
localStorage.setItem("nogg", "off");
} else {
option = "on";
localStorage.setItem("nogg", "on");
}
}
var option2 = localStorage.ADVcloak;
function toggleClickoff() {
if (option2 == undefined) return console.error("ADVcloak key unset!")
if (option2 === "on") {
option2 = "off";
localStorage.setItem("ADVcloak", "off");
} else {
option2 = "on";
localStorage.setItem("ADVcloak", "on");
}
}
function toggleSetting(localStorageKey, value) {
localStorage.setItem(localStorageKey, value);
}
window.onload = function () {
if (localStorage.getItem("ABfaviconURL") === null) {
} else if (localStorage.getItem("ABfaviconURL") == "") {
} else {
document.querySelector("#faviconInput").value =
localStorage.getItem("ABfaviconURL");
}
if (localStorage.getItem("ABtitle") === null) {
} else if (localStorage.getItem("ABtitle") == "") {
} else {
document.querySelector("#titleInput").value =
localStorage.getItem("ABtitle");
}
};
function saveAbInfo() {
var faviconURL = document.getElementById("faviconInput").value;
var title = document.getElementById("titleInput").value;
localStorage.setItem("ABfaviconURL", faviconURL);
localStorage.setItem("ABtitle", title);
var notification = `
<div class="notification-container" id="notification-container">
<div class="notification notification-success">
<strong>Success!</strong> Your settings have been saved!
</div>
</div>
`;
document.getElementById("notifhere").innerHTML = notification;
setTimeout(() => {
var NotificationOBJ = document.getElementById("notifhere");
}, 2000);
}

View file

@ -1,33 +0,0 @@
window.onload = function () {
changeCSS(
"--background-primary",
localStorage.getItem("--background-primary")
);
changeCSS("--navbar-color", localStorage.getItem("--navbar-color"));
changeCSS("--navbar-height", localStorage.getItem("--navbar-height"));
changeCSS("--navbar-text-color", localStorage.getItem("--navbar-text-color"));
changeCSS("--input-text-color", localStorage.getItem("--input-text-color"));
changeCSS(
"--input-placeholder-color",
localStorage.getItem("--input-placeholder-color")
);
changeCSS(
"--input-background-color",
localStorage.getItem("--input-background-color")
);
changeCSS(
"--input-border-color",
localStorage.getItem("--input-border-color")
);
changeCSS("--input-border-size", localStorage.getItem("--input-border-size"));
changeCSS("--navbar-link-color", localStorage.getItem("--navbar-link-color"));
changeCSS("--navbar-font", localStorage.getItem("--navbar-font"));
changeCSS(
"--navbar-logo-filter",
localStorage.getItem("--navbar-logo-filter")
);
changeCSS(
"--text-color-primary",
localStorage.getItem("--text-color-primary")
);
};

View file

@ -1,103 +0,0 @@
// Stealth engine, a dependency for everything above.
// ensures that the js file is loaded
window.stealthEngineLoaded = true;
function stealthEngine(encodedURL) {
// Remember that the EncodedURL argument must be pre-encoded, or encoded before the function is called.
// This function does not encode the argument at all!
// Initialize the variable
let inFrame;
// make sure there isn't a window open already
try {
inFrame = window !== top;
} catch (e) {
inFrame = true;
}
setTimeout(() => {
// Basically, a checklist to make sure that an error won't occur.
// In this if statement, we're checking if an iframe is already being opened, if popups are disabled, and if the user agent IS NOT firefox (firefox sucks, sorry Moz)
if (!inFrame && !navigator.userAgent.includes("Firefox")) {
const popup = open("about:blank", "_blank");
if (!popup || popup.closed) {
alert(
"StealthEngine was unable to open a popup. (do you have popups disabled?)"
);
} else {
const doc = popup.document;
const iframe = doc.createElement("iframe");
const style = iframe.style;
popup.onload = () => {
document.getElementById("lpoader").style.display = "none";
document.getElementById("connectorText").textContent =
"connecting to service";
setTimeout(() => {
document.getElementById("connectorText").textContent =
"connecting to service";
}, 17500);
};
var isClosed = setInterval(function () {
if (popup.closed) {
clearInterval(isClosed);
document.getElementById("lpoader").style.display = "none";
document.getElementById("connectorText").textContent =
"connecting to service";
}
}, 1000);
// Favicon attachment
const img = doc.createElement("link");
const arcSrc = doc.createElement("script");
// We attach ARC because it supports us, keeping our arc link there would be greatly appreciated :)
arcSrc.setAttribute("src", "https://arc.io/widget.min.js#BgaWcYfi");
arcSrc.setAttribute("async", "");
doc.head.appendChild(arcSrc);
const link = location.href;
img.rel = "icon";
img.href =
ABFavicon ||
"https://ssl.gstatic.com/images/branding/product/1x/drive_2020q4_32dp.png";
if (localStorage.nogg == "on") {
doc.title = ABTitle || getRandomName();
} else {
doc.title = ABTitle || "Nebula";
}
var currentLink = link.slice(0, link.length - 1);
iframe.src = currentLink + encodedURL;
style.position = "fixed";
style.top = style.bottom = style.left = style.right = 0;
style.border = style.outline = "none";
style.width = style.height = "100%";
doc.body.appendChild(iframe);
doc.head.appendChild(img);
}
}
}, 1500);
}
let tryAbFavi = localStorage.getItem("ABfaviconURL");
let ABFavicon = "";
if (tryAbFavi === null) {
console.warn("ABfaviconURL is null, Defaulting");
ABFavicon = "";
} else if (tryAbFavi == "") {
console.warn("ABfaviconURL is empty, Defaulting");
ABFavicon = "";
} else {
ABFavicon = tryAbFavi;
}
let tryAbTitle = localStorage.getItem("ABtitle");
let ABTitle = "";
if (tryAbTitle === null) {
console.warn("ABtitle is null, Defaulting");
ABTitle = "";
} else if (tryAbTitle == "") {
console.warn("ABtitle is empty, Defaulting");
ABTitle = "";
} else {
ABTitle = tryAbTitle;
}

View file

@ -1,189 +0,0 @@
const THEME_OPTIONS = {
dark: {
"--background-primary": "#191724",
"--navbar-color": "#26233a",
"--navbar-height": "60px",
"--navbar-text-color": "#7967dd",
"--input-text-color": "#e0def4",
"--input-placeholder-color": "#6e6a86",
"--input-background-color": "#1f1d2e",
"--input-placeholder-color": "white",
"--input-border-color": "#eb6f92",
"--input-border-size": "1.3px",
"--navbar-link-color": "#e0def4",
"--navbar-font": '"Roboto"',
"--navbar-logo-filter": "invert(0%)",
"--text-color-primary": "#e0def4"
},
light: {
"--background-primary": "#d8d8d8",
"--navbar-color": "#a2a2a2",
"--navbar-height": "4em",
"--navbar-text-color": "#000000",
"--input-text-color": "#e0def4",
"--input-placeholder-color": "white",
"--input-background-color": "black",
"--input-border-color": "#eb6f92",
"--input-border-size": "1.3px",
"--navbar-link-color": "#000000",
"--navbar-font": '"Roboto"',
"--navbar-logo-filter": "invert(30%)",
"--text-color-primary": "#303030"
},
suit: {
"--background-primary": "#0c0c0c",
"--navbar-color": "#ff2e4e",
"--navbar-height": "4em",
"--navbar-text-color": "#000000",
"--input-text-color": "#e0def4",
"--input-placeholder-color": "white",
"--input-background-color": "#00000000",
"--input-border-color": "#ff346e",
"--input-border-size": "1.3px",
"--navbar-link-color": "#000000",
"--navbar-font": '"Roboto"',
"--navbar-logo-filter": "brightness(30)",
"--text-color-primary": "#00000"
},
metallic: {
"--background-primary": "#171717",
"--navbar-color": "#004953",
"--navbar-height": "4em",
"--navbar-text-color": "#ffffff",
"--input-text-color": "#e0def4",
"--input-placeholder-color": "white",
"--input-background-color": "#004953",
"--input-border-color": "#000000",
"--input-border-size": "1.3px",
"--navbar-link-color": "#e0def4",
"--navbar-font": '"Roboto"',
"--navbar-logo-filter": "invert(50%)",
"--text-color-primary": "#e0def4"
},
dante: {
"--background-primary": "#131313",
"--navbar-color": "#e4ff8b",
"--navbar-height": "3.5em",
"--navbar-text-color": "#000000",
"--input-text-color": "#000000",
"--input-placeholder-color": "000000",
"--input-background-color": "#e4ff8b",
"--input-border-color": "#00000000",
"--input-border-size": "1.3px",
"--navbar-link-color": "#000000",
"--navbar-font": '"Roboto"',
"--navbar-logo-filter": "brightness(0%)",
"--text-color-primary": "#000"
}
};
function changeCSS(property, value, isRoot = false) {
const root = document.documentElement;
isRoot
? root.style.setProperty(property, value)
: root.style.setProperty(property, value, "important");
}
function saveCSS(variable, value) {
localStorage.setItem(variable, value);
}
function applyTheme(theme) {
Object.entries(theme).forEach(([property, value]) => {
changeCSS(property, value);
localStorage.setItem(property, value);
});
}
function switchTheme() {
const selecter = document.getElementById("themeSwitcher");
const selectedOption = selecter.value;
console.log(selectedOption);
const theme = THEME_OPTIONS[selectedOption];
if (!theme) {
return;
}
applyTheme(theme);
localStorage.setItem("theme", selectedOption);
if (selectedOption == "custom") {
let startCustom = prompt(
"Would you like to have an interactive setup? Y/N",
""
);
if (startCustom == "Y" || startCustom == "y") {
alert(
"Welcome to the interactive setup. Please enter the following values. If you don't know what to enter, just press enter. They will default to Dark Mode."
);
let background = prompt(
"Background color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #191724",
""
);
let navbar = prompt(
"Navbar color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #26233a",
""
);
let navbarHeight = prompt(
"Navbar height || Possible Types: ABSOLUTE: cm, mm, Q, in, pc, pt, px RELATIVE: em, ex, ch, rem, lh, rlh, vw, vh, vb, vi || Default Value: 60px",
""
);
let navbarText = prompt(
"Navbar text color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #7967dd",
""
);
let inputText = prompt(
"Input text color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #e0def4",
""
);
let inputPlaceholder = prompt(
"Input placeholder color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #6e6a86",
""
);
let inputBackground = prompt(
"Input background color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #1f1d2e",
""
);
let inputBorder = prompt(
"Input border color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #eb6f92",
""
);
let inputBorderSize = prompt(
"Input border size || Possible Types: ABSOLUTE: cm, mm, Q, in, pc, pt, px RELATIVE: em, ex, ch, rem, lh, rlh, vw, vh, vb, vi || Default Value: 1.3px",
""
);
let navbarFont = prompt(
'Navbar font || Enter a default font name, custom ones will likely not work. || Default Value: "Roboto"',
""
);
let navbarLink = prompt(
"Navbar link color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #e0def4",
""
);
let navbarLogoFilter = prompt(
"Navbar logo filter || Adjust the NavBar-Logo-Filter. || Default Value: invert(0%)",
""
);
let textColorPrimary = prompt(
"Text color primary || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #e0def4",
""
);
localStorage.setItem("theme", "custom");
changeCSS("--background-primary", background, true);
changeCSS("--navbar-color", navbar, true);
changeCSS("--navbar-height", navbarHeight, true);
changeCSS("--navbar-text-color", navbarText, true);
changeCSS("--input-text-color", inputText, true);
changeCSS("--input-placeholder-color", inputPlaceholder, true);
changeCSS("--input-background-color", inputBackground, true);
changeCSS("--input-border-color", inputBorder, true);
changeCSS("--input-border-size", inputBorderSize, true);
changeCSS("--navbar-link-color", navbarLink, true);
changeCSS("--navbar-font", navbarFont, true);
changeCSS("--navbar-logo-filter", navbarLogoFilter, true);
changeCSS("--text-color-primary", textColorPrimary, true);
} else {
alert("Non-Interactive Setup Not supported yet.");
}
}
}

View file

@ -1,43 +0,0 @@
const generateOTP = document.getElementById("generate-otp");
generateOTP.onclick = () => {
fetch("/generate-otp", { method: "PATCH" });
};
const validateOTP = document.getElementById("validate-otp");
validateOTP.onclick = () => {
fetch("/validate-otp", {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({
otp: document.getElementById("otp").value
})
})
.then((response) => {
return response.json();
})
.then((data) => {
if (data.success) {
setCookie("validation", data.validation, 30);
location.href = "/";
} else {
alert("Invalid OTP.");
}
})
.catch(() => {
alert("An error occurred while validating your OTP.");
});
};
function setCookie(name, value, days) {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}

View file

@ -1,500 +0,0 @@
@import url("https://fonts.googleapis.com/css2?family=Dongle&family=Roboto:wght@100&display=swap");
@import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@1,100&display=swap");
:root {
--background-primary: #191724;
--navbar-color: #26233a;
--navbar-height: 60px;
--navbar-text-color: #7967dd;
--navbar-link-color: #e0def4;
--navbar-font: "Roboto";
--input-text-color: #e0def4;
--input-placeholder-color: white;
--input-background-color: #1f1d2e;
--input-border-color: #eb6f92;
--input-border-size: 1.3px;
--navbar-logo-filter: none;
}
::-webkit-input-placeholder {
text-align: center;
font-family: "Roboto";
}
:-moz-placeholder {
text-align: center;
}
#navbar {
height: var(--navbar-height);
text-align: center;
align-items: center;
display: flex;
position: fixed;
top: 0;
left: 0;
right: 0;
background-color: var(--navbar-color);
}
.sidenav-btn {
display: none;
}
.sidenav {
height: 100%;
width: 0;
position: fixed;
z-index: 1;
top: 0;
left: 0;
background-color: #232133;
padding-top: 60px;
-webkit-transition: 0.5s;
transition: 0.5s;
overflow: hidden;
}
.sidenav a {
padding: 8px 16px 16px 32px;
text-decoration: none;
width: 100vw;
font-size: 24px;
color: #cfcfcf;
display: block;
transition: 0.3s;
}
.sidenav .closebtn {
position: absolute;
top: 0;
right: 25px;
font-size: 36px;
margin-left: 50px;
z-index: 2;
left: 140px;
}
@media only screen and (max-width: 690px) {
/* funny number */
#navbar > ul > li {
display: none;
}
.sidenav-btn {
display: flex;
align-items: center;
position: absolute;
left: 90vw;
width: var(--navbar-height);
height: var(--navbar-height);
}
.sidenav-btn > svg {
width: 50px;
height: 50px;
}
#digitalCLOContainerLI {
display: none;
}
#navbar > ul > li > a > svg {
min-width: 24px;
}
}
a {
color: var(--navbar-link-color);
text-decoration: none !important;
font-family: "Roboto";
}
a:hover {
color: grey;
transition: 0.5s;
cursor: pointer;
}
.down {
background-color: rgb(90, 24, 154);
left: inherit !important;
font-family: "Helvetica";
background-color: var(--navbar-color);
/* box-shadow: 2px 2px rgb(0 0 0 / 20%); */
color: white;
display: none;
visibility: hidden;
opacity: 0;
list-style-type: none;
position: fixed;
border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
list-style-type: none;
}
#navbar ul:not(.down) {
font-family: "Helvetica";
/* box-shadow: 2px 2px rgb(0 0 0 / 20%); */
color: white;
margin-left: auto;
list-style-type: none;
float: right !important;
margin-right: 2.3%;
display: flex;
align-items: center;
}
#navbar ul li {
float: left;
padding-top: 1em;
padding-bottom: 1em;
padding-right: 1em;
padding-left: 1em;
padding: 1rem;
}
::placeholder,
input[type="text"] {
color: var(--input-placeholder-color);
font-size: 20px;
text-align: center;
font-family: "Roboto";
}
#navbar ul p,
ul a {
font-weight: bold;
display: flex;
align-items: center;
}
*/
/*
#navbar ul li p, ul li a {
font-weight: normal;
}
*/
#navbar ul li ul {
border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
visibility: hidden;
opacity: 0;
margin-top: 1rem;
left: 0;
display: none;
}
#navbar ul li:hover ul,
ul li ul:hover {
visibility: visible;
opacity: 1;
display: block;
}
#navbar ul li p {
color: white;
font-family: "Calibri";
z-index: 3 !important;
}
#navbar ul li ul li {
clear: both;
width: 100%;
}
#content {
display: flex;
justify-content: center;
align-items: center;
height: 98%;
color: white;
flex-direction: column;
font-family: "Roboto";
}
#content h1 {
padding-bottom: 0.5em;
font-weight: 100;
text-align: center;
}
#content img {
padding-left: 0.5em;
filter: brightness(0) invert(1);
}
#content input {
font-size: 20px;
text-align: center;
font-family: "Calibri";
border-style: solid !important;
border: var(--input-border-size) solid var(--input-border-color);
border-width: 1px;
border-radius: 15px;
background-color: var(--input-background-color);
color: var(--input-text-color);
width: 300px;
height: 50px;
box-shadow: none !important;
outline: none;
text-align: center;
font-family: "Roboto";
}
#content input:focus {
outline: none;
box-shadow: none !important;
}
@keyframes inputwide {
0% {
width: 0px;
transition-duration: 0.5s;
}
100% {
width: 300px;
transition-duration: 0.5s;
}
}
.loader {
width: 283px;
text-align: center;
display: none;
justify-content: center;
align-items: center;
flex-direction: row;
transition: 0.1s;
}
svg path,
svg rect {
fill: #eb6f92;
}
.connector {
color: white;
margin-left: 10px;
font-size: 15px;
}
@keyframes popout {
0% {
height: -20px;
}
100% {
height: 0px;
}
}
.nebHeader {
font-family: var(--navbar-font);
color: var(--navbar-text-color);
display: flex;
align-items: center;
justify-content: center;
align-content: center;
flex-wrap: nowrap;
flex-direction: row;
margin-left: 0.5%;
}
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
background-color: var(--background-primary);
color: var(--text-color-primary);
animation: fadeInAnimation ease 1s;
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
@keyframes fadeInAnimation {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
input:focus::placeholder {
color: transparent;
}
#navbar #thumbImg {
transition: width 2s, height 2s, transform 2s;
filter: var(--navbar-logo-filter);
}
#navbar #thumbImg:hover {
transform: rotate(360deg);
}
.stamp {
text-align: right;
position: fixed;
bottom: 0;
font-family: "Montserrat", sans-serif;
font-style: italic;
font-weight: lighter;
color: whitesmoke;
opacity: 90%;
user-select: none;
font-size: 13px;
padding-left: 5px;
padding-bottom: 1px;
cursor: default;
}
.stamp:hover {
text-align: right;
position: fixed;
bottom: 0;
font-family: "Montserrat", sans-serif;
font-style: italic;
font-weight: lighter;
color: whitesmoke;
opacity: 38%;
user-select: none;
font-size: 13px;
padding-left: 5px;
padding-bottom: 1px;
cursor: default;
}
.github {
position: fixed;
right: 0;
bottom: 0;
padding-right: 10px;
}
.tos {
position: fixed;
right: 67px;
bottom: 0;
padding-right: 15px;
}
.privacy {
position: fixed;
right: 114px;
bottom: 0;
padding-right: 15px;
}
.modal-window {
position: fixed;
background-color: rgba(255, 255, 255, 0.25);
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 999;
visibility: hidden;
opacity: 0;
pointer-events: none;
transition: all 0.3s;
font-family: "Montserrat", sans-serif;
}
.modal-window:target {
visibility: visible;
opacity: 1;
pointer-events: auto;
}
.modal-window > div {
width: 400px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 2em;
background: white;
}
.modal-window header {
font-weight: bold;
}
.modal-window h1 {
font-size: 150%;
margin: 0 0 15px;
}
.modal-close {
color: #aaa;
line-height: 50px;
font-size: 80%;
position: absolute;
right: 0;
text-align: center;
top: 0;
width: 70px;
text-decoration: none;
}
.modal-close:hover {
color: black;
}
.modal-window > div {
border-radius: 1rem;
}
.modal-window div:not(:last-of-type) {
margin-bottom: 15px;
}
.logo {
max-width: 150px;
display: block;
}
small {
color: lightgray;
}
.btn {
background-color: white;
padding: 1em 1.5em;
border-radius: 0.5rem;
text-decoration: none;
}
.btn i {
padding-right: 0.3em;
}
@media only screen and (max-width: 1190px) {
#digitalClock {
display: none;
}
}
.clockColon {
opacity: 100%;
animation: blink 0.5s step-end infinite alternate;
transition: 0.2s;
}
@keyframes blink {
50% {
opacity: 0%;
}
}

View file

@ -1,556 +0,0 @@
@import url("https://fonts.googleapis.com/css2?family=Work+Sans:wght@300&display=swap");
:root {
--background-primary: #191724;
--sidebar-color: #191724;
--sidebar-text-color: #e0def4;
--text-color-primary: #e0def4;
--text-color-secondary: #6e6a86;
--focus-color: #eb6f92;
--header-height: 10vh;
--section-font-size: 20pt;
--section-font: 'Calibri';
--section-padding: 0.5em;
--setting-distance-from-sidebar: 1em;
--setting-distance-from-right: 1em;
--setting-name-font: 'Calibri';
--setting-desc-font: 'Calibri';
}
* {
user-select: none;
}
/*
body {
background-color: var(--background-primary);
}*/
.container {
display: flex;
flex-direction: row;
align-content: center;
flex-wrap: wrap;
justify-content: space-evenly;
align-items: flex-end;
margin-top: 75px;
}
#sidebar {
animation: fadeIn 700ms ease-in 30ms forwards;
-webkit-animation: fadeIn 700ms ease-in 300ms forwards;
position: absolute;
top: var(--header-height);
left: 0;
background-color: var(--sidebar-color);
transition: width 0.5s;
width: var(--sidebar-width);
}
.setting li a {
background-color: #2e2828;
padding: 10px 16px;
border-radius: 5px;
}
.settings-div li a ul {
border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
visibility: hidden;
opacity: 0;
margin-top: 10px;
left: 0 !important;
display: none;
}
.settings-div li a:hover ul,
ul li:hover {
visibility: visible;
opacity: 1;
display: block;
}
/*
.settings-div ul li p{
color: white;
font-family: 'Calibri';
z-index: 3 !important;
}*/
li {
list-style-type: none;
}
.settings-div li a ul li {
clear: both;
width: 100%;
}
.section {
background-color: transparent;
color: var(--sidebar-text-color);
font-size: var(--section-font-size);
font-family: var(--section-font);
width: 100%;
transition: background-color 0.5s;
padding-top: var(--section-padding);
padding-bottom: var(--section-padding);
}
.section:hover {
background-color: #ffffff20;
}
.settings-div {
position: absolute;
left: calc(var(--sidebar-width) + var(--setting-distance-from-sidebar));
top: 0;
width: calc(100vw - var(--sidebar-width) - var(--setting-distance-from-sidebar));
padding-top: 5%;
}
.setting-input {
/* left: 100%; */
color: black !important;
position: relative;
/* right: 0; */
transform: translateY(-1.5em);
float: right;
margin-right: 5%;
}
.setting-input {
display: none;
}
@import url('https://fonts.googleapis.com/css2?family=Mulish:wght@300&display=swap');
.toogle-button {
font-weight: bold;
font-size: 10PX;
display: inline-block;
width: 75px;
height: 35px;
background-color: #dfddf3;
border-radius: 30px;
position: relative;
cursor: pointer;
}
.toogle-button::after {
content: 'Off';
width: 40px;
height: 40px;
color: #E7E2CD;
background-color: #e14343;
border: 2px solid #E7E2CD;
border-radius: 50%;
box-shadow: 0 0 5px rgb(0 0 0 / 25%);
position: absolute;
top: -3px;
left: 0;
line-height: 0;
display: grid;
place-content: center;
transition: all .5s;
transform: 1s ease-in;
font-family: 'Mulish', sans-serif;
}
.setting-input:checked+.toogle-button::after {
content: 'On';
background-color: #53b357;
transform: translateX(35px) rotate(360deg);
}
ul li {
float: left;
padding-top: 1em;
padding-bottom: 1em;
padding-right: 1em;
padding-left: 1em;
padding: 1rem;
}
.button {
width: 30px;
height: 30px;
transform: translateY(-30px);
font-family: var(--setting-desc-font);
color: white;
}
@-webkit-keyframes fadeIn {
0% {
opacity: 0.01;
}
100% {
opacity: 1;
}
}
@keyframes fadeIn {
0% {
opacity: 0.01;
}
100% {
opacity: 1;
}
}
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500&display=swap');
.notification-container {
position: fixed;
top: 4px;
right: 5px;
width: 500px;
max-width: calc(100% - 30px);
font-family: 'Roboto', sans-serif;
}
.notification {
background-color: #fff;
border-radius: 5px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
color: #fff;
font-size: 16px;
padding: 15px 20px;
line-height: 20px;
margin-bottom: 15px;
animation: grow 0.5s ease-in forwards;
}
@keyframes grow {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
.notification.hide {
animation: shrink 0.3s ease-out forwards;
}
@keyframes shrink {
to {
opacity: 0;
transform: scale(0.8);
}
}
.notification strong {
font-size: 12px;
line-height: 20px;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.notification-info {
background-color: #00cae3;
}
.notification-success {
background-color: #3d3571;
}
.notification-warning {
background-color: #ff9e0f;
}
.notification-danger {
background-color: #f55145;
}
.stamp {
position: fixed;
bottom: 0;
}
.bk-btn {
height: 52px;
width: 52px;
background-color: black;
border-radius: 50%;
}
.bk-btn .bk-btn-triangle {
position: relative;
top: 13px;
left: 10.4px;
width: 0;
height: 0;
border-top: 13px solid transparent;
border-bottom: 13px solid transparent;
border-right: 13px solid white;
}
.bk-btn .bk-btn-bar {
position: relative;
background-color: white;
height: 7.8px;
width: 13px;
top: -3.64px;
left: 22.88px;
}
@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@300&display=swap');
.settings-cont {
background: #45454521;
box-sizing: border-box;
width: 300px;
height: 246px;
padding: 30px;
border: 2px solid rgb(0 0 0 / 64%);
border-radius: 9px;
margin-top: 20px;
display: flex;
align-items: center;
flex-wrap: wrap;
flex-direction: row;
justify-content: center;
}
.name {
color: var(--text-color-primary);
font-size: 27px;
margin: 0;
font-family: 'Ubuntu', sans-serif;
font-weight: 700;
font-style: bold;
}
.description {
color: var(--text-color-secondary);
margin: 0;
font-size: 17px;
font-family: 'Ubuntu', sans-serif;
font-weight: 300;
/* font-style: Italic; */
text-align: center;
}
.secondary-desc {
color: var(--text-color-primary);
margin: 0;
margin-top: 0.3em;
font-size: 18px;
font-family: 'Ubuntu', sans-serif;
font-weight: 300;
/* font-style: Italic; */
text-align: center;
}
@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@700&display=swap');
.new-tag {
font-size: 16px;
color: rgb(226, 68, 68);
font-family: 'Oxygen', sans-serif;
}
.square {
width: .7em;
height: .7em;
margin: .5em;
}
/* Custom dropdown */
.custom-dropdown {
position: relative;
display: inline-block;
vertical-align: middle;
margin: 10px;
/* demo only */
}
.custom-dropdown select {
background-color: rgb(121 103 221);
color: var(--text-color-primary);
font-size: inherit;
padding: .5em;
padding-right: 2.5em;
border: 0;
margin: 0;
border-radius: 3px;
text-indent: 0.01px;
text-overflow: '';
-webkit-appearance: button;
/* hide default arrow in chrome OSX */
-webkit-appearance: none;
}
.custom-dropdown::before,
.custom-dropdown::after {
content: "";
position: absolute;
pointer-events: none;
}
.custom-dropdown::after {
/* Custom dropdown arrow */
content: "\25BC";
height: 1em;
font-size: .625em;
line-height: 1;
right: 1.2em;
top: 50%;
margin-top: -.5em;
}
.custom-dropdown::before {
/* Custom dropdown arrow cover */
width: 2em;
right: 0;
top: 0;
bottom: 0;
border-radius: 0 3px 3px 0;
}
.custom-dropdown select[disabled] {
color: rgba(0, 0, 0, .3);
}
.custom-dropdown select[disabled]::after {
color: rgba(0, 0, 0, .1);
}
.custom-dropdown::before {
background-color: rgba(0, 0, 0, .15);
}
.custom-dropdown::after {
color: rgba(0, 0, 0, .4);
}
.button-save {
background-color: #5e18eb;
color: #fff;
font-size: inherit;
padding: 0.5em;
padding-right: -0.5em;
border: 0;
margin: 0;
border-radius: 3px;
text-indent: 0.01px;
text-overflow: '';
-webkit-appearance: button;
cursor: pointer;
}
.button-save-alt {
background-color: #5e18eb;
color: #fff;
font-size: inherit;
padding: 0.3em;
padding-right: -0.5em;
border: 0;
margin: 0.2vh;
border-radius: 3px;
text-indent: 0.01px;
text-overflow: '';
-webkit-appearance: button;
cursor: pointer;
}
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap');
.expiramental {
font-family: 'Roboto', sans-serif;
font-size: 10px;
color: rgb(184, 0, 0);
font-weight: 400;
text-decoration: underline;
}
input[type=checkbox] {
height: 0;
width: 0;
visibility: hidden;
}
label {
cursor: pointer;
/* text-indent: -10049px; */
width: 85px;
height: 37px;
background: #504e58;
display: block;
border-radius: 100px;
position: relative;
}
label:after {
content: '';
position: absolute;
top: 5px;
left: 5px;
width: 35px;
height: 28px;
background: #3d3571;
border-radius: 90px;
transition: 0.3s;
}
input:checked+label {
background: #7967dd;
}
input:checked+label:after {
left: calc(100% - 5px);
transform: translateX(-100%);
}
label:active:after {
width: 38px;
}
.bareLocationInput {
border-style: solid !important;
border: var(--input-border-size) solid #5e18eb;
border-width: 0px;
border-radius: 6px;
background-color: #191724;
color: var(--input-text-color);
width: auto;
height: auto;
box-shadow: none !important;
outline: none;
text-align: center;
width: 220px;
font-size: 16px;
font-family: 'Roboto';
height: 29px;
transition: 0.06s;
}
.bareLocationInput:hover {
border: var(--input-border-size) solid #5e18eb;
border-width: 1px;
border-radius: 6px;
}
.bareValidSignal {
margin-top: -13px;
color: #f45145bd;
font-family: 'Ubuntu', sans-serif;
font-weight: lighter;
font-size: 13px;
text-align: center;
}

View file

@ -1,5 +0,0 @@
body {
color: white;
font-family: "Roboto";
margin-left: 20px;
}

View file

@ -1,14 +1,75 @@
importScripts("./uv/uv.bundle.js"); importScripts("/uv/uv.bundle.js");
importScripts("./uv/uv.config.js"); importScripts("/uv/uv.config.js");
importScripts("./uv/uv.sw.js"); importScripts('/scram/scramjet.wasm.js');
importScripts("./osana/osana.worker.js"); importScripts('/scram/scramjet.shared.js');
importScripts('/scram/scramjet.worker.js');
importScripts("/workerware/workerware.js");
importScripts(__uv$config.sw || "/uv/uv.sw.js");
const uv = new UVServiceWorker();
const ww = new WorkerWare({ debug: false });
const sj = new ScramjetServiceWorker();
(async function () {
await sj.loadConfig();
})();
//me when Firefox (thanks vk6)
if (navigator.userAgent.includes("Firefox")) {
Object.defineProperty(globalThis, "crossOriginIsolated", {
value: true,
writable: true
});
}
const UV = new UVServiceWorker(); //where we handle our plugins!!!
const Osana = new OsanaServiceWorker(); self.addEventListener("message", function (event) {
console.log(event.data);
self.addEventListener("fetch", (event) => { uv.config.inject = [];
if (event.request.url.startsWith(location.origin + "/service/go/")) //loop over the required data (we don't verify here as types will take care of us :D)
event.respondWith(UV.fetch(event)); event.data.forEach((data) => {
if (event.request.url.startsWith(location.origin + "/service/~osana/")) if (data.remove) {
event.respondWith(Osana.fetch(event)); if (data.type === "page") {
const idx = uv.config.inject.indexOf(data.host);
uv.config.inject.splice(idx, 1);
} else if (data.type === "serviceWorker") {
ww.deleteByName(data.name);
}
} else {
if (data.type === "page") {
uv.config.inject.push({
host: data.host,
html: data.html,
injectTo: data.injectTo
});
} else if (data.type === "serviceWorker") {
const wwFunction = eval(data.function);
ww.use({
function: wwFunction ? wwFunction : new Function(data.function),
name: data.name,
events: data.events
});
} else {
console.error("NO type exists for that. Only serviceWorker & page exist.");
return;
}
}
});
});
self.addEventListener("fetch", function (event) {
event.respondWith(
(async () => {
const wwRes = await ww.run(event)();
if (wwRes.includes(null)) {
return;
}
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
return await uv.fetch(event);
}
else if (sj.route(event)) {
return await sj.fetch(event);
}
else {
return await fetch(event.request);
}
})()
);
}); });

View file

@ -1,10 +1,30 @@
self.__uv$config = { self.__uv$config = {
prefix: "/service/go/", prefix: "/~/uv/",
bare: "/bare/", bare: "/bare/",
encodeUrl: Ultraviolet.codec.xor.encode, encodeUrl: function encode(str) {
decodeUrl: Ultraviolet.codec.xor.decode, if (!str) return str;
handler: "/uv/uv.handler.js", return encodeURIComponent(
bundle: "/uv/uv.bundle.js", str
config: "/uv/uv.config.js", .toString()
sw: "/uv/uv.sw.js" .split("")
.map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 3) : char))
.join("")
);
},
decodeUrl: function decode(str) {
if (!str) return str;
let [input, ...search] = str.split("?");
return (
decodeURIComponent(input)
.split("")
.map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 3) : char))
.join("") + (search.length ? "?" + search.join("?") : "")
);
},
handler: "/uv/uv.handler.js",
client: "/uv/uv.client.js",
bundle: "/uv/uv.bundle.js",
config: "/uv/uv.config.js",
sw: "/uv/uv.sw.js"
}; };

View file

@ -0,0 +1,171 @@
importScripts("/workerware/WWError.js");
const dbg = console.log.bind(console, "[WorkerWare]");
const time = console.time.bind(console, "[WorkerWare]");
const timeEnd = console.timeEnd.bind(console, "[WorkerWare]");
/*
OPTS:
debug - Enables debug logging.
randomNames - Generate random names for middlewares.
timing - Logs timing for each middleware.
*/
const defaultOpt = {
debug: false,
randomNames: false,
timing: false
};
const validEvents = [
"abortpayment",
"activate",
"backgroundfetchabort",
"backgroundfetchclick",
"backgroundfetchfail",
"backgroundfetchsuccess",
"canmakepayment",
"contentdelete",
"cookiechange",
"fetch",
"install",
"message",
"messageerror",
"notificationclick",
"notificationclose",
"paymentrequest",
"periodicsync",
"push",
"pushsubscriptionchange",
"sync"
];
class WorkerWare {
constructor(opt) {
this._opt = Object.assign({}, defaultOpt, opt);
this._middlewares = [];
}
info() {
return {
version: "0.1.0",
middlewares: this._middlewares,
options: this._opt
};
}
use(middleware) {
let validateMW = this.validateMiddleware(middleware);
if (validateMW.error) throw new WWError(validateMW.error);
// This means the middleware is an anonymous function, or the user is silly and named their function "function"
if (middleware.function.name == "function") middleware.name = crypto.randomUUID();
if (!middleware.name) middleware.name = middleware.function.name;
if (this._opt.randomNames) middleware.name = crypto.randomUUID();
if (this._opt.debug) dbg("Adding middleware:", middleware.name);
this._middlewares.push(middleware);
}
// Run all middlewares for the event type passed in.
run(event) {
const middlewares = this._middlewares;
const returnList = [];
let fn = async () => {
for (let i = 0; i < middlewares.length; i++) {
if (middlewares[i].events.includes(event.type)) {
if (this._opt.timing) console.time(middlewares[i].name);
// Add the configuration to the event object.
event.workerware = {
config: middlewares[i].configuration || {}
};
if (!middlewares[i].explicitCall) {
let res = await middlewares[i].function(event);
if (this._opt.timing) console.timeEnd(middlewares[i].name);
returnList.push(res);
}
}
}
return returnList;
};
return fn;
}
deleteByName(middlewareID) {
if (this._opt.debug) dbg("Deleting middleware:", middlewareID);
this._middlewares = this._middlewares.filter((mw) => mw.name !== middlewareID);
}
deleteByEvent(middlewareEvent) {
if (this._opt.debug) dbg("Deleting middleware by event:", middlewareEvent);
this._middlewares = this._middlewares.filter((mw) => !mw.events.includes(middlewareEvent));
}
get() {
return this._middlewares;
}
/*
Run a single middleware by ID.
This assumes that the user knows what they're doing, and is running the middleware on an event that it's supposed to run on.
*/
runMW(name, event) {
const middlewares = this._middlewares;
if (this._opt.debug) dbg("Running middleware:", name);
// if (middlewares.includes(name)) {
// return middlewares[name](event);
// } else {
// throw new WWError("Middleware not found!");
// }
let didCall = false;
for (let i = 0; i < middlewares.length; i++) {
if (middlewares[i].name == name) {
didCall = true;
event.workerware = {
config: middlewares[i].configuration || {}
};
if (this._opt.timing) console.time(middlewares[i].name);
let call = middlewares[i].function(event);
if (this._opt.timing) console.timeEnd(middlewares[i].name);
return call;
}
}
if (!didCall) {
throw new WWError("Middleware not found!");
}
}
// type middlewareManifest = {
// function: Function,
// name?: string,
// events: string[], // Should be a union of validEvents.
// configuration?: Object // Optional configuration for the middleware.
// }
validateMiddleware(middleware) {
if (!middleware.function)
return {
error: "middleware.function is required"
};
if (typeof middleware.function !== "function")
return {
error: "middleware.function must be typeof function"
};
if (
typeof middleware.configuration !== "object" &&
middleware.configuration !== undefined
) {
return {
error: "middleware.configuration must be typeof object"
};
}
if (!middleware.events)
return {
error: "middleware.events is required"
};
if (!Array.isArray(middleware.events))
return {
error: "middleware.events must be an array"
};
if (middleware.events.some((ev) => !validEvents.includes(ev)))
return {
error: "Invalid event type! Must be one of the following: " + validEvents.join(", ")
};
if (middleware.explicitCall && typeof middleware.explicitCall !== "boolean") {
return {
error: "middleware.explicitCall must be typeof boolean"
};
}
return {
error: undefined
};
}
}

95
server/config.ts Normal file
View file

@ -0,0 +1,95 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import chalk from "chalk";
import { TomlPrimitive, parse } from "smol-toml";
interface TomlData {
marketplace: {
enabled: boolean;
psk: String;
};
server: {
server: {
port: number;
wisp: boolean;
logging: boolean;
};
};
seo: {
enabled: boolean;
domain: string;
};
db: {
name: string;
username: string;
password: string;
postgres: boolean;
};
postgres: {
domain: string;
port: number;
};
}
interface Verify {
name: string;
typeOF: any;
type: any;
verifyExtras?: () => boolean | Error;
}
let doc = readFileSync(fileURLToPath(new URL("../config.toml", import.meta.url))).toString();
const parsedDoc = parse(doc) as unknown as TomlData;
function verify(t: Verify[]) {
for (let i: number = 0; i !== t.length; i++) {
if (typeof t[i].typeOF !== t[i].type) {
throw new Error(`Invalid structure: "${t[i].name}" should be a(n) ${t[i].type}`);
}
if (t[i].verifyExtras) {
const extra = t[i].verifyExtras();
if (extra !== true) {
throw extra;
}
}
}
}
verify([
{ name: "marketplace", typeOF: parsedDoc.marketplace, type: "object" },
{ name: "marketplace.enabled", typeOF: parsedDoc.marketplace.enabled, type: "boolean" },
{ name: "marketplace.psk", typeOF: parsedDoc.marketplace.psk, type: "string" },
{ name: "server", typeOF: parsedDoc.server, type: "object" },
{ name: "server.server", typeOF: parsedDoc.server.server, type: "object" },
{ name: "server.server.port", typeOF: parsedDoc.server.server.port, type: "number" },
{ name: "server.server.wisp", typeOF: parsedDoc.server.server.wisp, type: "boolean" },
{ name: "server.server.logging", typeOF: parsedDoc.server.server.logging, type: "boolean" },
{ name: "seo", typeOF: parsedDoc.seo, type: "object" },
{ name: "seo.enabled", typeOF: parsedDoc.seo.enabled, type: "boolean" },
{ name: "seo.domain", typeOF: parsedDoc.seo.domain, type: "string", verifyExtras: () => {
try {
new URL(parsedDoc.seo.domain);
}
catch (e) {
return Error(e);
}
return true;
}},
{ name: "db", typeOF: parsedDoc.db, type: "object" },
{ name: "db.name", typeOF: parsedDoc.db.name, type: "string" },
{ name: "db.username", typeOF: parsedDoc.db.username, type: "string" },
{ name: "db.password", typeOF: parsedDoc.db.password, type: "string" },
{ name: "db.postgres", typeOF: parsedDoc.db.postgres, type: "boolean" },
{ name: "postgres", typeOF: parsedDoc.postgres, type: "object" },
{ name: "postgres.domain", typeOF: parsedDoc.postgres.domain, type: "string" },
{ name: "postgres.port", typeOF: parsedDoc.postgres.port, type: "number" }
]);
if (parsedDoc.marketplace.psk === "CHANGEME") {
console.warn(chalk.yellow.bold('PSK should be changed from "CHANGEME"'));
}
if (parsedDoc.db.password === "password") {
console.warn(chalk.red.bold("You should change your DB password!!"));
}
export { TomlData, parsedDoc };

98
server/dbSetup.ts Normal file
View file

@ -0,0 +1,98 @@
import { fileURLToPath } from "node:url";
import chalk from "chalk";
import ora from "ora";
import { ModelStatic } from "sequelize";
import { Catalog, CatalogModel } from "./marketplace.js";
interface Items extends Omit<Catalog, "background_video" | "background_image"> {
background_video?: string;
background_image?: string;
}
async function installItems(db: ModelStatic<CatalogModel>, items: Items[]) {
items.forEach(async (item) => {
await db.create({
package_name: item.package_name,
title: item.title,
image: item.image,
author: item.author,
version: item.version,
description: item.description,
tags: item.tags,
payload: item.payload,
background_video: item.background_video,
background_image: item.background_image,
type: item.type
});
});
}
async function setupDB(db: ModelStatic<CatalogModel>) {
//We have some packages that need to be installed if they aren't.
const items: Items[] = [
{
package_name: "com.nebula.gruvbox",
title: "Gruvbox",
image: "gruvbox.jpg",
author: "Nebula Services",
version: "1.0.0",
description: "The gruvbox theme",
tags: ["Theme", "Simple"],
payload: "gruvbox.css",
type: "theme"
},
{
package_name: "com.nebula.oled",
title: "Oled theme",
image: "oled.jpg",
author: "Nebula Services",
version: "1.0.0",
description: "A sleek & simple Oled theme for Nebula",
tags: ["Theme", "Simple", "Sleek"],
payload: "oled.css",
type: "theme"
},
{
package_name: "com.nebula.lightTheme",
title: "Light Theme",
image: "light.png",
author: "Nebula Services",
version: "1.0.0",
description: "A sleek light theme for Nebula",
tags: ["Theme", "Simple", "Light"],
payload: "light.css",
type: "theme"
},
{
package_name: "com.nebula.retro",
title: "Retro Theme",
image: "retro.png",
author: "Nebula Services",
version: "1.0.0",
description: "Give a retro look to Nebula",
tags: ["Theme", "Simple", "Dark", "Retro"],
payload: "retro.css",
type: "theme"
},
/* {
package_name: 'gyatt',
title: 'gyatt',
image: 'gyatt',
author: "nebuka",
version: "2",
description: "e",
tags: [ "e" ],
payload: "gyatt.js",
type: "plugin-page"
} */
//To add plugins: plugin types consist of plugin-sw (workerware) & plugin-page (uv.config.inject)
];
const dbItems = await db.findAll();
if (dbItems.length === 0) {
const spinner = ora(chalk.hex("#7967dd")("Performing DB setup...")).start();
await installItems(db, items);
spinner.succeed(chalk.hex("#eb6f92")("DB setup complete!"));
}
}
export { setupDB };

1
server/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module "@rubynetwork/rammerhead/src/server/index.js";

229
server/marketplace.ts Normal file
View file

@ -0,0 +1,229 @@
import { createWriteStream } from "node:fs";
import { constants, access, mkdir } from "node:fs/promises";
import { pipeline } from "node:stream/promises";
import { fileURLToPath } from "node:url";
import { FastifyInstance, FastifyRequest } from "fastify";
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
import { parsedDoc } from "./config.js";
const db = new Sequelize(parsedDoc.db.name, parsedDoc.db.username, parsedDoc.db.password, {
host: parsedDoc.db.postgres ? `${parsedDoc.postgres.domain}` : "localhost",
port: parsedDoc.db.postgres ? parsedDoc.postgres.port : undefined,
dialect: parsedDoc.db.postgres ? "postgres" : "sqlite",
logging: parsedDoc.server.server.logging,
storage: "database.sqlite" //this is sqlite only
});
type CatalogType = "theme" | "plugin-page" | "plugin-sw";
interface Catalog {
package_name: string;
title: string;
description: string;
author: string;
image: string;
tags: object;
version: string;
background_image: string;
background_video: string;
payload: string;
type: CatalogType;
}
interface CatalogModel
extends Catalog,
Model<InferAttributes<CatalogModel>, InferCreationAttributes<CatalogModel>> {}
const catalogAssets = db.define<CatalogModel>("catalog_assets", {
package_name: { type: DataTypes.STRING, unique: true },
title: { type: DataTypes.TEXT },
description: { type: DataTypes.TEXT },
author: { type: DataTypes.TEXT },
image: { type: DataTypes.TEXT },
tags: { type: DataTypes.JSON, allowNull: true },
version: { type: DataTypes.TEXT },
background_image: { type: DataTypes.TEXT, allowNull: true },
background_video: { type: DataTypes.TEXT, allowNull: true },
payload: { type: DataTypes.TEXT },
type: { type: DataTypes.TEXT }
});
function marketplaceAPI(app: FastifyInstance) {
app.get("/api/catalog-stats/", (request, reply) => {
reply.send({
version: "1.0.0",
spec: "Nebula Services",
enabled: true
});
});
// This API returns a list of the assets in the database (SW plugins and themes).
// It also returns the number of pages in the database.
// It can take a `?page=x` argument to display a different page, with a limit of 20 assets per page.
type CatalogAssetsReq = FastifyRequest<{ Querystring: { page: string } }>;
app.get("/api/catalog-assets/", async (request: CatalogAssetsReq, reply) => {
try {
const { page } = request.query;
const pageNum: number = parseInt(page, 10) || 1;
if (pageNum < 1) {
reply.status(400).send({ error: "Page must be a positive number!" });
}
const offset = (pageNum - 1) * 20;
const totalItems = await catalogAssets.count();
const dbAssets = await catalogAssets.findAll({ offset: offset, limit: 20 });
const assets = dbAssets.reduce((acc, asset) => {
acc[asset.package_name] = {
title: asset.title,
description: asset.description,
author: asset.author,
image: asset.image,
tags: asset.tags,
version: asset.version,
background_image: asset.background_image,
background_video: asset.background_video,
payload: asset.payload,
type: asset.type
};
return acc;
}, {});
return reply.send({ assets, pages: Math.ceil(totalItems / 20) });
} catch (error) {
return reply.status(500).send({ error: "An error occured" });
}
});
type PackageReq = FastifyRequest<{ Params: { package: string } }>;
app.get("/api/packages/:package", async (request: PackageReq, reply) => {
try {
const packageRow = await catalogAssets.findOne({
where: { package_name: request.params.package }
});
if (!packageRow) return reply.status(404).send({ error: "Package not found!" });
const details = {
title: packageRow.get("title"),
description: packageRow.get("description"),
image: packageRow.get("image"),
author: packageRow.get("author"),
tags: packageRow.get("tags"),
version: packageRow.get("version"),
background_image: packageRow.get("background_image"),
background_video: packageRow.get("background_video"),
payload: packageRow.get("payload"),
type: packageRow.get("type")
};
reply.send(details);
} catch (error) {
reply.status(500).send({ error: "An unexpected error occured" });
}
});
type UploadReq = FastifyRequest<{ Headers: { psk: string; packagename: string } }>;
type CreateReq = FastifyRequest<{
Headers: { psk: string };
Body: {
uuid: string;
title: string;
image: string;
author: string;
version: string;
description: string;
tags: object | any;
payload: string;
background_video: string;
background_image: string;
type: CatalogType;
};
}>;
interface VerifyStatus {
status: number;
error?: Error;
}
async function verifyReq(
request: UploadReq | CreateReq,
upload: Boolean,
data: any
): Promise<VerifyStatus> {
if (request.headers.psk !== parsedDoc.marketplace.psk) {
return { status: 403, error: new Error("PSK isn't correct!") };
} else if (upload && !request.headers.packagename) {
return { status: 500, error: new Error("No packagename defined!") };
} else if (upload && !data) {
return { status: 400, error: new Error("No file uploaded!") };
} else {
return { status: 200 };
}
}
app.post("/api/upload-asset", async (request: UploadReq, reply) => {
const data = await request.file();
const verify: VerifyStatus = await verifyReq(request, true, data);
if (verify.error !== undefined) {
reply.status(verify.status).send({ status: verify.error.message });
} else {
try {
await pipeline(
data.file,
createWriteStream(
fileURLToPath(
new URL(
`../database_assets/${request.headers.packagename}/${data.filename}`,
import.meta.url
)
)
)
);
} catch (error) {
return reply.status(500).send({
status: `File couldn't be uploaded! (Package most likely doesn't exist)`
});
}
return reply.status(verify.status).send({ status: "File uploaded successfully!" });
}
});
app.post("/api/create-package", async (request: CreateReq, reply) => {
const verify: VerifyStatus = await verifyReq(request, false, undefined);
if (verify.error !== undefined) {
reply.status(verify.status).send({ status: verify.error.message });
} else {
const body: Catalog = {
package_name: request.body.uuid,
title: request.body.title,
image: request.body.image,
author: request.body.author,
version: request.body.version,
description: request.body.description,
tags: request.body.tags,
payload: request.body.payload,
background_video: request.body.background_video,
background_image: request.body.background_image,
type: request.body.type as CatalogType
};
await catalogAssets.create({
package_name: body.package_name,
title: body.title,
image: body.image,
author: body.author,
version: body.version,
description: body.description,
tags: body.tags,
payload: body.payload,
background_video: body.background_video,
background_image: body.background_image,
type: body.type
});
const assets = fileURLToPath(new URL("../database_assets", import.meta.url));
try {
await access(`${assets}/${body.package_name}/`, constants.F_OK);
return reply.status(500).send({ status: "Package already exists!" });
} catch (err) {
await mkdir(`${assets}/${body.package_name}/`);
return reply
.status(verify.status)
.send({ status: "Package created successfully!" });
}
}
});
}
export { marketplaceAPI, db, catalogAssets, Catalog, CatalogModel };

93
server/server.ts Normal file
View file

@ -0,0 +1,93 @@
import { createWriteStream } from "node:fs";
import { constants, access, mkdir } from "node:fs/promises";
import { pipeline } from "node:stream/promises";
import { fileURLToPath } from "node:url";
import fastifyCompress from "@fastify/compress";
import fastifyHelmet from "@fastify/helmet";
import fastifyMiddie from "@fastify/middie";
import fastifyMultipart from "@fastify/multipart";
import fastifyStatic from "@fastify/static";
import chalk from "chalk";
import Fastify, { FastifyReply, FastifyRequest } from "fastify";
import gradient from "gradient-string";
//@ts-ignore WHY would I want this typechecked AT ALL
import { handler as ssrHandler } from "../dist/server/entry.mjs";
import { parsedDoc } from "./config.js";
import { setupDB } from "./dbSetup.js";
import { catalogAssets, marketplaceAPI } from "./marketplace.js";
import { serverFactory } from "./serverFactory.js";
const app = Fastify({
logger: parsedDoc.server.server.logging,
ignoreDuplicateSlashes: true,
ignoreTrailingSlash: true,
serverFactory: serverFactory
});
await app.register(fastifyCompress, {
encodings: ["br", "gzip", "deflate"]
});
await app.register(fastifyMultipart, {
limits: {
fileSize: 25 * 1024 * 1024,
parts: Infinity
},
});
await app.register(fastifyHelmet, {
xPoweredBy: false,
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: true,
contentSecurityPolicy: false //Disabled because astro DOES NOT LIKE IT
});
await app.register(fastifyStatic, {
root: fileURLToPath(new URL("../dist/client", import.meta.url))
});
//Our marketplace API. Not middleware as I don't want to deal with that LOL. Just a function that passes our app to it.
if (parsedDoc.marketplace.enabled) {
await app.register(fastifyStatic, {
root: fileURLToPath(new URL("../database_assets", import.meta.url)),
prefix: "/packages/",
decorateReply: false
});
marketplaceAPI(app);
}
await app.register(fastifyMiddie);
app.use(ssrHandler);
const port: number =
parseInt(process.env.PORT as string) || parsedDoc.server.server.port || parseInt("8080");
const titleText = `
_ _ _ _ ____ _
| \\ | | ___| |__ _ _| | __ _ / ___| ___ _ ____ _(_) ___ ___ ___
| \\| |/ _ \\ '_ \\| | | | |/ _' | \\___ \\ / _ \\ '__\\ \\ / / |/ __/ _ \\/ __|
| |\\ | __/ |_) | |_| | | (_| | ___) | __/ | \\ V /| | (_| __/\\__ \\
|_| \\_|\\___|_.__/ \\__,_|_|\\__,_| |____/ \\___|_| \\_/ |_|\\___\\___||___/
`;
const titleColors = {
purple: "#7967dd",
pink: "#eb6f92"
};
console.log(gradient(Object.values(titleColors)).multiline(titleText as string));
app.listen({ port: port, host: "0.0.0.0" }).then(async () => {
console.log(
chalk.hex("#7967dd")(
`Server listening on ${chalk.hex("#eb6f92").bold("http://localhost:" + port + "/")}`
)
);
console.log(
chalk.hex("#7967dd")(
`Server also listening on ${chalk.hex("#eb6f92").bold("http://0.0.0.0:" + port + "/")}`
)
);
if (parsedDoc.marketplace.enabled) {
await catalogAssets.sync();
await setupDB(catalogAssets);
}
});

29
server/serverFactory.ts Normal file
View file

@ -0,0 +1,29 @@
import { createServer } from "node:http";
import { FastifyServerFactory, FastifyServerFactoryHandler, RawServerDefault } from "fastify";
import wisp from "wisp-server-node";
import { LOG_LEVEL, WispOptions } from "wisp-server-node/dist/Types.js";
import { parsedDoc } from "./config.js";
const wispOptions: WispOptions = {
logLevel: parsedDoc.server.server.logging ? LOG_LEVEL.DEBUG : LOG_LEVEL.NONE,
pingInterval: 30
};
const serverFactory: FastifyServerFactory = (
handler: FastifyServerFactoryHandler
): RawServerDefault => {
const httpServer = createServer();
httpServer.on("request", (req, res) => {
handler(req, res);
});
httpServer.on("upgrade", (req, socket, head) => {
if (parsedDoc.server.server.wisp) {
if (req.url?.endsWith("/wisp/")) {
wisp.routeRequest(req, socket as any, head, wispOptions);
}
}
});
return httpServer;
};
export { serverFactory };

11
server/tsconfig.json Normal file
View file

@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": false,
"esModuleInterop": true,
"skipLibCheck": true,
"paths": {}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/assets/credits/uv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/assets/fortnite.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

61
src/components/Card.astro Normal file
View file

@ -0,0 +1,61 @@
---
interface Props {
title: string;
body: string;
href: string;
}
const { href, title, body } = Astro.props;
---
<li class="link-card">
<a href={href}>
<h2>
{title}
<span>&rarr;</span>
</h2>
<p>
{body}
</p>
</a>
</li>
<style>
.link-card {
list-style: none;
display: flex;
padding: 1px;
background-color: #23262d;
background-image: none;
background-size: 400%;
border-radius: 7px;
background-position: 100%;
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.link-card > a {
width: 100%;
text-decoration: none;
line-height: 1.4;
padding: calc(1.5rem - 1px);
border-radius: 8px;
color: white;
background-color: #23262d;
opacity: 0.8;
}
h2 {
margin: 0;
font-size: 1.25rem;
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
p {
margin-top: 0.5rem;
margin-bottom: 0;
}
.link-card:is(:hover, :focus-within) {
background-position: 0;
background-image: var(--accent-gradient);
}
.link-card:is(:hover, :focus-within) h2 {
color: rgb(var(--accent-light));
}
</style>

View file

@ -0,0 +1,71 @@
---
import { MARKETPLACE_ENABLED } from "astro:env/client";
import { Icon } from "astro-icon/components";
import { getLangFromUrl, useTranslations } from "../i18n/utils";
import HeaderButton from "./HeaderButton.astro";
import Logo from "./Logo.astro";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
---
<div
id="navbar"
class="flex h-16 flex-row items-center justify-end border-b-2 border-border-color bg-navbar-color px-4 z-30 relative"
>
<div class="w-1/8">
{/* Typical desktop menu */}
<div class="relative flex-row hidden lg:flex">
<HeaderButton text={t("header.home")} route={`/${lang}/`}>
<Icon
name="ph:house-bold"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
<HeaderButton text={t("header.games")} route={`/${lang}/games/`}>
<Icon
name="ph:cube"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
{
/* Astro won't let us pass the icon as a prop so it's going into the outlet here. */
}
</HeaderButton>
<HeaderButton
text={t("header.settings")}
route={`/${lang}/settings/appearance`}
>
<Icon
name="ph:wrench-fill"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
{MARKETPLACE_ENABLED &&
<HeaderButton text={t("header.catalog")} route={`/${lang}/catalog/1`}>
<Icon
name="ph:shopping-bag-open-fill"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
}
<HeaderButton text={t("header.morelinks")}>
<Icon
name="ph:link-bold"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
</div>
{/* Mobile hamburger menu */}
<div class="flex lg:hidden cursor-pointer" id="mobileNavTrigger">
<Icon
name="ph:text-align-justify-bold"
class="h-9 w-9 text-text-color"
id="hamburger_menu"
/>
<Icon
name="ph:caret-right-bold"
class="h-9 w-9 text-text-color hidden"
id="right_caret"
/>
</div>
</div>
</div>

View file

@ -0,0 +1,16 @@
---
const { text, route } = Astro.props;
---
<a
class="group flex w-full flex-row items-center justify-center border-t-2 border-solid border-navbar-text-color p-4 lg:border-none h-1/3 lg:h-fit"
id="header_anchor"
href={route}
>
<slot />
<span
class="font-roboto pl-2 text-center text-3xl font-bold text-text-color roboto transition duration-500 group-hover:text-text-hover-color lg:text-xl text-nowrap"
>
{text}
</span>
</a>

View file

@ -0,0 +1,23 @@
<div class="w-full h-full bg-primary text-navbar-text-color flex justify-center items-center">
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 400 400" width="400" height="400" fill="#5e17eb" class="animate-pulse-brighter w-48 h-48">
<g id="svgg">
<path id="path0" fill="currentColor" fill-rule="evenodd" class="s0" d="m213.6 84c1 0.3 3.4 0.7 5.1 1 1.8 0.2 4.1 0.7 5.2 1 13.2 4.1 20.3 6.8 24.5 9.1 0.6 0.3 2.3 1.2 3.8 2 2.8 1.4 13.1 8 14.4 9.2 0.5 0.3 2.3 1.9 4.2 3.5 6.7 5.5 15.5 14.9 19.2 20.4 1 1.4 2 2.7 2.2 2.8 0.3 0.1 0.5 0.5 0.5 0.8 0 0.3 1.2 2.2 2.5 4.3 2.3 3.4 7.8 14.3 9.8 19.3 0.8 2.1 0.9 2.2 10 4.9 5.6 1.6 11.1 3.4 11.7 3.8 0.3 0.2 2.4 1.1 4.7 1.9 11.1 4.1 23 12.5 27.3 19.4 5.5 8.7 3.6 20.5-4.5 28.5-3.1 3-7.5 6.4-8.4 6.4-0.3 0-0.7 0.2-0.8 0.5-1.1 2.3-23.3 11.2-35.9 14.3-3.2 0.9-3.5 1.2-5.7 6.8-7.5 19-25.5 40.6-42.3 51.1-1.6 1-3.1 2-3.3 2.2-0.1 0.2-0.9 0.7-1.7 1.2-0.8 0.4-2.2 1.2-2.9 1.6-0.8 0.4-1.6 1-1.8 1.1-0.5 0.5-4.1 2.2-8.1 3.9-1.8 0.8-3.8 1.6-4.5 2-0.7 0.3-3.1 1.1-5.2 1.8-8.3 2.6-9.8 3-21 4.9-6.4 1.1-25.3 1.3-30.5 0.3-1.9-0.3-5.8-1.1-8.6-1.6-6.8-1.3-12.7-3-20-5.7-3.3-1.2-18-8.8-19.7-10.1-0.9-0.7-4.1-3.1-7.1-5.2-5.7-4.1-17.9-15.9-20.7-20-0.9-1.2-2-2.7-2.5-3.2-3.2-3.3-13.7-21.7-13.7-24.1 0-0.6-0.2-1.2-0.6-1.4-0.3-0.2-0.8-1.2-1-2.3-0.4-1.9-1.7-2.7-6.5-3.8-23.2-5.6-43.1-17.2-48.6-28.5-7.1-14.4 4.5-31.3 27.3-39.7 1.8-0.7 4.1-1.6 5.2-2 3.7-1.5 8-2.9 19.5-6.2 1.6-0.5 2.8-1.2 2.8-1.7 0-2.4 9.8-21.6 13.1-25.7 0.2-0.4 1.4-1.8 2.5-3.2 13.8-18.1 30.2-30.6 50.6-38.8 4.3-1.7 6-2.3 14.3-4.5 5.5-1.6 11.2-2.4 18.4-2.9 7.9-0.5 24-0.2 26.8 0.6zm-29.7 17.3c-0.2 0.1-7.3 1.7-12.9 2.7-1.7 0.4-4.3 1.2-5.8 1.9-1.5 0.6-3.9 1.5-5.5 2-1.5 0.4-3.3 1.3-4 2-0.7 0.7-1.7 1.2-2.3 1.2-1.2 0-9.5 4.5-9.8 5.3-0.2 0.3-0.5 0.6-0.9 0.6-1.9 0-19.6 16-23.8 21.6-9.3 12.2-16.1 27.4-19.2 42.7-2 10.1-1.1 37.5 1.4 41.4 0 0.1 3.7 0.9 8.1 1.8 9.5 1.9 12.8 2.4 34.6 4.9 38.5 4.5 107.9 2.2 138.3-4.5 1.4-0.3 4.1-0.9 6.1-1.3 4.2-0.8 3.4 0.2 4.9-7.1 1.6-8.3 1.7-27 0.1-34.6-1.5-7.1-3.2-13.4-3.7-14.2-0.3-0.4-0.7-1.5-0.9-2.6-2.8-12.1-19.2-34.1-33-44.3-2.9-2.1-5.5-4-5.8-4.3-2.8-2.2-4.9-3.1-7.2-3.1-2.1 0-2.6-0.2-2.7-1.3-0.2-1.5-5.7-4.5-6.3-3.5-0.7 1.1-2.4 0.6-2.7-0.7-0.4-1.3-1.2-1.6-5.8-2.1-1.6-0.2-4-0.9-5.5-1.6-3.9-1.8-5.3-2.2-10.2-2.6-4.6-0.4-25.2-0.7-25.5-0.3zm74.3 42.2c7.4 9.8 4.8 23.5-4.6 24.9-6.9 1-20.9-5.8-21-10.2 0-0.2-0.3-0.8-0.8-1.3-6.4-6.8-5-20.8 2.4-24.1 6.7-2.9 17.2 1.8 24 10.7zm-176.4 36.4c-0.1-0.1-4.6 1.1-5.9 1.6-0.7 0.3-3 1.2-5.1 2-9.9 3.8-15.1 6.8-19.6 11.5-3.4 3.5-3.3 4.5 0.5 8.7 1 1 11.3 7.6 12 7.6 0.2 0 1.7 0.6 3.4 1.3 1.6 0.8 3.6 1.6 4.3 1.9 1.8 0.8 9.3 3.3 9.9 3.3 0.3 0 0.3-2 0-4.4-0.6-5.6-0.6-24.5 0.1-29.6 0.3-2.1 0.5-3.9 0.4-3.9zm229.3-0.3c-0.2 0 0 1 0.2 2.1 0.6 2.8 0.6 31.3 0 34-0.5 2.2-0.4 2.2 1.3 1.8 3.1-0.7 12.9-4.5 18.3-7 8.5-4 14.3-10.1 12.6-13.3-1.1-2.1-6.7-7.2-7.9-7.2-0.4 0-0.9-0.2-1-0.5-0.4-1.1-11.8-6.1-19.2-8.5-2.3-0.7-4.2-1.4-4.3-1.4zm-199.4 63.4l-3.1-0.4 1.8 3.2c0.9 1.8 1.9 3.4 2.2 3.5 0.3 0.1 0.5 0.6 0.5 1.1 0 0.4 0.6 1.5 1.3 2.3 0.7 0.9 1.5 1.9 1.8 2.2 0.3 0.4 0.8 1.2 1.1 1.7 6.2 10.7 35.6 33.5 43.3 33.5 0.2 0 1.3 0.4 2.5 0.9 2.5 1.2 10.6 3.4 15.3 4.2 9.5 1.8 11.6 2.1 17.4 2.1 6.6 0 16.4-1.3 22.9-3 2.2-0.5 5.2-1.3 6.8-1.7 1.6-0.3 3.2-0.9 3.5-1.2 0.4-0.3 1.1-0.6 1.6-0.6 2.3 0 22-10.6 24-12.9 0.2-0.2 2.2-1.9 4.5-3.7 5.7-4.5 11.8-11 17.1-18.4 1.6-2.3 3.2-4.5 3.6-4.9 0.4-0.4 0.7-1 0.7-1.2 0-0.2 0.8-1.9 1.9-3.6 1.1-1.7 1.9-3.2 1.9-3.4 0-0.2-3.8 0.4-11 1.6-31.7 5.4-85.1 6.7-126.9 3.1-9.6-0.8-23.1-2.3-27.8-3.2-2.2-0.4-5.3-0.9-6.9-1.2z"/>
</g>
</svg>
</div>
<style>
@keyframes pulse-brighter {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.animate-pulse-brighter {
animation: pulse-brighter 2s infinite;
height: 11rem;
}
</style>

15
src/components/Logo.astro Normal file
View file

@ -0,0 +1,15 @@
<svg
version="1.2"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 400"
width="400"
height="400"
style="width: 100%; height: 100%;"
><title>nebula</title><g id="svgg"
><path
id="path0"
fill-rule="evenodd"
d="m213.6 84c1 0.3 3.4 0.7 5.1 1 1.8 0.2 4.1 0.7 5.2 1 13.2 4.1 20.3 6.8 24.5 9.1 0.6 0.3 2.3 1.2 3.8 2 2.8 1.4 13.1 8 14.4 9.2 0.5 0.3 2.3 1.9 4.2 3.5 6.7 5.5 15.5 14.9 19.2 20.4 1 1.4 2 2.7 2.2 2.8 0.3 0.1 0.5 0.5 0.5 0.8 0 0.3 1.2 2.2 2.5 4.3 2.3 3.4 7.8 14.3 9.8 19.3 0.8 2.1 0.9 2.2 10 4.9 5.6 1.6 11.1 3.4 11.7 3.8 0.3 0.2 2.4 1.1 4.7 1.9 11.1 4.1 23 12.5 27.3 19.4 5.5 8.7 3.6 20.5-4.5 28.5-3.1 3-7.5 6.4-8.4 6.4-0.3 0-0.7 0.2-0.8 0.5-1.1 2.3-23.3 11.2-35.9 14.3-3.2 0.9-3.5 1.2-5.7 6.8-7.5 19-25.5 40.6-42.3 51.1-1.6 1-3.1 2-3.3 2.2-0.1 0.2-0.9 0.7-1.7 1.2-0.8 0.4-2.2 1.2-2.9 1.6-0.8 0.4-1.6 1-1.8 1.1-0.5 0.5-4.1 2.2-8.1 3.9-1.8 0.8-3.8 1.6-4.5 2-0.7 0.3-3.1 1.1-5.2 1.8-8.3 2.6-9.8 3-21 4.9-6.4 1.1-25.3 1.3-30.5 0.3-1.9-0.3-5.8-1.1-8.6-1.6-6.8-1.3-12.7-3-20-5.7-3.3-1.2-18-8.8-19.7-10.1-0.9-0.7-4.1-3.1-7.1-5.2-5.7-4.1-17.9-15.9-20.7-20-0.9-1.2-2-2.7-2.5-3.2-3.2-3.3-13.7-21.7-13.7-24.1 0-0.6-0.2-1.2-0.6-1.4-0.3-0.2-0.8-1.2-1-2.3-0.4-1.9-1.7-2.7-6.5-3.8-23.2-5.6-43.1-17.2-48.6-28.5-7.1-14.4 4.5-31.3 27.3-39.7 1.8-0.7 4.1-1.6 5.2-2 3.7-1.5 8-2.9 19.5-6.2 1.6-0.5 2.8-1.2 2.8-1.7 0-2.4 9.8-21.6 13.1-25.7 0.2-0.4 1.4-1.8 2.5-3.2 13.8-18.1 30.2-30.6 50.6-38.8 4.3-1.7 6-2.3 14.3-4.5 5.5-1.6 11.2-2.4 18.4-2.9 7.9-0.5 24-0.2 26.8 0.6zm-29.7 17.3c-0.2 0.1-7.3 1.7-12.9 2.7-1.7 0.4-4.3 1.2-5.8 1.9-1.5 0.6-3.9 1.5-5.5 2-1.5 0.4-3.3 1.3-4 2-0.7 0.7-1.7 1.2-2.3 1.2-1.2 0-9.5 4.5-9.8 5.3-0.2 0.3-0.5 0.6-0.9 0.6-1.9 0-19.6 16-23.8 21.6-9.3 12.2-16.1 27.4-19.2 42.7-2 10.1-1.1 37.5 1.4 41.4 0 0.1 3.7 0.9 8.1 1.8 9.5 1.9 12.8 2.4 34.6 4.9 38.5 4.5 107.9 2.2 138.3-4.5 1.4-0.3 4.1-0.9 6.1-1.3 4.2-0.8 3.4 0.2 4.9-7.1 1.6-8.3 1.7-27 0.1-34.6-1.5-7.1-3.2-13.4-3.7-14.2-0.3-0.4-0.7-1.5-0.9-2.6-2.8-12.1-19.2-34.1-33-44.3-2.9-2.1-5.5-4-5.8-4.3-2.8-2.2-4.9-3.1-7.2-3.1-2.1 0-2.6-0.2-2.7-1.3-0.2-1.5-5.7-4.5-6.3-3.5-0.7 1.1-2.4 0.6-2.7-0.7-0.4-1.3-1.2-1.6-5.8-2.1-1.6-0.2-4-0.9-5.5-1.6-3.9-1.8-5.3-2.2-10.2-2.6-4.6-0.4-25.2-0.7-25.5-0.3zm74.3 42.2c7.4 9.8 4.8 23.5-4.6 24.9-6.9 1-20.9-5.8-21-10.2 0-0.2-0.3-0.8-0.8-1.3-6.4-6.8-5-20.8 2.4-24.1 6.7-2.9 17.2 1.8 24 10.7zm-176.4 36.4c-0.1-0.1-4.6 1.1-5.9 1.6-0.7 0.3-3 1.2-5.1 2-9.9 3.8-15.1 6.8-19.6 11.5-3.4 3.5-3.3 4.5 0.5 8.7 1 1 11.3 7.6 12 7.6 0.2 0 1.7 0.6 3.4 1.3 1.6 0.8 3.6 1.6 4.3 1.9 1.8 0.8 9.3 3.3 9.9 3.3 0.3 0 0.3-2 0-4.4-0.6-5.6-0.6-24.5 0.1-29.6 0.3-2.1 0.5-3.9 0.4-3.9zm229.3-0.3c-0.2 0 0 1 0.2 2.1 0.6 2.8 0.6 31.3 0 34-0.5 2.2-0.4 2.2 1.3 1.8 3.1-0.7 12.9-4.5 18.3-7 8.5-4 14.3-10.1 12.6-13.3-1.1-2.1-6.7-7.2-7.9-7.2-0.4 0-0.9-0.2-1-0.5-0.4-1.1-11.8-6.1-19.2-8.5-2.3-0.7-4.2-1.4-4.3-1.4zm-199.4 63.4l-3.1-0.4 1.8 3.2c0.9 1.8 1.9 3.4 2.2 3.5 0.3 0.1 0.5 0.6 0.5 1.1 0 0.4 0.6 1.5 1.3 2.3 0.7 0.9 1.5 1.9 1.8 2.2 0.3 0.4 0.8 1.2 1.1 1.7 6.2 10.7 35.6 33.5 43.3 33.5 0.2 0 1.3 0.4 2.5 0.9 2.5 1.2 10.6 3.4 15.3 4.2 9.5 1.8 11.6 2.1 17.4 2.1 6.6 0 16.4-1.3 22.9-3 2.2-0.5 5.2-1.3 6.8-1.7 1.6-0.3 3.2-0.9 3.5-1.2 0.4-0.3 1.1-0.6 1.6-0.6 2.3 0 22-10.6 24-12.9 0.2-0.2 2.2-1.9 4.5-3.7 5.7-4.5 11.8-11 17.1-18.4 1.6-2.3 3.2-4.5 3.6-4.9 0.4-0.4 0.7-1 0.7-1.2 0-0.2 0.8-1.9 1.9-3.6 1.1-1.7 1.9-3.2 1.9-3.4 0-0.2-3.8 0.4-11 1.6-31.7 5.4-85.1 6.7-126.9 3.1-9.6-0.8-23.1-2.3-27.8-3.2-2.2-0.4-5.3-0.9-6.9-1.2z"
class="s0"></path></g
></svg
>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,102 @@
---
import { Icon } from "astro-icon/components";
import { getLangFromUrl, useTranslations } from "../i18n/utils";
import HeaderButton from "./HeaderButton.astro";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
import { MARKETPLACE_ENABLED } from "astro:env/client";
---
<div class="h-full mt-16 flex w-full flex-col justify-evenly bg-navbar-color m-auto" id="mobilenav">
<HeaderButton text={t("header.home")} route={`/${lang}/`}>
<Icon
name="ph:house-bold"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
<HeaderButton text={t("header.games")} route={`/${lang}/games/`}>
<Icon
name="ph:cube"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
<HeaderButton
text={t("header.settings")}
route={`/${lang}/settings/appearance`}
>
<Icon
name="ph:wrench-fill"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
{MARKETPLACE_ENABLED &&
<HeaderButton text={t("header.catalog")} route={`/${lang}/catalog/1`}>
<Icon
name="ph:shopping-bag-open-fill"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
}
<HeaderButton text={t("header.morelinks")}>
<Icon
name="ph:link-bold"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
</div>
<style is:global>
#mobilenav {
-webkit-transition-duration: 600ms;
transition-duration: 600ms;
transform: translateX(100%);
}
#mobilenavwrapper {
-webkit-transition-duration: 600ms;
transition-duration: 600ms;
transform: translateX(100%);
}
</style>
<script>
import { EventHandler } from "@utils/events";
import { Elements } from "@utils/index";
const init = async () => {
const els = Elements.select([
{ type: 'id', val: 'mobileNavTrigger' },
{ type: 'id', val: 'right_caret' },
{ type: 'id', val: 'hamburger_menu' },
{ type: 'id', val: 'mobilenavwrapper' },
{ type: 'id', val: 'mobilenav' }
]);
const trigger = Elements.exists<HTMLDivElement>(await els.next());
const rightCaret = Elements.exists<SVGElement>(await els.next());
const hamburgerMenu = Elements.exists<SVGElement>(await els.next());
const wrapper = Elements.exists<HTMLDivElement>(await els.next());
const mnm = Elements.exists<HTMLDivElement>(await els.next());
let isOpen = false;
Elements.attachEvent(trigger, "click", async () => {
console.log(isOpen);
if (isOpen) {
rightCaret.style.display = "none";
hamburgerMenu.style.display = "block";
mnm.style.transform = "translateX(100%)";
wrapper.style.transform = "translateX(100%)";
isOpen = false;
}
else {
hamburgerMenu.style.display = "none";
rightCaret.style.display = "block";
mnm.style.display = "flex";
mnm.style.transform = "translateX(0%)";
wrapper.style.transform = "translateX(0%)";
isOpen = true;
}
});
}
new EventHandler({
events: {
"astro:page-load": init
},
logging: false
}).bind();
</script>

View file

@ -0,0 +1,17 @@
---
const { title, route } = Astro.props;
---
<a
href={route}
class="snap-center snap-always group flex flex-col items-center md:p-0 max-sm:p-3 sm:p-3 bg-navbar-color w-full rounded-3xl md:flex-row md:bg-none md:rounded-none"
>
<div class="xl:p-2 max-sm:p-2 sm:p-2 md:p-0">
<slot />
</div>
<div
class="max-md:min-w-24 font-roboto text-center font-bold text-text-color roboto transition duration-500 group-hover:text-text-hover-color md:text-xl text-nowrap"
>
{title}
</div>
</a>

View file

@ -0,0 +1,58 @@
---
interface Props {
page: number | string | undefined;
lang: string;
}
interface Asset {
title: string;
description: string;
author: string;
image: string;
tags: [];
version: string;
background_image: string | null;
background_video: string | null;
payload: string;
type: "theme" | "plugin-sw" | "plugin-page";
}
const { page, lang } = Astro.props;
const getAssets = async () => {
const res = await fetch(new URL(`/api/catalog-assets?page=${page}`, Astro.url));
const data = await res.json();
return data.assets;
};
const assets = await getAssets();
---
<div class="text-3xl font-roboto font-bold text-text-color p-10">
{Object.keys(assets).length > 0 && (
<div class="flex flex-row gap-6 flex-wrap justify-center">
{Object.entries(assets).map((asset) => {
const pName = asset[0];
const a = asset[1] as unknown as Asset;
return (
<a href={`/${lang}/catalog/package/${pName}`}>
<div class="bg-navbar-color w-64 rounded-3xl shadow-lg overflow-hidden transition-transform duration-300 hover:scale-105 text-text-color">
<img src={`/packages/${pName}/${a.image}`} alt={a.title} class="w-full h-40 object-cover" />
<div class="p-6 text-sm">
<p class="font-semibold text-2xl mb-2"> {a.title} </p>
<p class="mb-4"> {a.description} </p>
<div class="flex flex-wrap gap-2 mb-4 w-full">
{a.tags.map((tag) => (
<p class="bg-navbar-text-color text-navbar-color font-bold px-3 py-1 rounded-md text-center"> { tag } </p>
))}
</div>
<p>
<strong>Version: </strong> { a.version }
</p>
<p>
<strong>Type: </strong> { a.type === "plugin-page" || a.type === "plugin-sw" ? "plugin" : a.type }
</p>
</div>
</div>
</a>
)
}
)}
</div>
)}
</div>

View file

@ -0,0 +1,107 @@
<div id="parent" class="flex flex-row flex-wrap gap-4 items-center font-roboto justify-center">
</div>
<div class="w-0 h-0 visibility-none hidden"> <asset-loader1 /> </div>
<script>
import { Elements } from "@utils/index";
import { Marketplace } from "@utils/marketplace";
import { SettingsVals } from "@utils/values";
type Item = {
description: string,
image: string,
package_name: string,
payload: string,
tags: [],
background_video: string,
background_image: string,
title: string,
type: string,
version: string
}
const getItem = async (item: any) => {
try {
const res = await fetch(new URL(`/api/packages/${item.name}`, window.location.origin));
const data = await res.json();
return { ...data, package_name: item.name }
}
catch (err: any) {
console.log(`Err in themes: ${err}`);
return null;
}
}
const constructElements = async (item: Item, parent: HTMLDivElement, marketplace: Marketplace) => {
const main = document.createElement("div");
main.classList.add("rounded-3xl", "bg-navbar-color", "w-64", "flex", "flex-col", "cursor-pointer", "border-text-color");
main.dataset.name = item.package_name
const click = document.createElement("div");
click.classList.add("w-full");
const img = document.createElement("img");
img.classList.add("aspect-[16/9]", "rounded-t-3xl");
img.src = `/packages/${item.package_name}/${item.image}`;
img.alt = `${item.type}-${item.package_name}`;
img.loading = "lazy";
const info = document.createElement("div");
info.classList.add("h-2/6", "text-center", "content-center", "p-3", "font-semibold", "items-center", "flex", "flex-col");
const infoTitle = document.createElement("p");
infoTitle.classList.add("text-2xl");
infoTitle.innerHTML = item.title;
const infoInner = document.createElement("div");
infoInner.classList.add("flex", "flex-row");
const infoInnerDelete = document.createElement("div");
infoInnerDelete.classList.add("h-8", "w-8", "cursor-pointer");
// This is cursed yes. SVG's SUUUCK
infoInnerDelete.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16M112 168a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm0-120H96v-8a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8Z" /></svg>`;
const infoInnerOpen = document.createElement("a");
infoInnerOpen.classList.add("h-8", "w-8", "cursor-pointer");
infoInnerOpen.href = `../catalog/package/${item.package_name}`;
infoInnerOpen.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M192 136v72a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16V80a16 16 0 0 1 16-16h72a8 8 0 0 1 0 16H48v128h128v-72a8 8 0 0 1 16 0m32-96a8 8 0 0 0-8-8h-64a8 8 0 0 0-5.66 13.66L172.69 72l-42.35 42.34a8 8 0 0 0 11.32 11.32L184 83.31l26.34 26.35A8 8 0 0 0 224 104Z" /></svg>`;
click.appendChild(img);
infoInner.appendChild(infoInnerDelete);
infoInner.appendChild(infoInnerOpen);
info.appendChild(infoTitle);
info.appendChild(infoInner);
main.appendChild(click);
main.appendChild(info);
parent.appendChild(main);
Elements.attachEvent(infoInnerDelete, "click", async () => {
await marketplace.uninstallPlugin({ name: item.package_name, type: item.type as "page" | "serviceWorker" })
parent.removeChild(main);
});
}
const init = async () => {
await Marketplace.ready();
const mp = Marketplace.getInstances().next().value!;
const els = Elements.select([
{ type: 'id', val: 'parent' }
]);
const itemsJSON = JSON.parse(await mp.getValueFromStore(SettingsVals.marketPlace.plugins)) || [];
const filteredItems = itemsJSON.filter((dat: any) => dat.remove !== true);
const itemPromises = filteredItems.map(getItem);
const itemsArray = await Promise.all(itemPromises);
return {
items: itemsArray.filter((data) => data !== null),
elements: els,
marketplace: mp
}
}
Elements.createCustomElement("asset-loader1", async () => {
const { items, elements, marketplace } = await init();
const parentElem = Elements.exists<HTMLDivElement>(await elements.next());
const promises = items.map(item => constructElements(item, parentElem, marketplace));
await Promise.all(promises);
});
</script>

View file

@ -0,0 +1,138 @@
<div id="parent" class="flex flex-row flex-wrap gap-4 items-center font-roboto justify-center">
<div id="main-theme" class="rounded-3xl bg-navbar-color w-64 flex flex-col cursor-pointer">
<div class="w-full">
<img src="/classic_theme.png" alt="Classic nebula" class="aspect-[16/9] rounded-t-3xl" loading="eager" />
</div>
<div class="h-2/6 text-center content-center p-3 font-semibold items-center flex flex-col text-2xl">
Classic Nebula
</div>
</div>
</div>
<div class="w-0 h-0 visibility-none hidden">
<asset-loader />
</div>
<script>
import { Elements } from "@utils/index";
import { Marketplace } from "@utils/marketplace";
import { SettingsVals } from "@utils/values";
type Item = {
description: string,
image: string,
package_name: string,
payload: string,
tags: [],
background_video: string,
background_image: string,
title: string,
type: string,
version: string
}
const getItem = async (item: any) => {
try {
const res = await fetch(new URL(`/api/packages/${item}`, window.location.origin));
const data = await res.json();
return { ...data, package_name: item }
}
catch (err: any) {
console.log(`Err in themes: ${err}`);
return null;
}
}
const constructElements = async (item: Item, mainTheme: HTMLDivElement, parent: HTMLDivElement, marketplace: Marketplace) => {
const main = document.createElement("div");
main.classList.add("rounded-3xl", "bg-navbar-color", "w-64", "flex", "flex-col", "cursor-pointer", "border-text-color");
main.dataset.name = item.package_name
const click = document.createElement("div");
click.classList.add("w-full");
const img = document.createElement("img");
img.classList.add("aspect-[16/9]", "rounded-t-3xl");
img.src = `/packages/${item.package_name}/${item.image}`;
img.alt = `${item.type}-${item.package_name}`;
img.loading = "lazy";
const info = document.createElement("div");
info.classList.add("h-2/6", "text-center", "content-center", "p-3", "font-semibold", "items-center", "flex", "flex-col");
const infoTitle = document.createElement("p");
infoTitle.classList.add("text-2xl");
infoTitle.innerHTML = item.title;
const infoInner = document.createElement("div");
infoInner.classList.add("flex", "flex-row");
const infoInnerDelete = document.createElement("div");
infoInnerDelete.classList.add("h-8", "w-8", "cursor-pointer");
// This is cursed yes. SVG's SUUUCK
infoInnerDelete.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16M112 168a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm0-120H96v-8a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8Z" /></svg>`;
const infoInnerOpen = document.createElement("a");
infoInnerOpen.classList.add("h-8", "w-8", "cursor-pointer");
infoInnerOpen.href = `../catalog/package/${item.package_name}`;
infoInnerOpen.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M192 136v72a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16V80a16 16 0 0 1 16-16h72a8 8 0 0 1 0 16H48v128h128v-72a8 8 0 0 1 16 0m32-96a8 8 0 0 0-8-8h-64a8 8 0 0 0-5.66 13.66L172.69 72l-42.35 42.34a8 8 0 0 0 11.32 11.32L184 83.31l26.34 26.35A8 8 0 0 0 224 104Z" /></svg>`;
click.appendChild(img);
infoInner.appendChild(infoInnerDelete);
infoInner.appendChild(infoInnerOpen);
info.appendChild(infoTitle);
info.appendChild(infoInner);
main.appendChild(click);
main.appendChild(info);
parent.appendChild(main);
Elements.attachEvent(mainTheme, "click", () => {
marketplace.theme({ type: 'remove' });
});
Elements.attachEvent(click, "click", async () => {
await marketplace.theme(
{
type: 'normal',
payload: item.payload,
sources: {
video: item.background_video,
bg: item.background_image
},
name: item.package_name
}
);
});
Elements.attachEvent(infoInnerDelete, "click", async () => {
await marketplace.uninstallTheme({ name: item.package_name });
parent.removeChild(main);
await marketplace.theme({ type: 'remove' });
});
}
const init = async () => {
await Marketplace.ready();
const mp = Marketplace.getInstances().next().value!;
const els = Elements.select([
{ type: 'id', val: 'main-theme' },
{ type: 'id', val: 'parent' }
]);
const itemsJSON = JSON.parse(await mp.getValueFromStore(SettingsVals.marketPlace.themes)) || [];
const itemPromises = itemsJSON.map(getItem);
const itemsArray = await Promise.all(itemPromises);
return {
items: itemsArray.filter((data) => data !== null),
elements: els,
marketplace: mp
}
}
Elements.createCustomElement("asset-loader", async () => {
const { items, elements, marketplace } = await init();
const mainTheme = Elements.exists<HTMLDivElement>(await elements.next());
const parentElem = Elements.exists<HTMLDivElement>(await elements.next());
const promises = items.map(item => constructElements(item, mainTheme, parentElem, marketplace));
await Promise.all(promises);
});
</script>

View file

@ -0,0 +1,19 @@
---
import { Image } from "astro:assets";
import { type ImageMetadata } from "astro";
const images = import.meta.glob<{ default: ImageMetadata }>(
"/src/assets/credits/*.{jpeg,jpg,png,gif,webp}"
);
interface Props {
image?: string;
name: string;
link: string;
}
const { image, name, link } = Astro.props;
---
<a class="rounded-md bg-navbar-color h-50 w-50 p-2 flex flex-col items-center" href={link} target="_blank" rel="noopener noreferrer">
{image && <Image loading='lazy' class='w-32 h-32 object-cover rounded-md' src={images[image]()} alt={name} />}
<p class="h-12 w-full text-text-color flex items-center justify-center text-xl font-semibold">{name}</p>
</a>

View file

@ -0,0 +1,70 @@
<script>
import { EventHandler } from "@utils/events";
import { SW, createProxyScripts, checkProxyScripts, createBareMuxConn, setTransport } from "@utils/serviceWorker";
import { Settings } from "@utils/settings";
import { log } from "@utils/index";
import { Marketplace } from "@utils/marketplace";
import { SettingsVals } from "@utils/values";
const titleText = `
_ _ _ _ ____ _
| \\ | | ___| |__ _ _| | __ _ / ___| ___ _ ____ _(_) ___ ___ ___
| \\| |/ _ \\ '_ \\| | | | |/ _' | \\___ \\ / _ \\ '__\\ \\ / / |/ __/ _ \\/ __|
| |\\ | __/ |_) | |_| | | (_| | ___) | __/ | \\ V /| | (_| __/\\__ \\
|_| \\_|\\___|_.__/ \\__,_|_|\\__,_| |____/ \\___|_| \\_/ |_|\\___\\___||___/
`;
const info = "Hello developer or curious individual & welcome to the console! \nThere isn't a whole lot here for you unless you have run into an error.";
const sysInfo = `In which case please include the info below when opening the issue: \n\nOS: ${navigator.platform} \nBrowser: ${navigator.userAgent} \nService workers: ${"serviceWorker" in navigator ? "Yes" : "No"}`
const init = async (): Promise<Marketplace> => {
log({ type: 'normal', bg: false, prefix: false }, titleText);
log({ type: 'normal', bg: true, prefix: false }, info);
log({ type: 'normal', bg: true, prefix: false }, sysInfo);
log({ type: 'info', bg: true, prefix: true }, "General init...");
for (const script of createProxyScripts()) {
document.body.appendChild(script);
}
await checkProxyScripts();
const conn = await createBareMuxConn("/baremux/worker.js");
const sw = new SW(conn);
const mp = new Marketplace();
const { serviceWorker, bareMuxConn, sj } = await sw.getSWInfo();
log({ type: 'info', bg: true, prefix: true }, `General init completed! \n\nServiceWorker: ${serviceWorker.active?.state} \nBareMuxConn: ${bareMuxConn ? 'Active': 'Not active'} \nScramjetController: ${sj ? 'Active' : 'Not active'}`);
return mp;
}
const handleTheme = async (marketplace: Marketplace) => {
const name = await marketplace.getValueFromStore(SettingsVals.marketPlace.appearance.theme.name);
const payload = await marketplace.getValueFromStore(SettingsVals.marketPlace.appearance.theme.payload);
const video = await marketplace.getValueFromStore(SettingsVals.marketPlace.appearance.video);
const image = await marketplace.getValueFromStore(SettingsVals.marketPlace.appearance.image);
if (name !== null) {
marketplace.theme({
type: 'normal',
name: name,
payload: payload,
sources: {
video: video,
bg: image
}
});
}
}
const initSettings = async (marketplace: Marketplace) => {
log({ type: 'info', bg: true, prefix: true }, "Initializing settings...");
for await (const _ of Settings.initDefaults());
await handleTheme(marketplace);
log({ type: 'info', bg: true, prefix: true }, "Initialized Settings!");
}
const eventHandler = new EventHandler({
events: {
"DOMContentLoaded": async () => {
const mp = await init();
await initSettings(mp);
}
},
logging: true
});
//bind the events
eventHandler.bind();
</script>

View file

@ -0,0 +1,67 @@
---
interface Inputs {
input: boolean;
required?: boolean;
placeholder?: string;
validate?: boolean;
validateString?: string;
}
interface SelectOptions {
value: string;
name: string;
disabled: boolean;
}
interface Both {
enabled: boolean;
showOnSelect?: {
value: string;
};
showOnInput?: boolean;
}
interface Selects {
select: boolean;
name?: string;
multiple?: boolean;
options?: SelectOptions[];
}
interface Buttons {
name: string;
id: string;
}
interface Props {
title: string;
description: string;
input: Inputs;
select: Selects;
both: Both;
button: Buttons;
}
const { title, description, input, select, both, button } = Astro.props;
---
<div class={`${both.enabled ? "h-72" : "h-64"} w-64 rounded-3xl bg-navbar-color flex flex-col items-center p-4`}>
<h1 class="text-3xl font-bold mb-2"> { title } </h1>
<p class="text-md w-full text-ellipsis text-center"> { description } </p>
<div class="w-full h-full flex-grow flex justify-center items-center flex-col gap-4">
<!-- We only want to render an input if it's enabled -->
{(input.input && !both.enabled) &&
<input class="text-md w-full h-10 p-2 bg-input border border-input-border-color rounded-md text-input-text" required={input.required} placeholder={input.placeholder}></input>
}
<!-- Same with dropdown selections -->
{select.select &&
<select id={select.name?.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()} class="text-md w-full h-10 p-2 bg-input border border-input-border-color rounded-md text-input-text" multiple={select.multiple} name={select.name}>
{select.options!.map((option) => (
<option id={option.value} disabled={option.disabled} value={option.value}>{option.name}</option>
))}
</select>
}
{(both.enabled && both.showOnSelect?.value) &&
<input id={'inputOnSelectValue' + both.showOnSelect?.value} class="hidden text-md w-full h-10 p-2 bg-input border border-input-border-color rounded-md text-input-text" required={input.required} placeholder={input.placeholder}></input>
}
<button id={button.id.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()} class="w-36 h-10 rounded-md border border-input-border-color text-input-text bg-input hover:border-input hover:bg-input-border-color hover:text-input hover:font-bold active:bg-input active:text-input-text transition-all duration-200">
{button.name}
</button>
</div>
</div>

View file

@ -0,0 +1,13 @@
---
const { image, title } = Astro.props;
import { Image } from "astro:assets";
---
<div class="rounded-3xl bg-navbar-color w-64 flex flex-col">
<div class="w-full">
<Image src={image} alt="Theme" class="aspect-[16/9] rounded-t-3xl" />
</div>
<div class="h-2/6 text-center content-center p-3 font-semibold">
{title}
</div>
</div>

Some files were not shown because too many files have changed in this diff Show more