Compare commits

..

No commits in common. "main" and "v7.11.8" have entirely different histories.

141 changed files with 6180 additions and 14021 deletions

View file

@ -1,12 +0,0 @@
{
"$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 }
}

12
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,12 @@
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

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

View file

@ -1,17 +0,0 @@
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 Normal file
View file

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

View file

@ -1,50 +0,0 @@
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

View file

@ -1,50 +0,0 @@
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,43 +1,10 @@
# build output
dist/
server/*.js
# generated types
.astro/
# dependencies # dependencies
node_modules/ /node_modules
package-lock.json
#external assets # System Files
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
# jetbrains setting folder memory.txt
.idea/ old.app.js
# nebula catalog database
database.sqlite
# YOUR config
config.toml
# Goofy PNPM problem
~/

3
.gitmodules vendored
View file

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

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
node_modules

6
.prettierrc Normal file
View file

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

86
.replit Normal file
View file

@ -0,0 +1,86 @@
entrypoint = "app.js"
hidden = [".config", "package-lock.json"]
[interpreter]
command = [
"prybar-nodejs",
"-q",
"--ps1",
"\u0001\u001b[33m\u0002\u0001\u001b[00m\u0002 ",
"-i"
]
[[hints]]
regex = "Error \\[ERR_REQUIRE_ESM\\]"
message = "We see that you are using require(...) inside your code. We currently do not support this syntax. Please use 'import' instead when using external modules. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)"
[nix]
channel = "stable-22_11"
[env]
XDG_CONFIG_HOME = "/home/runner/.config"
PATH = "/home/runner/$REPL_SLUG/.config/npm/node_global/bin:/home/runner/$REPL_SLUG/node_modules/.bin"
npm_config_prefix = "/home/runner/$REPL_SLUG/.config/npm/node_global"
[gitHubImport]
requiredFiles = [".replit", "replit.nix", ".config", "package.json", "package-lock.json"]
[packager]
language = "nodejs"
[packager.features]
packageSearch = true
guessImports = true
enabledForHosting = false
[unitTest]
language = "nodejs"
[debugger]
support = true
[debugger.interactive]
transport = "localhost:0"
startCommand = [ "dap-node" ]
[debugger.interactive.initializeMessage]
command = "initialize"
type = "request"
[debugger.interactive.initializeMessage.arguments]
clientID = "replit"
clientName = "replit.com"
columnsStartAt1 = true
linesStartAt1 = true
locale = "en-us"
pathFormat = "path"
supportsInvalidatedEvent = true
supportsProgressReporting = true
supportsRunInTerminalRequest = true
supportsVariablePaging = true
supportsVariableType = true
[debugger.interactive.launchMessage]
command = "launch"
type = "request"
[debugger.interactive.launchMessage.arguments]
args = []
console = "externalTerminal"
cwd = "."
environment = []
pauseForSourceMap = false
program = "./index.js"
request = "launch"
sourceMaps = true
stopOnEntry = false
type = "pwa-node"
[languages]
[languages.javascript]
pattern = "**/{*.js,*.jsx,*.ts,*.tsx}"
[languages.javascript.languageServer]
start = "typescript-language-server --stdio"

View file

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

11
.vscode/launch.json vendored
View file

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

10
.vscode/settings.json vendored Normal file
View file

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

View file

@ -1,36 +0,0 @@
# 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,19 +1,7 @@
FROM node:22-alpine FROM node:alpine
WORKDIR /usr/src/app
WORKDIR /app
COPY package*.json .
COPY . . COPY . .
RUN npm install
RUN apk update RUN npm ci
RUN apk add python3 py3-pip alpine-sdk openssl-dev build-base python3-dev EXPOSE 3000
RUN python3 -m pip install setuptools --break-system-packages CMD ["npm", "start"]
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) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (C) 2022 Nebula Services
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.

542
README.md
View file

@ -1,371 +1,213 @@
<div align="center"> # Nebula
<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> ![license](https://img.shields.io/badge/License-GNU%20AGPL%20v3-blue)
<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>
</div> ![chat](https://img.shields.io/badge/chat-1139%20online-brightgreen)
<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:
- This will **NOT** deploy on GitHub Pages, Netlify, Vercel, Gitlab Pages, or any other _static_ host
- This will **NOT** work on Render
---
## 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
- Multiple Proxy "Backends": - Stunning and highly functional UI with multiple themes
- [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet) - XOR/b64 encoding all traffic
- [RammerHead](https://github.com/binary-person/rammerhead) - Hides your IP from sites
- [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
[![Deploy to Heroku](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/master/buttons/remade/heroku.svg)](https://heroku.com/deploy/?template=https://github.com/NebulaServices/Nebula)
<br>
[![Run on Replit](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/master/buttons/remade/replit.svg)](https://replit.com/github/NebulaServices/Nebula)
<br>
[![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)
<br>
[![Deploy to Render](https://raw.githubusercontent.com/BinBashBanana/deploy-buttons/main/buttons/remade/render.svg)](https://render.com/deploy?repo=https://github.com/NebulaServices/Nebula)
--- ---
## Contributors ## Deployment Configuration Guide
(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
- [Astro](https://astro.build) - HTML, JS, CSS
- [Fastify](https://fastify.dev) - Partical.JS (Specifically v4, 5, 6.1 &< only)
- [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet) - Ultraviolet (proxy)
- [RammerHead](https://github.com/binary-person/rammerhead) - Osana (proxy)
- [Epoxy](https://github.com/mercuryworkshop/epoxy-tls) - TompHTTP Bare Server Node
- [Libcurl.js](https://github.com/ading2210/libcurl.js) - ExpressJS
- HTML, CSS, and JavaScript (DUH)
---
## Catalog/Marketplace ## Support
- By default, the marketplace is enabled and uses SQLite For support, email chloe@nebula.bio or join our discord: discord.gg/unblocker
- 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)
### How to make a theme ## Demo
- Themes allow you to customize Nebula's *look*. [Click here to see a demo of Nebula](https://nebulaproxy.io/)
#### Prerequisites: ## Acknowledgements
- Make sure you have our [Discord server](https://discord.gg/unblocker) so you can submit your theme
##### Making the themes: - [UV (one of the proxies use)](https://github.com/titaniumnetwork-dev/Ultraviolet)
- [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)
1. Firstly, copy the CSS vars: ## License
```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: ;
}
```
> [!NOTE] (Nebula's license is now GNU AGPL V3 as of v7.10)
> Copyright Nebula Services 2021 - Present
> You can add a custom font as well! To do so, add this to your `:root` <br>
> 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 Normal file
View file

@ -0,0 +1,249 @@
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";
const PORT = process.env.PORT || 3000;
const __dirname = process.cwd();
const ACTIVE_CODES = new Set();
if (!fs.existsSync("./memory.txt")) {
fs.writeFileSync("./memory.txt", "", "utf-8");
}
let TOKENS = fs
.readFileSync("./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(
"./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);
}

7
app.json Normal file
View file

@ -0,0 +1,7 @@
{
"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"]
}

View file

@ -1,133 +0,0 @@
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"
})
});

View file

@ -1,32 +0,0 @@
{
"$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"
}
}
}

View file

@ -1,23 +0,0 @@
[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

@ -1,22 +0,0 @@
: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.

Before

Width:  |  Height:  |  Size: 9.6 KiB

View file

@ -1,18 +0,0 @@
: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.

Before

Width:  |  Height:  |  Size: 56 KiB

View file

@ -1,19 +0,0 @@
: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.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1,24 +0,0 @@
: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.

Before

Width:  |  Height:  |  Size: 50 KiB

23
deployment.config.json Normal file
View file

@ -0,0 +1,23 @@
{
"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"
}
}
}

View file

@ -1,21 +0,0 @@
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,20 +1,10 @@
version: "3"
services: services:
nebula: nebula:
image: ghcr.io/nebulaservices/nebula:latest image: nebula:latest
build: .
container_name: nebula container_name: nebula
restart: unless-stopped restart: unless-stopped
ports: ports:
# HOST:CONTAINER (DO NOT CHANGE THE CONTAINER PORT, UNLESS EDITED IN THE config.toml FILE) # DO NOT CHANGE 3000!
- 8080:8080 - your port here:3000
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

8
ecosystem.config.js Normal file
View file

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

2551
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,66 +1,28 @@
{ {
"name": "nebula", "name": "nebula-web",
"version": "7.11.6",
"description": "Explore the web. Freely.",
"type": "module", "type": "module",
"version": "9.1.0", "main": "app.js",
"private": true,
"scripts": { "scripts": {
"dev": "astro dev --host 0.0.0.0 & tsx --watch server/server.ts", "start": "node app.js"
"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": {
"@astrojs/check": "^0.9.4", "@sendgrid/mail": "^7.7.0",
"@astrojs/node": "^9.0.0", "@titaniumnetwork-dev/ultraviolet": "^1.0.8-beta",
"@astrojs/sitemap": "^3.2.1", "@tomphttp/bare-server-node": "^1.2.3",
"@astrojs/svelte": "^7.0.2", "bcrypt": "^5.1.0",
"@astrojs/tailwind": "^5.1.4", "cookie-parser": "^1.4.6",
"@fastify/compress": "^8.0.1", "express": "^4.18.2",
"@fastify/helmet": "^13.0.0", "nodemailer": "^6.9.1",
"@fastify/middie": "^9.0.2", "uuid": "^9.0.0"
"@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"
}, },
"devDependencies": { "engines": {
"@biomejs/biome": "^1.9.4", "node": ">=18"
"@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

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1,16 +0,0 @@
<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
>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1,19 +0,0 @@
: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

@ -0,0 +1,2 @@
(()=>{"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

@ -0,0 +1 @@
{"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

@ -0,0 +1,2 @@
(()=>{"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

@ -0,0 +1,154 @@
{
"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

@ -0,0 +1,46 @@
// 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

@ -0,0 +1,356 @@
// 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:30px;"
)
);
setTimeout(
console.log.bind(
console,
"%c If 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:12px;"
)
);
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 = "No browser detection";
}
console.log.bind(
console,
`%cInformation: \n URL: ${diagnosticDomain} \n BrowserName: ${browserName} \n IsOnline: ${online} \n UA: ${userAgent}, `,
"background: gray;color:#FFF;padding:3px;border-radius: 0px; font-size:12px;"
);
}
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/");
}
}

111
public/resources/options.js Normal file
View file

@ -0,0 +1,111 @@
// 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");
console.log(selectedOption);
}
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() {
console.log("Checked");
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.getItem("ADVcloak");
function toggleClickoff() {
if (option2 === "on") {
option2 = "off";
localStorage.setItem("ADVcloak", "off");
} else {
option2 = "on";
localStorage.setItem("ADVcloak", "on");
}
}
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

@ -0,0 +1,33 @@
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

@ -0,0 +1,103 @@
// 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;
}

189
public/resources/theme.js Normal file
View file

@ -0,0 +1,189 @@
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.");
}
}
}

43
public/resources/v.js Normal file
View file

@ -0,0 +1,43 @@
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=/";
}

500
public/style/main.css Normal file
View file

@ -0,0 +1,500 @@
@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%;
}
}

556
public/style/options.css Normal file
View file

@ -0,0 +1,556 @@
@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,75 +1,14 @@
importScripts("/uv/uv.bundle.js"); importScripts("./uv/uv.bundle.js");
importScripts("/uv/uv.config.js"); importScripts("./uv/uv.config.js");
importScripts('/scram/scramjet.wasm.js'); importScripts("./uv/uv.sw.js");
importScripts('/scram/scramjet.shared.js'); importScripts("./osana/osana.worker.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
});
}
//where we handle our plugins!!! const UV = new UVServiceWorker();
self.addEventListener("message", function (event) { const Osana = new OsanaServiceWorker();
console.log(event.data);
uv.config.inject = [];
//loop over the required data (we don't verify here as types will take care of us :D)
event.data.forEach((data) => {
if (data.remove) {
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) { self.addEventListener("fetch", (event) => {
event.respondWith( if (event.request.url.startsWith(location.origin + "/service/go/"))
(async () => { event.respondWith(UV.fetch(event));
const wwRes = await ww.run(event)(); if (event.request.url.startsWith(location.origin + "/service/~osana/"))
if (wwRes.includes(null)) { event.respondWith(Osana.fetch(event));
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,30 +1,10 @@
self.__uv$config = { self.__uv$config = {
prefix: "/~/uv/", prefix: "/service/go/",
bare: "/bare/", bare: "/bare/",
encodeUrl: function encode(str) { encodeUrl: Ultraviolet.codec.xor.encode,
if (!str) return str; decodeUrl: Ultraviolet.codec.xor.decode,
return encodeURIComponent( handler: "/uv/uv.handler.js",
str bundle: "/uv/uv.bundle.js",
.toString() config: "/uv/uv.config.js",
.split("") sw: "/uv/uv.sw.js"
.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

@ -1,171 +0,0 @@
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
};
}
}

10
render.yaml Normal file
View file

@ -0,0 +1,10 @@
services:
- type: web
name: Nebula
env: docker
repo: https://github.com/NebulaServices/Nebula.git
region: oregon
plan: free
branch: main
numInstances: 1
healthCheckPath: /

8
replit.nix Normal file
View file

@ -0,0 +1,8 @@
{ pkgs }: {
deps = [
pkgs.nodejs-18_x
pkgs.nodePackages.typescript-language-server
pkgs.yarn
pkgs.replitPackages.jest
];
}

View file

@ -1,95 +0,0 @@
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 };

View file

@ -1,98 +0,0 @@
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
View file

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

View file

@ -1,229 +0,0 @@
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 };

View file

@ -1,93 +0,0 @@
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);
}
});

View file

@ -1,29 +0,0 @@
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 };

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,61 +0,0 @@
---
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

@ -1,71 +0,0 @@
---
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

@ -1,16 +0,0 @@
---
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

@ -1,23 +0,0 @@
<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>

View file

@ -1,15 +0,0 @@
<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
>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -1,102 +0,0 @@
---
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

@ -1,17 +0,0 @@
---
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

@ -1,58 +0,0 @@
---
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

@ -1,107 +0,0 @@
<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

@ -1,138 +0,0 @@
<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

@ -1,19 +0,0 @@
---
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

@ -1,70 +0,0 @@
<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

@ -1,67 +0,0 @@
---
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>

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