Compare commits
5 Commits
api-worker
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c44bc69d9f | ||
|
|
7d93ee3a7d | ||
|
|
79e1984940 | ||
|
|
e6526a3666 | ||
|
|
f9a976ef1d |
11
.dockerignore
Normal file
@@ -0,0 +1,11 @@
|
||||
.vscode
|
||||
.DS_Store
|
||||
.Trashes
|
||||
.nuxt
|
||||
.output
|
||||
dist
|
||||
node_modules
|
||||
.env
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
generated
|
||||
13
.gitattributes
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
* text eol=lf
|
||||
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.mp4 binary
|
||||
*.mp3 binary
|
||||
*.gz binary
|
||||
*.zip binary
|
||||
*.ttf binary
|
||||
*.woff binary
|
||||
30
.github/CONTRIBUTING.md
vendored
@@ -1,30 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
## Requiered knowledge
|
||||
|
||||
- JavaScript
|
||||
- html5
|
||||
- NodeJS
|
||||
|
||||
Additional:
|
||||
|
||||
- CSS
|
||||
- [VueJS](https://vuejs.org/)
|
||||
- [ElectronJS](https://electronjs.org/)
|
||||
- [NPMjs](https://www.npmjs.com/)
|
||||
|
||||
A source code editor is also requiered. We recommend [Visual Studio Code](https://code.visualstudio.com/).
|
||||
|
||||
### Installing the components
|
||||
|
||||
1. Install [Git](https://git-scm.com/)
|
||||
2. Install [Node](https://nodejs.org/en/)
|
||||
|
||||
### Cloning the project
|
||||
|
||||
1. Fork the [repository](https://github.com/PreMiD/PreMiD)
|
||||
2. Open a terminal and type `git clone https://github.com/PreMiD/PreMiD`
|
||||
|
||||
### Coding your vision
|
||||
|
||||
Please keep the structure. We don't want to disorganize our project. Chaotic files may not be accepted.
|
||||
BIN
.github/Electron/Chrome_bsp.png
vendored
|
Before Width: | Height: | Size: 332 KiB |
BIN
.github/Electron/PMD_Banner.png
vendored
|
Before Width: | Height: | Size: 682 KiB |
2
.github/FUNDING.yml
vendored
@@ -1,3 +1,3 @@
|
||||
github: Timeraa
|
||||
github: [PreMiD, Timeraa]
|
||||
patreon: Timeraa
|
||||
ko_fi: Timeraa
|
||||
|
||||
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
---
|
||||
|
||||
<!-- NOTE -->
|
||||
<!-- Keep all presence related bugs in our Presences repository! -->
|
||||
<!-- https://github.com/PreMiD/Presences/issues -->
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,16 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for PreMiD
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
BIN
.github/Logo.png
vendored
|
Before Width: | Height: | Size: 11 KiB |
BIN
.github/Patreon.png
vendored
|
Before Width: | Height: | Size: 3.0 KiB |
1
.github/PayPal.svg
vendored
|
Before Width: | Height: | Size: 5.1 KiB |
13
.github/SUPPORT.md
vendored
@@ -1,13 +0,0 @@
|
||||
# How to get support
|
||||
|
||||
## Take a look at the [wiki](https://wiki.premid.app)
|
||||
Our GitHub wiki is full of information around PreMiD.<br>
|
||||
Take a look and feel free to contribute if you want to add something new.
|
||||
|
||||
## [Open a issue](https://github.com/PreMiD/PreMiD/issues/new/choose) on [GitHub](https://github.com/PreMiD/PreMiD)
|
||||
Simply open a issue if you don't feel allright.<br>
|
||||
*Aand there he goes...*
|
||||
|
||||
## Ask a staff member in [#support](https://discord.premid.app)
|
||||
The team is ready to tell you the secrets of the underworld.<br>
|
||||
Join our [Discord server](https://discord.premid.app) and find out what we're hiding.
|
||||
BIN
.github/TwitterButton.png
vendored
|
Before Width: | Height: | Size: 4.1 KiB |
36
.github/actions/build-and-push-docker/action.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Build and Push Docker Image
|
||||
description: Builds a Docker image and pushes it to GitHub Container Registry
|
||||
inputs:
|
||||
app:
|
||||
description: Name of the app
|
||||
required: true
|
||||
token:
|
||||
description: GitHub token
|
||||
required: true
|
||||
outputs:
|
||||
version:
|
||||
description: Version of the app
|
||||
value: ${{ steps.get_version.outputs.version }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get package.json version
|
||||
id: get_version
|
||||
run: echo ::set-output name=version::$(node -p "require('./apps/${{ inputs.app }}/package.json').version")
|
||||
shell: bash
|
||||
|
||||
- name: Convert repository owner to lowercase
|
||||
id: repo
|
||||
run: echo "::set-output name=lowercase::$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')"
|
||||
shell: bash
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
uses: premid/premid/.github/actions/build-docker@monorepo
|
||||
with:
|
||||
dockerfile: ./apps/${{ inputs.app }}/Dockerfile
|
||||
push: true
|
||||
token: ${{ inputs.token }}
|
||||
tags: ghcr.io/${{ steps.repo.outputs.lowercase }}/${{ inputs.app }}:${{ steps.get_version.outputs.version }},ghcr.io/${{ steps.repo.outputs.lowercase }}/${{ inputs.app }}:latest
|
||||
46
.github/actions/build-docker/action.yaml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Build Docker Image
|
||||
description: Builds a Docker image using Docker Buildx
|
||||
inputs:
|
||||
dockerfile:
|
||||
description: Path to the Dockerfile
|
||||
required: true
|
||||
tags:
|
||||
description: Comma-separated list of tags for the Docker image
|
||||
required: true
|
||||
push:
|
||||
description: Whether to push the Docker image to the registry
|
||||
required: false
|
||||
default: "false"
|
||||
token:
|
||||
description: GitHub Token
|
||||
required: false
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ inputs.push == 'true' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ inputs.token }}
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ inputs.push }}
|
||||
tags: ${{ inputs.tags }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
20
.github/dependabot.yml
vendored
@@ -1,20 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
commit-message:
|
||||
prefix: chore
|
||||
include: scope
|
||||
labels:
|
||||
- "dependencies"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
commit-message:
|
||||
prefix: chore
|
||||
include: scope
|
||||
labels:
|
||||
- "dependencies"
|
||||
BIN
.github/example.png
vendored
|
Before Width: | Height: | Size: 332 KiB |
5
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>Recodive/Recodive:renovate-config"],
|
||||
"automerge": false
|
||||
}
|
||||
65
.github/workflows/cd.yaml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: CD
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "*"
|
||||
permissions:
|
||||
packages: write
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docker Images
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- pd
|
||||
- schema-server
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and Push website
|
||||
uses: docker/build-push-action@v6
|
||||
if: matrix.target == 'website'
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: SERVICE=${{ matrix.target }}
|
||||
target: website
|
||||
tags: ghcr.io/premid/${{ matrix.target }}:beta-${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Get package.json version
|
||||
if: matrix.target != 'website'
|
||||
id: get_version
|
||||
run: echo ::set-output name=version::$(node -p "require('./apps/${{ matrix.target }}/package.json').version")
|
||||
shell: bash
|
||||
|
||||
- name: Build and push other images
|
||||
uses: docker/build-push-action@v6
|
||||
if: matrix.target != 'website' && startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
target: prod
|
||||
build-args: SERVICE=${{ matrix.target }}
|
||||
tags: ghcr.io/premid/${{ matrix.target }}:latest,ghcr.io/premid/${{ matrix.target }}:${{ steps.get_version.outputs.version }}
|
||||
68
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
name: Build, Lint and Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
name: Build, Lint and Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: package.json
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Codegen
|
||||
run: pnpm -r codegen
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
|
||||
build-docker:
|
||||
name: Build Docker Images
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- pd
|
||||
- schema-server
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Get Target
|
||||
id: get_target
|
||||
run: echo "target=$([[ ${{ matrix.target }} == 'website' ]] && echo 'website' || echo 'prod')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
target: ${{ steps.get_target.outputs.target }}
|
||||
build-args: SERVICE=${{ matrix.target }}
|
||||
98
.github/workflows/deploy.yml
vendored
@@ -1,98 +0,0 @@
|
||||
name: DePloY
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
env:
|
||||
NODE_ENV: DePloY
|
||||
jobs:
|
||||
package:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/setup-node@master
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm i
|
||||
npm i -g typescript rimraf
|
||||
- name: Prepare to package
|
||||
run: npm run init
|
||||
- name: Package
|
||||
run: |
|
||||
npm run pkg
|
||||
rimraf dist/app
|
||||
node util/zip dist ${{ matrix.os }}.zip --zip
|
||||
- name: Upload bundle
|
||||
env:
|
||||
SSHHOST: ${{ secrets.MAIN_HOST }}
|
||||
SSH_USERNAME: ${{ secrets.SSH_USERNAME }}
|
||||
SSH_KEY: ${{ secrets.SSH_KEY }}
|
||||
run: |
|
||||
tsc util/uploadFile util/zip
|
||||
node util/uploadFile ${{ matrix.os }}.zip /home/PreMiD/download/util/${{ matrix.os }}.zip
|
||||
createInstallers:
|
||||
runs-on: "ubuntu-latest"
|
||||
needs: package
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/setup-node@master
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo npm i
|
||||
sudo npm i -g typescript
|
||||
- name: Download InstallBuilder
|
||||
run: |
|
||||
wget https://clients.bitrock.com/installbuilder/installbuilder-enterprise-20.12.0-linux-x64-installer.run
|
||||
chmod u+x installbuilder-enterprise-20.12.0-linux-x64-installer.run
|
||||
- name: Install InstallBuilder
|
||||
run: |
|
||||
./installbuilder-enterprise-20.12.0-linux-x64-installer.run --installer-language en --prefix ./installbuilder --mode unattended
|
||||
echo "${{ secrets.IBLICENSE }}" > ./installbuilder/license.xml
|
||||
- name: Prepare Upgrade Installer
|
||||
run: |
|
||||
tsc util/prepare
|
||||
node util/prepare
|
||||
- name: Create Upgrade Installer (MacOS 64bit)
|
||||
run: |
|
||||
installbuilder/bin/builder build installer_assets/PreMiD-Upgrade.xml osx
|
||||
- name: Create Upgrade Installer (Windows)
|
||||
run: |
|
||||
installbuilder/bin/builder build installer_assets/PreMiD-Upgrade.xml windows
|
||||
- name: Upload files
|
||||
env:
|
||||
SSHHOST: ${{ secrets.MAIN_HOST }}
|
||||
SSH_USERNAME: ${{ secrets.SSH_USERNAME }}
|
||||
SSH_KEY: ${{ secrets.SSH_KEY }}
|
||||
run: |
|
||||
tsc util/uploadFile util/zip
|
||||
node util/uploadFile dist/installer/upgrader.exe /home/PreMiD/download/upgrader.exe
|
||||
node util/uploadFile dist/installer/upgrader.app.zip /home/PreMiD/download/util/upgrader.app.zip
|
||||
- name: Finalize build
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.MAIN_HOST }}
|
||||
username: ${{ secrets.SSH_USERNAME }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
script: |
|
||||
cd /home/PreMiD/download/util
|
||||
unzip upgrader.app.zip
|
||||
tar -czvf upgrader.app.tgz upgrader.app
|
||||
mv upgrader.app.tgz ../
|
||||
rm -rf upgrader.app upgrader.app.zip
|
||||
unzip windows-latest.zip
|
||||
cd windows-latest/PreMiD-win32-x64/
|
||||
zip -r ../../PreMiD-win32-x64.zip .
|
||||
mv ../../PreMiD-win32-x64.zip /home/PreMiD/download/
|
||||
cd ../PreMiD-win32-ia32/
|
||||
zip -r ../../PreMiD-win32-x86.zip .
|
||||
mv ../../PreMiD-win32-x86.zip /home/PreMiD/download/
|
||||
cd ../..
|
||||
rm -rf windows-latest windows-latest.zip
|
||||
unzip macOS-latest.zip
|
||||
cd macOS-latest/PreMiD-darwin-x64/
|
||||
zip -r ../../PreMiD-darwin-x64.zip .
|
||||
mv ../../PreMiD-darwin-x64.zip /home/PreMiD/download/
|
||||
cd ../..
|
||||
rm -rf macOS-latest macOS-latest.zip
|
||||
8
.gitignore
vendored
@@ -2,6 +2,8 @@ node_modules
|
||||
out
|
||||
dist
|
||||
tmp
|
||||
lib
|
||||
data
|
||||
|
||||
.vscode
|
||||
.env
|
||||
@@ -18,3 +20,9 @@ src/update.ini
|
||||
*.app
|
||||
*.xml.backup
|
||||
*.js
|
||||
!*.config.js
|
||||
|
||||
coverage
|
||||
*.tsbuildinfo
|
||||
.DS_Store
|
||||
*.log
|
||||
2
.husky/commit-msg
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
pnpm exec commitlint --edit $1
|
||||
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
*.js
|
||||
*.ts
|
||||
*.json
|
||||
22
@types/PreMiD/ExtensionSettings.d.ts
vendored
@@ -1,22 +0,0 @@
|
||||
export default interface ExtensionSettings {
|
||||
/**
|
||||
* If extension is enabled
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Autolaunch enabled
|
||||
*/
|
||||
autoLaunch: boolean;
|
||||
/**
|
||||
* Media keys enabled
|
||||
*/
|
||||
mediaKeys: boolean;
|
||||
/**
|
||||
* title menubar (TrayTitle)
|
||||
*/
|
||||
titleMenubar: boolean;
|
||||
/**
|
||||
* language of extension
|
||||
*/
|
||||
language: string;
|
||||
}
|
||||
16
@types/PreMiD/Presence.d.ts
vendored
@@ -1,16 +0,0 @@
|
||||
import * as Discord from "discord-rpc";
|
||||
|
||||
export default interface Presence {
|
||||
/**
|
||||
* Client ID of presence
|
||||
*/
|
||||
clientId: string;
|
||||
/**
|
||||
* Rich Procedual call connection
|
||||
*/
|
||||
rpc: Discord.Client;
|
||||
/**
|
||||
* Connection ready?
|
||||
*/
|
||||
ready: Boolean;
|
||||
}
|
||||
33
@types/PreMiD/PresenceData.d.ts
vendored
@@ -1,33 +0,0 @@
|
||||
import * as Discord from "discord-rpc";
|
||||
|
||||
export default interface PresenceData {
|
||||
/**
|
||||
* Client ID of presence
|
||||
*/
|
||||
clientId: string;
|
||||
/**
|
||||
* Tray title to be shown in Mac OS tray
|
||||
*/
|
||||
trayTitle: string;
|
||||
/**
|
||||
* service name of presence
|
||||
* @deprecated
|
||||
*/
|
||||
service: string;
|
||||
/**
|
||||
* Determines if the service is currently playing something back or not, if false it will automatically hide after 1 minute
|
||||
*/
|
||||
playback: boolean;
|
||||
/**
|
||||
* Discord Presence which gets sent directly to Discord app
|
||||
*/
|
||||
presenceData: Discord.Presence;
|
||||
/**
|
||||
* Determines if the service should be hidden (clearActivity)
|
||||
*/
|
||||
hidden: boolean;
|
||||
/**
|
||||
* Determines if the service is mediaKey able / uses them
|
||||
*/
|
||||
mediaKeys: boolean;
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
* @Timeraa
|
||||
* @Timeraa
|
||||
@@ -2,65 +2,38 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at contact@premid.app or by contacting a staff member on our [Discord server](https://discord.premid.app). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@premid.app or by contacting a staff member on our [Discord server](https://discord.premid.app). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
36
Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
||||
FROM gplane/pnpm:node20-alpine AS base
|
||||
RUN corepack enable
|
||||
ARG SERVICE
|
||||
|
||||
FROM base AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY . /app
|
||||
|
||||
RUN pnpm i --frozen-lockfile
|
||||
|
||||
RUN if [ "$SERVICE" != "website" ]; then pnpm run -r codegen; fi
|
||||
RUN if [ "$SERVICE" != "website" ]; then pnpm run build; fi
|
||||
RUN if [ "$SERVICE" == "website" ]; then pnpm --filter @premid/website run build; fi
|
||||
RUN if [ "$SERVICE" != "website" ]; then pnpm --filter @premid/${SERVICE} deploy --prod /prod/${SERVICE}; fi
|
||||
|
||||
FROM node:20-alpine AS prod
|
||||
ARG SERVICE
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /prod/${SERVICE} ./
|
||||
ENV PORT=80
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["npm", "start"]
|
||||
|
||||
FROM node:20-alpine AS website
|
||||
WORKDIR /app
|
||||
ENV PORT=80
|
||||
|
||||
COPY --from=build /app/apps/website/.output /app
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["node", "server/index.mjs"]
|
||||
77
README.md
@@ -1,46 +1,63 @@
|
||||
<div align="center">
|
||||
<img width="1280" height="800" alt="Chrome Global Screenshots" src="https://github.com/user-attachments/assets/1ea21f91-7499-43de-8b9a-3344d1c0fe48" />
|
||||
|
||||
<img src=".github/Logo.png" width="150px" draggable="false"><br>
|
||||
# <img src="https://cdn.rcd.gg/PreMiD.png" height="40px" /> PreMiD
|
||||
|
||||
# PreMiD
|
||||
[](https://crowdin.com/project/premid)
|
||||
|
||||
## Your Rich Presence for web services!
|
||||
PreMiD is a simple, configurable utility that lets you show what you're doing on the web in your Discord profile. Whether you're watching videos, listening to music, browsing your favorite sites, or playing browser games, PreMiD helps you share your online activities with your friends through Discord's Rich Presence feature.
|
||||
|
||||

|
||||

|
||||

|
||||
[](https://chrome.google.com/webstore/detail/premid/agjnjboanicjcpenljmaaigopkgdnihi)
|
||||

|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FPreMiD%2FPreMiD?ref=badge_shield)
|
||||
## Getting Started
|
||||
|
||||
<img src=".github/example.png" draggable="false"><br>
|
||||
**Looking to use PreMiD?** Head over to our [official website](https://premid.app) to add the browser extension!
|
||||
|
||||
# About
|
||||
**Want to create your own Activity?** All of our community-created activities are open source and available at [github.com/PreMiD/Activities](https://github.com/PreMiD/Activities). We'd love to see what you create!
|
||||
|
||||
**PreMiD** is a simple, configurable utility that allows you to show what you're doing on the web in your Discord **now playing status**. It supports many different websites, and will support multiple users watching the same content simultaneously in an upcoming update.
|
||||
## Features
|
||||
|
||||
# Features
|
||||
- 🎵 Show what you're listening to on YouTube and more (Spotify has native Discord support)
|
||||
- 📺 Display what you're watching on Netflix, Disney+, Twitch, and hundreds of other sites
|
||||
- 🎮 Share your browser game activity with friends
|
||||
- ✨ Fully customizable with thousands of user-created Activities
|
||||
- 🌍 Available in multiple languages thanks to our amazing community translators
|
||||
|
||||
· Displays your current web service in Discord as your status.<br>
|
||||
· Grants full control over Presences.<br>
|
||||
· Supports over 1,000 web services, still rising!<br>
|
||||
· _Watch parties and more are coming soon!_
|
||||
## Community
|
||||
|
||||
# Installation/Troubleshooting
|
||||
PreMiD is built by the community, for the community. Join us and help make PreMiD even better!
|
||||
|
||||
### Installation instructions, Troubleshooting guides etc. can be located at our [**docs**](https://docs.premid.app).
|
||||
- **Activities Repository**: [github.com/PreMiD/Activities](https://github.com/PreMiD/Activities)
|
||||
- **Documentation**: [docs.premid.app](https://docs.premid.app)
|
||||
- **Discord Server**: [discord.premid.app](https://discord.premid.app)
|
||||
- **Feedback & Bug Reports**: [feedback.premid.app](https://feedback.premid.app)
|
||||
|
||||
# Support us
|
||||
## Contributing
|
||||
|
||||
<div>
|
||||
<a target="_blank" href="https://www.patreon.com/bePatron?u=4610890" data-patreon-widget-type="become-patron-button" title="Support me on Patreon!">
|
||||
<img height="75px" draggable="false" src=".github/Patreon.png">
|
||||
</a>
|
||||
<a target="_blank" href="https://discord.premid.app/" title="Join our Discord!">
|
||||
<img src="https://discordapp.com/api/guilds/493130730549805057/widget.png?style=banner2" height="76px" draggable="false" alt="Join our Discord!">
|
||||
</a>
|
||||
</div>
|
||||
We love community contributions! While **PreMiD's Activities are fully open source** (the code that makes websites show up on your profile), the PreMiD extension is not currently open source. This decision allows our small team to move fast and iterate quickly to deliver the best experience possible.
|
||||
|
||||
You can contribute by:
|
||||
|
||||
- Creating new Activities at [github.com/PreMiD/Activities](https://github.com/PreMiD/Activities)
|
||||
- Helping translate PreMiD on [Crowdin](https://crowdin.com/project/premid)
|
||||
- Reporting bugs and suggesting features at [feedback.premid.app](https://feedback.premid.app)
|
||||
- Supporting the project and spreading the word!
|
||||
|
||||
### Release
|
||||
|
||||
To release a new version of a package, run the following command:
|
||||
|
||||
```bash
|
||||
cd apps/<app>
|
||||
pnpm bumpp -y -t <app>-v
|
||||
```
|
||||
|
||||
Replace `<app>` with the name of the package you want to release. For example, to release a new version of the `schema-server` package, you would run:
|
||||
|
||||
```bash
|
||||
cd apps/schema-server
|
||||
pnpm bumpp -y -t schema-server-v
|
||||
```
|
||||
|
||||
This will use bumpp to bump the version of the package in the `package.json` file, create a tag for the new version, and push the changes to the remote repository.
|
||||
|
||||
## License
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FPreMiD%2FPreMiD?ref=badge_large)
|
||||
This project is licensed under the [MPL-2.0 License](LICENSE).
|
||||
|
||||
3
apps/pd/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# @premid/pd
|
||||
|
||||
A simple url shortener service to shorten urls longer than 256 characters.
|
||||
12
apps/pd/environment.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
NODE_ENV?: "development" | "production" | "test";
|
||||
REDIS_URL?: string;
|
||||
MAX_FILE_SIZE?: string;
|
||||
PORT?: string;
|
||||
HOST?: string;
|
||||
RATELIMIT_MAX?: string;
|
||||
RATELIMIT_WINDOW?: string;
|
||||
BASE_URL?: string;
|
||||
}
|
||||
}
|
||||
BIN
apps/pd/fixtures/1x1.png
Normal file
|
After Width: | Height: | Size: 95 B |
BIN
apps/pd/fixtures/test.mp4
Normal file
34
apps/pd/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@premid/pd",
|
||||
"type": "module",
|
||||
"version": "1.2.4",
|
||||
"private": true,
|
||||
"description": "A small service to shorten image urls to get around Discord's 256 character limit",
|
||||
"license": "MPL-2.0",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "node --enable-source-maps .",
|
||||
"dev": "node --watch --enable-source-maps ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^9.0.1",
|
||||
"@fastify/multipart": "^8.1.0",
|
||||
"@fastify/rate-limit": "^9.1.0",
|
||||
"@keyv/redis": "^2.8.4",
|
||||
"fastify": "^4.26.0",
|
||||
"file-type": "^19.0.0",
|
||||
"got": "^14.2.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"ipaddr.js": "^2.1.0",
|
||||
"keyv": "^4.5.4",
|
||||
"mime-types": "^2.1.35",
|
||||
"nanoid": "^5.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
}
|
||||
9
apps/pd/src/functions/createKeyv.test.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import createKeyv from "./createKeyv.js";
|
||||
|
||||
it("should return keyv instance", () => {
|
||||
const keyv = createKeyv();
|
||||
|
||||
expect(keyv).toStrictEqual(expect.any(Object));
|
||||
});
|
||||
28
apps/pd/src/functions/createKeyv.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import process from "node:process";
|
||||
import KeyvRedis from "@keyv/redis";
|
||||
import Keyv from "keyv";
|
||||
|
||||
import redis from "../redis.js";
|
||||
|
||||
export default function createKeyv() {
|
||||
let options: Keyv.Options<string> | undefined;
|
||||
|
||||
/* c8 ignore next 8 */
|
||||
if (process.env.REDIS_SENTINELS) {
|
||||
options = {
|
||||
namespace: "pd",
|
||||
store: new KeyvRedis(redis),
|
||||
};
|
||||
}
|
||||
|
||||
const keyv = new Keyv<string>(
|
||||
options,
|
||||
);
|
||||
|
||||
/* c8 ignore next 3 */
|
||||
keyv.on("error", (error) => {
|
||||
console.error("Keyv connection error:", error);
|
||||
});
|
||||
|
||||
return keyv;
|
||||
}
|
||||
30
apps/pd/src/functions/createRedis.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/* eslint-disable no-console */
|
||||
import { hostname } from "node:os";
|
||||
|
||||
import process from "node:process";
|
||||
import { Redis } from "ioredis";
|
||||
|
||||
/* c8 ignore start */
|
||||
export default function createRedis(): Redis {
|
||||
const redis = new Redis({
|
||||
connectionName: `pd-${hostname()}-${process.pid.toString()}`,
|
||||
lazyConnect: true,
|
||||
name: "mymaster",
|
||||
sentinels: process.env.REDIS_SENTINELS?.split(",").map(s => ({
|
||||
host: s,
|
||||
port: 26_379,
|
||||
})),
|
||||
});
|
||||
|
||||
/* c8 ignore next 3 */
|
||||
redis.on("error", (error) => {
|
||||
console.error("Redis error", error);
|
||||
});
|
||||
|
||||
/* c8 ignore next 3 */
|
||||
redis.on("connect", () => {
|
||||
console.log("Redis connected");
|
||||
});
|
||||
|
||||
return redis;
|
||||
}
|
||||
10
apps/pd/src/functions/createServer.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createServer } from "../functions/createServer.js";
|
||||
|
||||
describe("createServer", () => {
|
||||
it("should return a fastify instance", async () => {
|
||||
const server = await createServer();
|
||||
expect(server).toBeDefined();
|
||||
});
|
||||
});
|
||||
62
apps/pd/src/functions/createServer.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import process from "node:process";
|
||||
import cors from "@fastify/cors";
|
||||
import fastifyMultipart from "@fastify/multipart";
|
||||
import ratelimit from "@fastify/rate-limit";
|
||||
import fastify from "fastify";
|
||||
import type { Redis } from "ioredis";
|
||||
|
||||
import createFromBase64 from "../routes/createFromBase64.js";
|
||||
import createFromImage from "../routes/createFromImage.js";
|
||||
import createShortenedLink from "../routes/createShortenedLink.js";
|
||||
import getFullLink from "../routes/getFullLink.js";
|
||||
|
||||
export async function createServer(redis?: Redis) {
|
||||
const server = fastify({
|
||||
trustProxy: true,
|
||||
});
|
||||
|
||||
await server.register(cors, {
|
||||
methods: ["GET"],
|
||||
origin: "*",
|
||||
});
|
||||
|
||||
await server.register(ratelimit, {
|
||||
max: Number.parseInt(process.env.RATELIMIT_MAX ?? "25"),
|
||||
nameSpace: "pd-ratelimit-",
|
||||
redis,
|
||||
timeWindow: process.env.RATELIMIT_WINDOW ?? "1 minute",
|
||||
});
|
||||
|
||||
await server.register(fastifyMultipart, {
|
||||
limits: {
|
||||
fileSize: Number.parseInt(process.env.MAX_FILE_SIZE ?? (5 * 1024 * 1024).toString()),
|
||||
files: 1,
|
||||
},
|
||||
});
|
||||
|
||||
server.post("/create/image", createFromImage);
|
||||
server.post("/create/base64", createFromBase64);
|
||||
server.get("/create/*", createShortenedLink);
|
||||
|
||||
server.get(
|
||||
"/*",
|
||||
{
|
||||
config: {
|
||||
rateLimit: false,
|
||||
},
|
||||
},
|
||||
getFullLink,
|
||||
);
|
||||
|
||||
server.get(
|
||||
"/health",
|
||||
{
|
||||
config: {
|
||||
rateLimit: false,
|
||||
},
|
||||
},
|
||||
(_, reply) => reply.status(204).send(),
|
||||
);
|
||||
|
||||
return server;
|
||||
}
|
||||
30
apps/pd/src/functions/getGoogleAddresses.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import got from "got";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe.concurrent("getGoogleAddresses", () => {
|
||||
it("should return an array of CIDR objects", async () => {
|
||||
const { default: getGoogleAddresses } = await import("./getGoogleAddresses.js");
|
||||
|
||||
vi.spyOn(got, "get").mockResolvedValue({
|
||||
body: JSON.stringify({
|
||||
prefixes: [
|
||||
{ ipv4Prefix: "0.0.0.0" },
|
||||
{ ipv6Prefix: "0000:0000:0000::/16" },
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await getGoogleAddresses();
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"ipv4Prefix": "0.0.0.0",
|
||||
},
|
||||
{
|
||||
"ipv6Prefix": "0000:0000:0000::/16",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
24
apps/pd/src/functions/getGoogleAddresses.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import got from "got";
|
||||
|
||||
import type { CIDR } from "./isInCidRange.js";
|
||||
|
||||
export default async function getGoogleAddresses(): Promise<CIDR> {
|
||||
const { body } = await got.get("https://www.gstatic.com/ipranges/cloud.json");
|
||||
const result = JSON.parse(body) as GoogleResult;
|
||||
return result.prefixes.map(({ ipv4Prefix, ipv6Prefix }) => {
|
||||
return ipv6Prefix ? { ipv6Prefix } : { ipv4Prefix };
|
||||
});
|
||||
}
|
||||
|
||||
interface GoogleResult {
|
||||
syncToken: string;
|
||||
creationTime: string;
|
||||
prefixes: GoogleIP[];
|
||||
}
|
||||
|
||||
interface GoogleIP {
|
||||
ipv6Prefix: string;
|
||||
ipv4Prefix: string;
|
||||
service: string;
|
||||
scope: string;
|
||||
}
|
||||
31
apps/pd/src/functions/isInCidRange.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import isInCIDRRange from "./isInCidRange.js";
|
||||
|
||||
it("isInCIDRRange - IPv4 - in range", () => {
|
||||
const CIDRs = [{ ipv4Prefix: "192.0.2.0/24" }];
|
||||
const ip = "192.0.2.123";
|
||||
const result = isInCIDRRange(CIDRs, ip);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("isInCIDRRange - IPv4 - not in range", () => {
|
||||
const CIDRs = [{ ipv4Prefix: "192.0.2.0/24" }];
|
||||
const ip = "192.0.3.123";
|
||||
const result = isInCIDRRange(CIDRs, ip);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("isInCIDRRange - IPv6 - in range", () => {
|
||||
const CIDRs = [{ ipv6Prefix: "2001:db8::/32" }];
|
||||
const ip = "2001:db8::1234";
|
||||
const result = isInCIDRRange(CIDRs, ip);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it("isInCIDRRange - IPv6 - not in range", () => {
|
||||
const CIDRs = [{ ipv6Prefix: "2001:db8::/32" }];
|
||||
const ip = "2001:db9::1234";
|
||||
const result = isInCIDRRange(CIDRs, ip);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
29
apps/pd/src/functions/isInCidRange.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import ipaddr from "ipaddr.js";
|
||||
|
||||
export default function isInCIDRRange(CIDRs: CIDR, ip: string) {
|
||||
const parsed = ipaddr.parse(ip);
|
||||
|
||||
for (const CIDR of CIDRs.filter((c) => {
|
||||
if (parsed.kind() === "ipv4" && "ipv4Prefix" in c)
|
||||
return true;
|
||||
else if (parsed.kind() === "ipv6" && "ipv6Prefix" in c)
|
||||
return true;
|
||||
else return false;
|
||||
})) {
|
||||
const check = parsed.match(ipaddr.parseCIDR("ipv4Prefix" in CIDR ? CIDR.ipv4Prefix : CIDR.ipv6Prefix));
|
||||
|
||||
if (check)
|
||||
return check;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export type CIDR = (
|
||||
| {
|
||||
ipv4Prefix: string;
|
||||
}
|
||||
| {
|
||||
ipv6Prefix: string;
|
||||
}
|
||||
)[];
|
||||
3
apps/pd/src/googleCIDRs.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import getGoogleAddresses from "./functions/getGoogleAddresses.js";
|
||||
|
||||
export default await getGoogleAddresses();
|
||||
14
apps/pd/src/index.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createServer } from "./functions/createServer.js";
|
||||
|
||||
it("/health", async () => {
|
||||
const server = await createServer();
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: "/health",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(204);
|
||||
expect(result.body).toBe("");
|
||||
});
|
||||
20
apps/pd/src/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/* eslint-disable no-console */
|
||||
/* c8 ignore start */
|
||||
import process from "node:process";
|
||||
import { createServer } from "./functions/createServer.js";
|
||||
import redis from "./redis.js";
|
||||
|
||||
if (!process.env.REDIS_SENTINELS)
|
||||
console.log("WARNING: No REDIS_SENTINELS environment variable set");
|
||||
if (process.env.NODE_ENV === "production" && !process.env.BASE_URL)
|
||||
throw new Error("BASE_URL environment variable is required in production");
|
||||
|
||||
export const server = await createServer(redis);
|
||||
|
||||
const url = await server.listen({
|
||||
host: process.env.HOST ?? "0.0.0.0",
|
||||
port: Number.parseInt(process.env.PORT ?? "80"),
|
||||
});
|
||||
|
||||
console.log(`Server listening at ${url}`);
|
||||
// TODO Make proper error codes & json responses
|
||||
5
apps/pd/src/keyv.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import createKeyv from "./functions/createKeyv.js";
|
||||
|
||||
export const keyv = createKeyv();
|
||||
|
||||
export const ttl = 30 * 60 * 1000;
|
||||
3
apps/pd/src/redis.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createRedis from "./functions/createRedis.js";
|
||||
|
||||
export default createRedis();
|
||||
94
apps/pd/src/routes/createFromBase64.test.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { describe, it } from "vitest";
|
||||
|
||||
import { createServer } from "../functions/createServer.js";
|
||||
|
||||
describe.concurrent("createFromBase64", async () => {
|
||||
const server = await createServer();
|
||||
|
||||
it("should return a 400 when the body is not present", async ({ expect }) => {
|
||||
const result = await server.inject({
|
||||
method: "POST",
|
||||
url: "/create/base64",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(400);
|
||||
expect(result.body).toMatchInlineSnapshot("\"Invalid body\"");
|
||||
});
|
||||
|
||||
it("should return a 400 when the body is not a string", async ({ expect }) => {
|
||||
const result = await server.inject({
|
||||
method: "POST",
|
||||
payload: new Blob([]),
|
||||
url: "/create/base64",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(400);
|
||||
expect(result.body).toMatchInlineSnapshot("\"Invalid body\"");
|
||||
});
|
||||
|
||||
it("should return a 400 when the body is not a valid base64 string", async ({ expect }) => {
|
||||
const result = await server.inject({
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
method: "POST",
|
||||
payload: "data:image/png;base64t",
|
||||
url: "/create/base64",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(400);
|
||||
expect(result.body).toMatchInlineSnapshot("\"Invalid base64 string\"");
|
||||
});
|
||||
|
||||
it("should return a 400 when the base64 string is not a valid image", async ({ expect }) => {
|
||||
const result = await server.inject({
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
method: "POST",
|
||||
payload: "data:image/sv;base64,a",
|
||||
url: "/create/base64",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(400);
|
||||
expect(result.body).toMatchInlineSnapshot("\"Invalid base64 string\"");
|
||||
|
||||
const result2 = await server.inject({
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
method: "POST",
|
||||
payload: "data:image/svg+xml;base64,s",
|
||||
url: "/create/base64",
|
||||
});
|
||||
|
||||
expect(result2.statusCode).toBe(400);
|
||||
expect(result2.body).toMatchInlineSnapshot("\"Supported types: png, jpeg, jpg, gif, webp\"");
|
||||
});
|
||||
|
||||
it("should return a 200 when the base64 string is valid", async ({ expect }) => {
|
||||
const result = await server.inject({
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
method: "POST",
|
||||
payload: "data:image/png;base64,s",
|
||||
url: "/create/base64",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
|
||||
const result2 = await server.inject({
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
method: "POST",
|
||||
payload: "data:image/png;base64,s",
|
||||
url: "/create/base64",
|
||||
});
|
||||
|
||||
expect(result2.statusCode).toBe(200);
|
||||
expect(result2.body).toStrictEqual(expect.any(String));
|
||||
});
|
||||
});
|
||||
47
apps/pd/src/routes/createFromBase64.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import crypto from "node:crypto";
|
||||
|
||||
import process from "node:process";
|
||||
import mime from "mime-types";
|
||||
import { nanoid } from "nanoid";
|
||||
import type { RouteHandlerMethod } from "fastify";
|
||||
|
||||
import { keyv, ttl } from "../keyv.js";
|
||||
|
||||
const handler: RouteHandlerMethod = async (request, reply) => {
|
||||
const { body } = request;
|
||||
|
||||
if (!body)
|
||||
return reply.status(400).send("Invalid body");
|
||||
|
||||
if (typeof body !== "string")
|
||||
return reply.status(400).send("Invalid body");
|
||||
|
||||
const matches = body.match(/^data:(.+);base64,(.+)/);
|
||||
|
||||
if (!matches || matches.length === 0)
|
||||
return reply.status(400).send("Invalid base64 string");
|
||||
|
||||
const type = mime.extension(matches.at(1)!);
|
||||
|
||||
if (!type)
|
||||
return reply.status(400).send("Invalid base64 string");
|
||||
|
||||
if (!["png", "jpeg", "jpg", "gif", "webp"].includes(type))
|
||||
return reply.status(400).send("Supported types: png, jpeg, jpg, gif, webp");
|
||||
|
||||
const hash = crypto.createHash("sha256").update(body).digest("hex");
|
||||
const existingUrl = await keyv.get(hash);
|
||||
|
||||
if (existingUrl) {
|
||||
return reply.send(process.env.BASE_URL! + existingUrl);
|
||||
}
|
||||
|
||||
const uniqueId = `${nanoid(10)}.${type}`;
|
||||
|
||||
await keyv.set(hash, uniqueId, ttl);
|
||||
await keyv.set(uniqueId, body, ttl);
|
||||
|
||||
return reply.send(process.env.BASE_URL! + uniqueId);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
106
apps/pd/src/routes/createFromImage.test.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Buffer } from "node:buffer";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
import type { RequestOptions } from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import { afterAll, beforeAll, describe, it } from "vitest";
|
||||
|
||||
import { createServer } from "../functions/createServer.js";
|
||||
|
||||
describe.concurrent("createFromImage", async () => {
|
||||
const server = await createServer();
|
||||
const form = new FormData();
|
||||
const defaultRequestOptions: RequestOptions = {
|
||||
hostname: "localhost",
|
||||
method: "POST",
|
||||
path: "/create/image",
|
||||
protocol: "http:",
|
||||
};
|
||||
|
||||
let url: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
url = await server.listen();
|
||||
defaultRequestOptions.port = (server.server.address() as AddressInfo).port;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
void server.close();
|
||||
});
|
||||
|
||||
it("should return a 400 when request is not multipart", async ({ expect }) => {
|
||||
const result = await fetch(`${url}/create/image`, {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(400);
|
||||
expect(await result.text()).toMatchInlineSnapshot("\"Request is not multipart\"");
|
||||
});
|
||||
|
||||
it("should return a 400 status code when no file is provided", async ({ expect }) => {
|
||||
const result = await fetch(`${url}/create/image`, {
|
||||
body: form,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(400);
|
||||
expect(await result.text()).toMatchInlineSnapshot("\"Invalid file\"");
|
||||
});
|
||||
it("should return a 400 status code when the file is invalid", async ({ expect }) => {
|
||||
form.set("file", Buffer.alloc(1024 * 1024 * 2));
|
||||
|
||||
const result = await fetch(`${url}/create/image`, {
|
||||
body: form,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(400);
|
||||
expect(await result.text()).toMatchInlineSnapshot("\"Invalid file\"");
|
||||
|
||||
form.set("file", new Blob([new Uint8Array(1024 * 1024 * 2)]));
|
||||
|
||||
const result2 = await fetch(`${url}/create/image`, {
|
||||
body: form,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
expect(result2.status).toBe(400);
|
||||
expect(await result2.text()).toMatchInlineSnapshot("\"Invalid file\"");
|
||||
});
|
||||
|
||||
it("should return a 400 status code when the file is not an image", async ({ expect }) => {
|
||||
form.set("file", new Blob([await readFile(new URL("../../fixtures/test.mp4", import.meta.url))]));
|
||||
|
||||
const result = await fetch(`${url}/create/image`, {
|
||||
body: form,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(400);
|
||||
expect(await result.text()).toMatchInlineSnapshot("\"Only png, jpeg, jpg, gif and webp are supported\"");
|
||||
});
|
||||
|
||||
it("should return a 200 status code when the file is valid", async ({ expect }) => {
|
||||
form.set("file", new Blob([await readFile(new URL("../../fixtures/1x1.png", import.meta.url))]));
|
||||
|
||||
const result = await fetch(`${url}/create/image`, {
|
||||
body: form,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(200);
|
||||
expect(await result.text()).toStrictEqual(expect.any(String));
|
||||
});
|
||||
|
||||
it("should return a 200 status code when the file is valid and the same file is uploaded again", async ({ expect }) => {
|
||||
form.set("file", new Blob([await readFile(new URL("../../fixtures/1x1.png", import.meta.url))]));
|
||||
|
||||
const result = await fetch(`${url}/create/image`, {
|
||||
body: form,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
expect(result.status).toBe(200);
|
||||
expect(await result.text()).toStrictEqual(expect.any(String));
|
||||
});
|
||||
});
|
||||
53
apps/pd/src/routes/createFromImage.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import crypto from "node:crypto";
|
||||
|
||||
import process from "node:process";
|
||||
import { fileTypeFromBuffer } from "file-type";
|
||||
import { nanoid } from "nanoid";
|
||||
import type { RouteHandlerMethod } from "fastify";
|
||||
|
||||
import { keyv, ttl } from "../keyv.js";
|
||||
|
||||
const handler: RouteHandlerMethod = async (request, reply) => {
|
||||
if (!request.isMultipart())
|
||||
return reply.status(400).send("Request is not multipart");
|
||||
|
||||
const file = await request.file();
|
||||
|
||||
if (!file)
|
||||
return reply.status(400).send("Invalid file");
|
||||
|
||||
const type = await fileTypeFromBuffer(await file.toBuffer());
|
||||
|
||||
if (!type)
|
||||
return reply.status(400).send("Invalid file");
|
||||
|
||||
if (![
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
].includes(type.mime)) {
|
||||
return reply.status(400).send("Only png, jpeg, jpg, gif and webp are supported");
|
||||
}
|
||||
|
||||
const buffer = await file.toBuffer();
|
||||
const body = `data:${type.mime};base64,${buffer.toString("base64")}`;
|
||||
const hash = crypto.createHash("sha256").update(body).digest("hex");
|
||||
const existingUrl = await keyv.get(hash);
|
||||
|
||||
if (existingUrl) {
|
||||
return reply.send(process.env.BASE_URL! + existingUrl);
|
||||
}
|
||||
|
||||
const uniqueId = `${nanoid(10)}.${type.ext}`;
|
||||
|
||||
await Promise.all([
|
||||
keyv.set(hash, uniqueId, ttl),
|
||||
keyv.set(uniqueId, body, ttl),
|
||||
]);
|
||||
|
||||
return reply.send(process.env.BASE_URL! + uniqueId);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
152
apps/pd/src/routes/createShortenedLink.test.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createServer } from "../functions/createServer.js";
|
||||
|
||||
describe.concurrent("/create", async () => {
|
||||
const server = await createServer();
|
||||
|
||||
it("should return a 400 status code when no URL is provided", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: "/create/",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(400);
|
||||
expect(result.body).toMatchInlineSnapshot("\"Invalid URL\"");
|
||||
});
|
||||
|
||||
it("should return a 400 status code when the URL is too short", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: "/create/https://www.google.com",
|
||||
});
|
||||
expect(result.statusCode).toBe(400);
|
||||
expect(result.body).toMatchInlineSnapshot("\"URL is too short\"");
|
||||
});
|
||||
|
||||
it("should return a 400 status code when the URL is invalid", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/file://www.googl${"e".repeat(256)}`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(400);
|
||||
expect(result.body).toMatchInlineSnapshot("\"Invalid URL\"");
|
||||
});
|
||||
|
||||
it("should return a 200 status code when the URL is valid", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.googl${"e".repeat(256)}.com`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
});
|
||||
|
||||
it("should return a 200 status code when the URL is valid and already exists", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.googl${"d".repeat(256)}.com`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
const { body } = result;
|
||||
const result2 = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.googl${"d".repeat(256)}.com`,
|
||||
});
|
||||
|
||||
expect(result2.statusCode).toBe(200);
|
||||
expect(result2.body).toStrictEqual(body);
|
||||
});
|
||||
|
||||
it("should preserve file extension when URL has a valid image extension", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.exampl${"e".repeat(256)}.com/image.png`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
expect(result.body).toMatch(/\.png$/);
|
||||
});
|
||||
|
||||
it("should preserve file extension when URL has .jpg extension", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.exampl${"e".repeat(256)}.com/photo.jpg`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
expect(result.body).toMatch(/\.jpg$/);
|
||||
});
|
||||
|
||||
it("should preserve file extension when URL has .webp extension", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.exampl${"e".repeat(256)}.com/image.webp`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
expect(result.body).toMatch(/\.webp$/);
|
||||
});
|
||||
|
||||
it("should preserve file extension when URL has .png with query parameters", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.exampl${"e".repeat(256)}.com/image.png?ref=example`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
expect(result.body).toMatch(/\.png$/);
|
||||
});
|
||||
|
||||
it("should preserve file extension when URL has .gif with complex query parameters", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.exampl${"e".repeat(256)}.com/animated.gif?size=large&quality=high`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
expect(result.body).toMatch(/\.gif$/);
|
||||
});
|
||||
|
||||
it("should not preserve file extension when URL has invalid extension", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.exampl${"e".repeat(256)}.com/document.pdf`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
expect(result.body).not.toMatch(/\.pdf$/);
|
||||
});
|
||||
|
||||
it("should work normally when URL has no file extension", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.exampl${"e".repeat(256)}.com/page`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
expect(result.body).not.toMatch(/\.\w+$/);
|
||||
});
|
||||
|
||||
it("should handle case-insensitive extensions", async () => {
|
||||
const result = await server.inject({
|
||||
method: "GET",
|
||||
url: `/create/https://www.exampl${"e".repeat(256)}.com/image.PNG`,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.body).toStrictEqual(expect.any(String));
|
||||
expect(result.body).toMatch(/\.png$/); //* Should be lowercase in result
|
||||
});
|
||||
});
|
||||
47
apps/pd/src/routes/createShortenedLink.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import crypto from "node:crypto";
|
||||
|
||||
import process from "node:process";
|
||||
import { nanoid } from "nanoid";
|
||||
import type { RouteHandlerMethod } from "fastify";
|
||||
|
||||
import { keyv, ttl } from "../keyv.js";
|
||||
|
||||
const handler: RouteHandlerMethod = async (request, reply) => {
|
||||
const url = request.url.replace("/create/", "").trim();
|
||||
|
||||
if (url.length === 0)
|
||||
return reply.status(400).send("Invalid URL");
|
||||
|
||||
if (url.length < 256)
|
||||
return reply.status(400).send("URL is too short");
|
||||
|
||||
const urlObject = new URL(url);
|
||||
if (!["http:", "https:"].includes(urlObject.protocol))
|
||||
return reply.status(400).send("Invalid URL");
|
||||
|
||||
//* Extract file extension from URL pathname
|
||||
const pathname = urlObject.pathname;
|
||||
const extensionMatch = pathname.match(/\.([^./]+)$/);
|
||||
const extension = extensionMatch?.[1]?.toLowerCase() ?? null;
|
||||
|
||||
//* Check if extension is in allowed list
|
||||
const allowedExtensions = ["png", "jpeg", "jpg", "gif", "webp"];
|
||||
const hasValidExtension = extension && allowedExtensions.includes(extension);
|
||||
|
||||
const hash = crypto.createHash("sha256").update(url).digest("hex");
|
||||
const existingShortenedUrl = await keyv.get(hash);
|
||||
|
||||
if (existingShortenedUrl) {
|
||||
await Promise.all([keyv.set(hash, existingShortenedUrl, ttl), keyv.set(existingShortenedUrl, url, ttl)]);
|
||||
return reply.send(process.env.BASE_URL! + existingShortenedUrl);
|
||||
}
|
||||
|
||||
//* Create unique ID with extension if valid, otherwise without extension
|
||||
const uniqueId = hasValidExtension ? `${nanoid(10)}.${extension}` : nanoid(10);
|
||||
|
||||
await Promise.all([keyv.set(hash, uniqueId, ttl), keyv.set(uniqueId, url, ttl)]);
|
||||
|
||||
return reply.send(process.env.BASE_URL! + uniqueId);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
128
apps/pd/src/routes/getFullLink.test.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Buffer } from "node:buffer";
|
||||
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createServer } from "../functions/createServer.js";
|
||||
import * as isInCIDRRange from "../functions/isInCidRange.js";
|
||||
|
||||
describe("getFullLink", async () => {
|
||||
const server = await createServer();
|
||||
let url: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
url = await server.listen();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
it("should fail if not a Google Cloud IP", async () => {
|
||||
const result = await server.inject({
|
||||
headers: {
|
||||
"cf-connecting-ip": "",
|
||||
},
|
||||
url: "/1234567890",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
it("should fail if not a valid ID", async () => {
|
||||
vi.spyOn(isInCIDRRange, "default").mockReturnValueOnce(true);
|
||||
|
||||
const result = await server.inject({
|
||||
headers: {
|
||||
"cf-connecting-ip": "",
|
||||
},
|
||||
url: "/123",
|
||||
});
|
||||
|
||||
vi.spyOn(isInCIDRRange, "default").mockReturnValueOnce(true);
|
||||
const result2 = await server.inject({
|
||||
headers: {
|
||||
"cf-connecting-ip": "",
|
||||
},
|
||||
url: "/1234567890.",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(404);
|
||||
expect(result2.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it("should redirect to the correct URL", async () => {
|
||||
vi.spyOn(isInCIDRRange, "default").mockReturnValueOnce(true);
|
||||
|
||||
const { body } = await server.inject({
|
||||
url: `/create/https://${"a".repeat(256)}`,
|
||||
});
|
||||
|
||||
expect(body).toStrictEqual(expect.any(String));
|
||||
|
||||
const result = await server.inject({
|
||||
headers: {
|
||||
"cf-connecting-ip": "",
|
||||
},
|
||||
url: body,
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(302);
|
||||
expect(result.headers.location).toBe(`https://${"a".repeat(256)}`);
|
||||
});
|
||||
|
||||
it("should return the correct image", async () => {
|
||||
const imageBuffer = await readFile(new URL("../../fixtures/test.mp4", import.meta.url));
|
||||
const imageBase64 = `data:image/png;base64,${imageBuffer.toString("base64")}`;
|
||||
|
||||
const { body } = await server.inject({
|
||||
headers: {
|
||||
"Content-Type": "text/plain",
|
||||
},
|
||||
method: "POST",
|
||||
payload: imageBase64,
|
||||
url: "/create/base64",
|
||||
});
|
||||
|
||||
expect(body).toStrictEqual(expect.any(String));
|
||||
|
||||
vi.spyOn(isInCIDRRange, "default").mockReturnValueOnce(true);
|
||||
|
||||
const result = await fetch(`${url}${body}`, {
|
||||
headers: {
|
||||
"cf-connecting-ip": "",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.status).toBe(200);
|
||||
expect(result.headers.get("content-type")).toBe("image/png");
|
||||
expect(Buffer.from(await result.arrayBuffer())).toStrictEqual(imageBuffer);
|
||||
});
|
||||
|
||||
it("should fetch and return PNG image instead of redirecting for URLs with .png extension", async () => {
|
||||
const testUrl = `https://cdn.rcd.gg/PreMiD/resources/reading.png?v=${"a".repeat(250)}`;
|
||||
|
||||
const { body } = await server.inject({
|
||||
url: `/create/${testUrl}`,
|
||||
});
|
||||
|
||||
expect(body).toStrictEqual(expect.any(String));
|
||||
|
||||
vi.spyOn(isInCIDRRange, "default").mockReturnValueOnce(true);
|
||||
|
||||
const result = await fetch(`${url}${body}`, {
|
||||
headers: {
|
||||
"cf-connecting-ip": "",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.status).toBe(200);
|
||||
expect(result.headers.get("content-type")).toMatch(/^image\//);
|
||||
//* Should return image data, not redirect
|
||||
expect(result.headers.get("location")).toBeNull();
|
||||
|
||||
//* Verify we got actual image data
|
||||
const imageBuffer = await result.arrayBuffer();
|
||||
expect(imageBuffer.byteLength).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
93
apps/pd/src/routes/getFullLink.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Buffer } from "node:buffer";
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import type { RouteHandlerMethod } from "fastify";
|
||||
|
||||
import isInCIDRRange from "../functions/isInCidRange.js";
|
||||
import googleCIDRs from "../googleCIDRs.js";
|
||||
import { keyv, ttl } from "../keyv.js";
|
||||
|
||||
const handler: RouteHandlerMethod = async (request, reply) => {
|
||||
/* c8 ignore next 2 */
|
||||
const ip = request.headers["cf-connecting-ip"]?.toString() || request.socket.remoteAddress || request.ip;
|
||||
|
||||
if (
|
||||
!isInCIDRRange(
|
||||
googleCIDRs,
|
||||
ip,
|
||||
)
|
||||
) {
|
||||
return reply.status(401).send("Not a Google Cloud IP");
|
||||
}
|
||||
|
||||
const id = (request.params as { "*": string })["*"].trim();
|
||||
|
||||
if (id.split(".")[0]?.length !== 10)
|
||||
return reply.code(404).send("Invalid ID");
|
||||
|
||||
const url = await keyv.get(id);
|
||||
if (!url)
|
||||
return reply.code(404).send("Unknown ID");
|
||||
|
||||
const hash = crypto.createHash("sha256").update(url).digest("hex");
|
||||
|
||||
await Promise.all([keyv.set(hash, id, ttl), keyv.set(id, url, ttl)]);
|
||||
|
||||
//* If it is a base64 string, decode and return the image
|
||||
if (url.startsWith("data:image")) {
|
||||
const image = Buffer.from(
|
||||
url.replace(/^data:image\/\w+;base64,/, ""),
|
||||
"base64",
|
||||
);
|
||||
|
||||
const mime = url.split(";")[0]!.split(":")[1]!;
|
||||
|
||||
return reply.type(mime).send(image);
|
||||
}
|
||||
|
||||
//* Check if URL has a valid image extension
|
||||
const urlObject = new URL(url);
|
||||
const pathname = urlObject.pathname;
|
||||
const extensionMatch = pathname.match(/\.([^./]+)$/);
|
||||
const extension = extensionMatch?.[1]?.toLowerCase() ?? null;
|
||||
|
||||
const allowedExtensions = ["png", "jpeg", "jpg", "gif", "webp"];
|
||||
const hasValidImageExtension = extension && allowedExtensions.includes(extension);
|
||||
|
||||
//* If URL has valid image extension, fetch and return the image
|
||||
if (hasValidImageExtension) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"Accept": "image/*",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Cache-Control": "no-cache",
|
||||
"Pragma": "no-cache",
|
||||
"Referer": urlObject.origin, //* Set referer to the origin domain to bypass hotlink protection
|
||||
"Sec-Fetch-Dest": "image",
|
||||
"Sec-Fetch-Mode": "no-cors",
|
||||
"Sec-Fetch-Site": "cross-site",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return reply.code(404).send("Image not found");
|
||||
}
|
||||
|
||||
const contentType = response.headers.get("content-type") || `image/${extension}`;
|
||||
const imageBuffer = Buffer.from(await response.arrayBuffer());
|
||||
|
||||
return reply.type(contentType).send(imageBuffer);
|
||||
}
|
||||
catch {
|
||||
//* If fetch fails, fall back to redirect
|
||||
return reply.redirect(url);
|
||||
}
|
||||
}
|
||||
|
||||
//* For all other URLs, redirect to them
|
||||
return reply.redirect(url);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
8
apps/pd/tsconfig.app.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
}
|
||||
}
|
||||
8
apps/pd/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"compilerOptions": {
|
||||
"types": ["@types/node"],
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["environment.d.ts", "src"]
|
||||
}
|
||||
2
apps/schema-server/.eslintrc.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
rules:
|
||||
no-console: off
|
||||
3
apps/schema-server/Readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# @premid/schema-server
|
||||
|
||||
This is a simple schema server which serves JSON schemas for Presence Developers.
|
||||
7
apps/schema-server/environment.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
NODE_ENV?: "development" | "production" | "test";
|
||||
PORT?: string;
|
||||
HOST?: string;
|
||||
}
|
||||
}
|
||||
22
apps/schema-server/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@premid/schema-server",
|
||||
"type": "module",
|
||||
"version": "1.0.11",
|
||||
"private": true,
|
||||
"description": "A small service to serve the JSON schemas for PreMiD",
|
||||
"license": "MPL-2.0",
|
||||
"main": "dist/index.js",
|
||||
"files": [
|
||||
"dist",
|
||||
"schemas"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "node --enable-source-maps .",
|
||||
"dev": "node --watch --enable-source-maps ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/helmet": "^11.1.1",
|
||||
"fastify": "^4.26.0",
|
||||
"globby": "^14.0.1"
|
||||
}
|
||||
}
|
||||
241
apps/schema-server/schemas/metadata/1.0.json
Normal file
@@ -0,0 +1,241 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.0",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(_[A-Z]{2})?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(_[A-Z]{2})?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?:\\/\\/?(?:[a-z0-9-]+\\.)*[0-9a-z_-]+(?:\\.[a-z]+)+\\/.*$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?:\\/\\/?([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+\\/.*$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa[bs] fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
252
apps/schema-server/schemas/metadata/1.1.json
Normal file
@@ -0,0 +1,252 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.0",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(_[A-Z]{2})?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(_[A-Z]{2})?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?:\\/\\/?(?:[a-z0-9-]+\\.)*[0-9a-z_-]+(?:\\.[a-z]+)+\\/.*$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?:\\/\\/?([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+\\/.*$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa[bs] fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
277
apps/schema-server/schemas/metadata/1.10.json
Normal file
@@ -0,0 +1,277 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.10",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"matches": {
|
||||
"type": "array",
|
||||
"description": "A glob pattern required to match Google's match pattern (https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns). This is required for Presences.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A glob pattern.",
|
||||
"pattern": "^(https|http|[*])://.*/.*"
|
||||
}
|
||||
},
|
||||
|
||||
"iFrameMatches": {
|
||||
"type": "array",
|
||||
"description": "A glob pattern required to match Google's match pattern (https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns). This is required for Presences.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A glob pattern.",
|
||||
"pattern": "^(https|http|[*])://.*/.*"
|
||||
}
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
260
apps/schema-server/schemas/metadata/1.11.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.11",
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"en"
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"apiVersion": {
|
||||
"type": "integer",
|
||||
"description": "The Presence System version this Presence supports.",
|
||||
"minimum": 1,
|
||||
"maximum": 2
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
"tags": {
|
||||
"type": [
|
||||
"array"
|
||||
],
|
||||
"description": "The tags for the presence.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": [
|
||||
"anime",
|
||||
"games",
|
||||
"music",
|
||||
"socials",
|
||||
"videos",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
"value": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
"items": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"multiLanguage": {
|
||||
"type": [
|
||||
"string",
|
||||
"boolean",
|
||||
"array"
|
||||
],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"author",
|
||||
"service",
|
||||
"description",
|
||||
"url",
|
||||
"version",
|
||||
"apiVersion",
|
||||
"logo",
|
||||
"thumbnail",
|
||||
"color",
|
||||
"tags",
|
||||
"category"
|
||||
]
|
||||
}
|
||||
252
apps/schema-server/schemas/metadata/1.12.json
Normal file
@@ -0,0 +1,252 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.12",
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"en"
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"apiVersion": {
|
||||
"type": "integer",
|
||||
"description": "The Presence System version this Presence supports.",
|
||||
"minimum": 1,
|
||||
"maximum": 2
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
"tags": {
|
||||
"type": [
|
||||
"array"
|
||||
],
|
||||
"description": "The tags for the presence.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": [
|
||||
"anime",
|
||||
"games",
|
||||
"music",
|
||||
"socials",
|
||||
"videos",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
"value": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
"items": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"multiLanguage": {
|
||||
"type": "boolean",
|
||||
"description": "When true, strings from the `general.json` file are available for use, plus the <service>.json file. False is not allowed."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"author",
|
||||
"service",
|
||||
"description",
|
||||
"url",
|
||||
"version",
|
||||
"apiVersion",
|
||||
"logo",
|
||||
"thumbnail",
|
||||
"color",
|
||||
"tags",
|
||||
"category"
|
||||
]
|
||||
}
|
||||
248
apps/schema-server/schemas/metadata/1.13.json
Normal file
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.13",
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a activity.",
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this activity."
|
||||
},
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this activity.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this activity is for."
|
||||
},
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the activity in multiple languages.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(-regioncode).",
|
||||
"pattern": "^[a-z]{2,3}(?:-(?:[a-z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2,3}(?:-(?:[a-z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the activity in the key's language."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"en"
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the activity. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"apiVersion": {
|
||||
"type": "integer",
|
||||
"description": "The Activity System version this activity supports.",
|
||||
"minimum": 1,
|
||||
"maximum": 2
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this activity is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this activity is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this activity is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
"tags": {
|
||||
"type": [
|
||||
"array"
|
||||
],
|
||||
"description": "The tags for the activity.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the activity falls under.",
|
||||
"enum": [
|
||||
"anime",
|
||||
"games",
|
||||
"music",
|
||||
"socials",
|
||||
"videos",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity should run in IFrames."
|
||||
},
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the activity to inject into."
|
||||
},
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the activity to inject into."
|
||||
},
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the activity.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
"value": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
"items": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"multiLanguage": {
|
||||
"type": "boolean",
|
||||
"description": "When true, strings from the `general.json` file are available for use, plus the <service>.json file. False is not allowed."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"mobile": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity has support for mobile devices."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"author",
|
||||
"service",
|
||||
"description",
|
||||
"url",
|
||||
"version",
|
||||
"apiVersion",
|
||||
"logo",
|
||||
"thumbnail",
|
||||
"color",
|
||||
"tags",
|
||||
"category"
|
||||
]
|
||||
}
|
||||
252
apps/schema-server/schemas/metadata/1.14.json
Normal file
@@ -0,0 +1,252 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.14",
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a activity.",
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this activity."
|
||||
},
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this activity.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this activity is for."
|
||||
},
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the activity in multiple languages.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(-regioncode).",
|
||||
"pattern": "^[a-z]{2,3}(?:-(?:[a-z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2,3}(?:-(?:[a-z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the activity in the key's language."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"en"
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the activity. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"apiVersion": {
|
||||
"type": "integer",
|
||||
"description": "The Activity System version this activity supports.",
|
||||
"minimum": 1,
|
||||
"maximum": 2
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this activity is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this activity is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this activity is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
"tags": {
|
||||
"type": [
|
||||
"array"
|
||||
],
|
||||
"description": "The tags for the activity.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the activity falls under.",
|
||||
"enum": [
|
||||
"anime",
|
||||
"games",
|
||||
"music",
|
||||
"socials",
|
||||
"videos",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity should run in IFrames."
|
||||
},
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the activity to inject into."
|
||||
},
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the activity to inject into."
|
||||
},
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the activity.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "The description of the setting. Only applicable if `multiLanguage` is disabled."
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
"value": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
"items": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"multiLanguage": {
|
||||
"type": "boolean",
|
||||
"description": "When true, strings from the `general.json` file are available for use, plus the <service>.json file. False is not allowed."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"mobile": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity has support for mobile devices."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"author",
|
||||
"service",
|
||||
"description",
|
||||
"url",
|
||||
"version",
|
||||
"apiVersion",
|
||||
"logo",
|
||||
"thumbnail",
|
||||
"color",
|
||||
"tags",
|
||||
"category"
|
||||
]
|
||||
}
|
||||
256
apps/schema-server/schemas/metadata/1.15.json
Normal file
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.15",
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a activity.",
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this activity."
|
||||
},
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this activity.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this activity is for."
|
||||
},
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the activity in multiple languages.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(-regioncode).",
|
||||
"pattern": "^[a-z]{2,3}(?:-(?:[a-z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2,3}(?:-(?:[a-z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the activity in the key's language."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"en"
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the activity. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"apiVersion": {
|
||||
"type": "integer",
|
||||
"description": "The Activity System version this activity supports.",
|
||||
"minimum": 1,
|
||||
"maximum": 2
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this activity is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this activity is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this activity is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
"tags": {
|
||||
"type": [
|
||||
"array"
|
||||
],
|
||||
"description": "The tags for the activity.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the activity falls under.",
|
||||
"enum": [
|
||||
"anime",
|
||||
"games",
|
||||
"music",
|
||||
"socials",
|
||||
"videos",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity should run in IFrames."
|
||||
},
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the activity to inject into."
|
||||
},
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the activity to inject into."
|
||||
},
|
||||
"allowURLOverrides": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity should allow URL overrides."
|
||||
},
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the activity.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "The description of the setting. Only applicable if `multiLanguage` is disabled."
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
"value": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
"items": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"multiLanguage": {
|
||||
"type": "boolean",
|
||||
"description": "When true, strings from the `general.json` file are available for use, plus the <service>.json file. False is not allowed."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"mobile": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity has support for mobile devices."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"author",
|
||||
"service",
|
||||
"description",
|
||||
"url",
|
||||
"version",
|
||||
"apiVersion",
|
||||
"logo",
|
||||
"thumbnail",
|
||||
"color",
|
||||
"tags",
|
||||
"category"
|
||||
]
|
||||
}
|
||||
255
apps/schema-server/schemas/metadata/1.16.json
Normal file
@@ -0,0 +1,255 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.16",
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes an activity.",
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this activity."
|
||||
},
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this activity.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this activity is for."
|
||||
},
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the activity in multiple languages.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key in the format: languagecode or languagecode-regioncode.",
|
||||
"pattern": "^[a-z]{2,3}(?:-(?:[a-z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2,3}(?:-(?:[a-z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the activity in the key's language."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"en"
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"type": [
|
||||
"string",
|
||||
"array"
|
||||
],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added. Not used for matching.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the activity in the format: major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"apiVersion": {
|
||||
"type": "integer",
|
||||
"description": "The Activity System version this activity supports.",
|
||||
"minimum": 1,
|
||||
"maximum": 2
|
||||
},
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this activity is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this activity is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this activity is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"description": "The tags for the activity.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the activity falls under.",
|
||||
"enum": [
|
||||
"anime",
|
||||
"games",
|
||||
"music",
|
||||
"socials",
|
||||
"videos",
|
||||
"other"
|
||||
]
|
||||
},
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity should run in iframes."
|
||||
},
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the activity to inject into."
|
||||
},
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match iframes for the activity to inject into."
|
||||
},
|
||||
"allowURLOverrides": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity should allow the user to override the activity's matching regExps."
|
||||
},
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the activity.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "The description of the setting. Only applicable if `multiLanguage` is disabled."
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
"value": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
"items": {
|
||||
"type": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean"
|
||||
],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
"multiLanguage": {
|
||||
"type": "boolean",
|
||||
"description": "When true, strings from the `general.json` file are available for use, plus the <service>.json file. False is not allowed."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"mobile": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the activity has support for mobile devices."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"author",
|
||||
"service",
|
||||
"description",
|
||||
"url",
|
||||
"version",
|
||||
"apiVersion",
|
||||
"logo",
|
||||
"thumbnail",
|
||||
"color",
|
||||
"tags",
|
||||
"category",
|
||||
"regExp"
|
||||
]
|
||||
}
|
||||
257
apps/schema-server/schemas/metadata/1.2.json
Normal file
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.0",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(_[A-Z]{2})?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(_[A-Z]{2})?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?:\\/\\/?(?:[a-z0-9-]+\\.)*[0-9a-z_-]+(?:\\.[a-z]+)+\\/.*$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?:\\/\\/?([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+\\/.*$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa[bs] fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
257
apps/schema-server/schemas/metadata/1.3.json
Normal file
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.3",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(_[A-Z]{2})?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(_[A-Z]{2})?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?:\\/\\/?(?:[a-z0-9-]+\\.)*[0-9a-z_-]+(?:\\.[a-z]+)+\\/.*$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?:\\/\\/?([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+\\/.*$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa[bsd] fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
257
apps/schema-server/schemas/metadata/1.4.json
Normal file
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.4",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(_[A-Z]{2})?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(_[A-Z]{2})?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?://(i).(imgur).(com)/(.*?).(png|jpg)$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?://(i).(imgur).(com)/(.*?).(png|jpg)$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa[bsd] fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
257
apps/schema-server/schemas/metadata/1.5.json
Normal file
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.5",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(_[A-Z]{2})?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(_[A-Z]{2})?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?://(i).(imgur).(com)/(.*?).(png|jpe?g|gif)$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?://(i).(imgur).(com)/(.*?).(png|jpe?g)$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa[bsd] fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
257
apps/schema-server/schemas/metadata/1.6.json
Normal file
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.6",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?://(i).(imgur).(com)/(.*?).(png|jpe?g|gif)$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?://(i).(imgur).(com)/(.*?).(png|jpe?g)$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa[bsd] fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
257
apps/schema-server/schemas/metadata/1.7.json
Normal file
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.7",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?://(i).(imgur).(com)/(.*?).(png|jpe?g|gif)$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?://(i).(imgur).(com)/(.*?).(png|jpe?g)$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
257
apps/schema-server/schemas/metadata/1.8.json
Normal file
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.8",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?://(cdn[.]rcd[.]gg|i[.]imgur[.]com)/(.*?)[.](png|jpe?g|gif|webp)$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?://(cdn[.]rcd[.]gg|i[.]imgur[.]com)/(.*?)[.](png|jpe?g|gif|webp)$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
257
apps/schema-server/schemas/metadata/1.9.json
Normal file
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://schemas.premid.app/metadata/1.9",
|
||||
|
||||
"title": "Metadata",
|
||||
"type": "object",
|
||||
"description": "Metadata that describes a presence.",
|
||||
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"description": "User information.",
|
||||
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the user."
|
||||
},
|
||||
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The Discord snowflake of the user.",
|
||||
"pattern": "^\\d+$"
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "id"]
|
||||
}
|
||||
},
|
||||
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"$comment": "This is required otherwise the schema will fail itself when it is applied to a document via $schema. This is optional so that validators that use this schema don't fail if the metadata doesn't have the $schema property.",
|
||||
|
||||
"type": "string",
|
||||
"description": "The metadata schema URL."
|
||||
},
|
||||
|
||||
"author": {
|
||||
"$ref": "#/definitions/user",
|
||||
"description": "The author of this presence."
|
||||
},
|
||||
|
||||
"contributors": {
|
||||
"type": "array",
|
||||
"description": "Any extra contributors to this presence.",
|
||||
|
||||
"items": {
|
||||
"$ref": "#/definitions/user"
|
||||
}
|
||||
},
|
||||
|
||||
"service": {
|
||||
"type": "string",
|
||||
"description": "The service this presence is for."
|
||||
},
|
||||
|
||||
"altnames": {
|
||||
"type": "array",
|
||||
"description": "Alternative names for the service.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "An alternative name."
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"description": {
|
||||
"type": "object",
|
||||
"description": "A description of the presence in multiple languages.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The language key. The key must be languagecode(_REGIONCODE).",
|
||||
"pattern": "^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$"
|
||||
},
|
||||
"patternProperties": {
|
||||
"^[a-z]{2}(?:_(?:[A-Z]{2}|[0-9]{1,3}))?$": {
|
||||
"type": "string",
|
||||
"description": "The description of the presence in the key's language."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["en"]
|
||||
},
|
||||
|
||||
"url": {
|
||||
"type": ["string", "array"],
|
||||
"description": "The service's website URL, or an array of URLs. Protocols should not be added.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "One of the service's website URLs.",
|
||||
"pattern": "^(([a-z0-9-]+\\.)*[0-9a-z_-]+(\\.[a-z]+)+|(\\d{1,3}\\.){3}\\d{1,3}|localhost)$"
|
||||
},
|
||||
"minItems": 2
|
||||
},
|
||||
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "The SemVer version of the presence. Must just be major.minor.patch.",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "The logo of the service this presence is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
|
||||
"thumbnail": {
|
||||
"type": "string",
|
||||
"description": "A thumbnail of the service this presence is for.",
|
||||
"pattern": "^https?://.+\\.(png|jpe?g|gif|webp)$"
|
||||
},
|
||||
|
||||
"color": {
|
||||
"type": "string",
|
||||
"description": "The theme color of the service this presence is for. Must be either a 6 digit or a 3 digit hex code.",
|
||||
"pattern": "^#([A-Fa-f0-9]{3}){1,2}$"
|
||||
},
|
||||
|
||||
"tags": {
|
||||
"type": ["array"],
|
||||
"description": "The tags for the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "A tag.",
|
||||
"pattern": "^[^A-Z\\s!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^_`{|}~]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "The category the presence falls under.",
|
||||
"enum": ["anime", "games", "music", "socials", "videos", "other"]
|
||||
},
|
||||
|
||||
"iframe": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the presence should run in IFrames."
|
||||
},
|
||||
|
||||
"readLogs": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the extension should be reading logs."
|
||||
},
|
||||
|
||||
"regExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match URLs for the presence to inject into."
|
||||
},
|
||||
|
||||
"iFrameRegExp": {
|
||||
"type": "string",
|
||||
"description": "A regular expression used to match IFrames for the presence to inject into."
|
||||
},
|
||||
|
||||
"button": {
|
||||
"type": "boolean",
|
||||
"description": "Controls whether the presence is automatically added when the extension is installed. For partner presences only."
|
||||
},
|
||||
|
||||
"warning": {
|
||||
"type": "boolean",
|
||||
"description": "Shows a warning saying that it requires additional steps for the presence to function correctly."
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"type": "array",
|
||||
"description": "An array of settings the user can change in the presence.",
|
||||
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A setting.",
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the setting. Required only if `multiLanguage` is disabled."
|
||||
},
|
||||
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "The icon of the setting. Required only if `multiLanguage` is disabled.",
|
||||
"pattern": "^fa([bsdrlt]|([-](brands|solid|duotone|regular|light|thin))) fa-[0-9a-z-]+$"
|
||||
},
|
||||
|
||||
"if": {
|
||||
"type": "object",
|
||||
"description": "Restrict showing this setting if another setting is the defined value.",
|
||||
|
||||
"propertyNames": {
|
||||
"type": "string",
|
||||
"description": "The ID of the setting."
|
||||
},
|
||||
|
||||
"patternProperties": {
|
||||
"": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"placeholder": {
|
||||
"type": "string",
|
||||
"description": "The placeholder for settings that require input. Shown when the input is empty."
|
||||
},
|
||||
|
||||
"value": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The default value of the setting. Not compatible with `values`."
|
||||
},
|
||||
|
||||
"values": {
|
||||
"type": "array",
|
||||
"description": "The default values of the setting. Not compatible with `value`.",
|
||||
|
||||
"items": {
|
||||
"type": ["string", "number", "boolean"],
|
||||
"description": "The value of the setting."
|
||||
}
|
||||
},
|
||||
|
||||
"multiLanguage": {
|
||||
"type": ["string", "boolean", "array"],
|
||||
"description": "When false, multi-localization is disabled. When true, strings from the `general.json` file are available for use. When a string, it is the name of a file (excluding .json) of a used language from the localization GitHub repo. When an array of strings, it is all of the file names (excluding .json) of used languages from the localization GitHub repo.",
|
||||
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "The name of a file from the localization GitHub repository."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": ["author", "service", "description", "url", "version", "logo", "thumbnail", "color", "tags", "category"]
|
||||
}
|
||||
35
apps/schema-server/schemas/metadata/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Metadata Schema
|
||||
|
||||
This schema is used to validate the `metadata.json` files in [PreMiD/Presences](https://github.com/PreMiD/Presences).
|
||||
|
||||
## 1.7
|
||||
|
||||
- Updates `icon` regex for compability with FontAwesome v6 icon labelling
|
||||
|
||||
## 1.6
|
||||
|
||||
- Adds support for language locales that end in numbers (e.g. es_419)
|
||||
|
||||
## 1.5
|
||||
|
||||
- Allows `.gif` extensions for service logos and `.jpeg` extensions for service logos and thumbnails.
|
||||
|
||||
## 1.4
|
||||
|
||||
- Adds regex for only allowing imgur links for `logo` & `thumbnail`.
|
||||
|
||||
## 1.3
|
||||
|
||||
- Fixes validation for icons.
|
||||
|
||||
## 1.2
|
||||
|
||||
- Adds "readLogs".
|
||||
|
||||
## 1.1
|
||||
|
||||
- Adds "altnames" and "multiLanguage".
|
||||
|
||||
## 1.0
|
||||
|
||||
- First iteration of the metadata file format.
|
||||
37
apps/schema-server/src/index.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { app } from "./index.js";
|
||||
|
||||
describe("schemas", () => {
|
||||
it("/ should return date", async () => {
|
||||
const result = await app.inject({ url: "/" });
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.json()).toEqual({ date: expect.any(String) });
|
||||
});
|
||||
|
||||
it("/metadata/1.0 should return metadata schema", async () => {
|
||||
const result = await app.inject({ url: "/metadata/1.0" });
|
||||
const schema = await import(resolve(import.meta.dirname, "../schemas/metadata/1.0.json")) as { default: Record<string, unknown> };
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.json()).toEqual(schema.default);
|
||||
});
|
||||
|
||||
it("/metadata/1.0 should return cached metadata schema", async () => {
|
||||
const result = await app.inject({ url: "/metadata/1.0" });
|
||||
const schema = await import(resolve(import.meta.dirname, "../schemas/metadata/1.0.json")) as { default: Record<string, unknown> };
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.json()).toEqual(schema.default);
|
||||
});
|
||||
|
||||
it("/metadata/1 should return 404", async () => {
|
||||
const result = await app.inject({ url: "/metadata/1" });
|
||||
|
||||
expect(result.statusCode).toBe(404);
|
||||
expect(result.json()).toEqual({ error: "Schema not found." });
|
||||
});
|
||||
});
|
||||
63
apps/schema-server/src/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { extname, resolve } from "node:path";
|
||||
|
||||
import process from "node:process";
|
||||
import helmet from "@fastify/helmet";
|
||||
import fastify from "fastify";
|
||||
import { globby } from "globby";
|
||||
import type { RequestGenericInterface } from "fastify";
|
||||
|
||||
export const app = fastify();
|
||||
|
||||
await app.register(helmet);
|
||||
|
||||
app.get("/", (_, reply) => reply.send({ date: new Date() }));
|
||||
|
||||
interface versionRequest extends RequestGenericInterface {
|
||||
Params: {
|
||||
schemaName: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
const availableSchemas: Record<string, {
|
||||
version: string;
|
||||
content: Record<string, unknown>;
|
||||
}[]> = {};
|
||||
|
||||
for (const schemaPath of await globby(resolve(import.meta.dirname, "../schemas/*/*.json"), { onlyFiles: true })) {
|
||||
const [schemaName, version] = schemaPath.split("/").slice(-2) as [string, string];
|
||||
|
||||
let schemaVersions = availableSchemas[schemaName];
|
||||
|
||||
if (!schemaVersions)
|
||||
schemaVersions = [];
|
||||
|
||||
const { default: content } = await import(schemaPath, { with: { type: "json" } }) as { default: Record<string, unknown> };
|
||||
schemaVersions.push({
|
||||
content,
|
||||
version: version.replace(extname(version), ""),
|
||||
});
|
||||
|
||||
availableSchemas[schemaName] = schemaVersions;
|
||||
}
|
||||
|
||||
app.get<versionRequest>("/:schemaName/:version", async (request, reply) => {
|
||||
const { schemaName, version } = request.params;
|
||||
|
||||
if (!availableSchemas[schemaName]?.some(schema => schema.version === version))
|
||||
return reply.status(404).send({ error: "Schema not found." });
|
||||
|
||||
return reply.send(availableSchemas[schemaName]?.find(schema => schema.version === version)?.content);
|
||||
});
|
||||
|
||||
/* c8 ignore start */
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
const url = await app.listen({
|
||||
host: process.env.HOST ?? "0.0.0.0",
|
||||
port: Number.parseInt(process.env.PORT ?? "80"),
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Listening on ${url}`);
|
||||
}
|
||||
/* c8 ignore stop */
|
||||
8
apps/schema-server/tsconfig.app.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
}
|
||||
}
|
||||
8
apps/schema-server/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"compilerOptions": {
|
||||
"types": ["@types/node"],
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["environment.d.ts", "src"]
|
||||
}
|
||||
1
commitlint.config.js
Normal file
@@ -0,0 +1 @@
|
||||
export default { extends: ["@commitlint/config-conventional"] };
|
||||
13
eslint.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import antfu from "@antfu/eslint-config";
|
||||
|
||||
export default antfu({
|
||||
ignores: [".pnpm-store/**", "**/lib/**", "**/generated/**"],
|
||||
formatters: true,
|
||||
vue: true,
|
||||
stylistic: {
|
||||
indent: "tab",
|
||||
semi: true,
|
||||
quotes: "double",
|
||||
},
|
||||
typescript: true,
|
||||
});
|
||||
@@ -1,416 +0,0 @@
|
||||
<project>
|
||||
<shortName>PreMiD</shortName>
|
||||
<fullName>PreMiD</fullName>
|
||||
<version>VERSION</version>
|
||||
<installerFilename>upgrader.${platform_exec_suffix}</installerFilename>
|
||||
<debugLevel>0</debugLevel>
|
||||
<licenseFile>../LICENSE</licenseFile>
|
||||
<leftImage>leftSide.png</leftImage>
|
||||
<logoImage>logo.png</logoImage>
|
||||
<splashImage>logo.png</splashImage>
|
||||
<componentList>
|
||||
<component>
|
||||
<name>default</name>
|
||||
<description>Default Component</description>
|
||||
<canBeEdited>1</canBeEdited>
|
||||
<selected>1</selected>
|
||||
<show>1</show>
|
||||
<folderList>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfiles</name>
|
||||
<platforms>all</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfileslinux</name>
|
||||
<platforms>linux</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfileslinux64</name>
|
||||
<platforms>linux-x64</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfileswindows</name>
|
||||
<platforms>windows</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfileswindows64</name>
|
||||
<platforms>windows-x64</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfilesosx</name>
|
||||
<platforms>osx</platforms>
|
||||
</folder>
|
||||
</folderList>
|
||||
<startMenuShortcutList>
|
||||
<startMenuShortcut>
|
||||
<comment>Uninstall ${product_fullname}</comment>
|
||||
<name>Uninstall ${product_fullname}</name>
|
||||
<runAsAdmin>0</runAsAdmin>
|
||||
<runInTerminal>0</runInTerminal>
|
||||
<windowsExec>${installdir}/${uninstallerName}.exe</windowsExec>
|
||||
<windowsExecArgs></windowsExecArgs>
|
||||
<windowsIcon></windowsIcon>
|
||||
<windowsPath>${installdir}/</windowsPath>
|
||||
</startMenuShortcut>
|
||||
<startMenuShortcut>
|
||||
<comment>Rich Presence for web services.</comment>
|
||||
<name>PreMiD</name>
|
||||
<runAsAdmin>0</runAsAdmin>
|
||||
<runInTerminal>0</runInTerminal>
|
||||
<windowsExec>${installdir}/PreMiD.${platform_exec_suffix}</windowsExec>
|
||||
<windowsExecArgs></windowsExecArgs>
|
||||
<windowsIcon></windowsIcon>
|
||||
<windowsPath></windowsPath>
|
||||
</startMenuShortcut>
|
||||
</startMenuShortcutList>
|
||||
</component>
|
||||
</componentList>
|
||||
<initializationActionList>
|
||||
<setInstallerVariable>
|
||||
<name>installdir</name>
|
||||
<persist>1</persist>
|
||||
<value>${windows_folder_appdata}/PreMiD</value>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</setInstallerVariable>
|
||||
<setInstallerVariable>
|
||||
<name>installdir</name>
|
||||
<persist>1</persist>
|
||||
<value>${platform_install_prefix}/PreMiD</value>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</setInstallerVariable>
|
||||
</initializationActionList>
|
||||
<readyToInstallActionList>
|
||||
<actionGroup>
|
||||
<customErrorMessage>Couldn't download release... Try again later.</customErrorMessage>
|
||||
<progressText>Downloading latest release...</progressText>
|
||||
<actionList>
|
||||
<showProgressDialog>
|
||||
<title>Downloading latest release...</title>
|
||||
<width>450</width>
|
||||
<actionList>
|
||||
<httpGet>
|
||||
<customErrorMessage>${platform_name}</customErrorMessage>
|
||||
<filename>${system_temp_directory}/PreMiD-release.zip</filename>
|
||||
<url>https://github.com/PreMiD/PreMiD/releases/latest/download/PreMiD-win32-x64.zip</url>
|
||||
</httpGet>
|
||||
</actionList>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows-x64</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</showProgressDialog>
|
||||
<showProgressDialog>
|
||||
<title>Downloading latest release...</title>
|
||||
<actionList>
|
||||
<httpGet>
|
||||
<customErrorMessage>${platform_name}</customErrorMessage>
|
||||
<filename>${system_temp_directory}/PreMiD-release.zip</filename>
|
||||
<url>https://github.com/PreMiD/PreMiD/releases/latest/download/PreMiD-win32-ia32.zip</url>
|
||||
</httpGet>
|
||||
</actionList>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows-x86</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</showProgressDialog>
|
||||
<showProgressDialog>
|
||||
<title>Downloading latest release...</title>
|
||||
<actionList>
|
||||
<httpGet>
|
||||
<customErrorMessage>${platform_name}</customErrorMessage>
|
||||
<filename>${system_temp_directory}/PreMiD-release.zip</filename>
|
||||
<url>https://github.com/PreMiD/PreMiD/releases/latest/download/PreMiD-darwin-x64.zip</url>
|
||||
</httpGet>
|
||||
</actionList>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</showProgressDialog>
|
||||
</actionList>
|
||||
</actionGroup>
|
||||
<actionGroup>
|
||||
<actionList>
|
||||
<!-- Remove the old ARP Entry
|
||||
Get the old version -->
|
||||
<registryGet>
|
||||
<key>HKEY_LOCAL_MACHINE\Software\${project.windowsSoftwareRegistryPrefix}</key>
|
||||
<name>Version</name>
|
||||
<variable>oldVersion</variable>
|
||||
</registryGet>
|
||||
|
||||
<!-- Delete the old ARP registry keys -->
|
||||
<registryDelete>
|
||||
<key>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${project.fullName} ${oldVersion}</key>
|
||||
<name></name>
|
||||
</registryDelete>
|
||||
<registryDelete>
|
||||
<key>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Management\ARPCache\${project.fullName} ${oldVersion}</key>
|
||||
<name></name>
|
||||
</registryDelete>
|
||||
</actionList>
|
||||
<ruleList>
|
||||
<platformTest type="windows"/>
|
||||
<isTrue value="${isUpgradeMode}"/>
|
||||
</ruleList>
|
||||
</actionGroup>
|
||||
<actionGroup>
|
||||
<progressText>Killing ${product_fullname}...</progressText>
|
||||
<actionList>
|
||||
<kill>
|
||||
<abortOnError>0</abortOnError>
|
||||
<name>${product_fullname}.exe</name>
|
||||
<showMessageOnError>0</showMessageOnError>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</kill>
|
||||
<runProgram>
|
||||
<program>pkill</program>
|
||||
<programArguments>PreMiD</programArguments>
|
||||
<runAs>${env(USER)}</runAs>
|
||||
<useMSDOSPath>0</useMSDOSPath>
|
||||
<workingDirectory>${installdir}/</workingDirectory>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
<processTest>
|
||||
<logic>is_running</logic>
|
||||
<name>PreMiD</name>
|
||||
</processTest>
|
||||
</ruleList>
|
||||
</runProgram>
|
||||
</actionList>
|
||||
</actionGroup>
|
||||
</readyToInstallActionList>
|
||||
<postInstallationActionList>
|
||||
<unzip>
|
||||
<addToUninstaller>1</addToUninstaller>
|
||||
<destinationDirectory>${installdir}</destinationDirectory>
|
||||
<progressText>Extracting release...</progressText>
|
||||
<zipFile>${system_temp_directory}/PreMiD-release.zip</zipFile>
|
||||
</unzip>
|
||||
<addDirectoriesToUninstaller>
|
||||
<addContents>1</addContents>
|
||||
<files>${installdir}/</files>
|
||||
</addDirectoriesToUninstaller>
|
||||
</postInstallationActionList>
|
||||
<preUninstallationActionList>
|
||||
<actionGroup>
|
||||
<progressText>Killing ${product_fullname}...</progressText>
|
||||
<actionList>
|
||||
<kill>
|
||||
<abortOnError>0</abortOnError>
|
||||
<name>${product_fullname}.exe</name>
|
||||
<showMessageOnError>0</showMessageOnError>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</kill>
|
||||
<runProgram>
|
||||
<program>pkill</program>
|
||||
<programArguments>PreMiD</programArguments>
|
||||
<runAs>${env(USER)}</runAs>
|
||||
<useMSDOSPath>0</useMSDOSPath>
|
||||
<workingDirectory>${installdir}/</workingDirectory>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
<processTest>
|
||||
<logic>is_running</logic>
|
||||
<name>PreMiD</name>
|
||||
</processTest>
|
||||
</ruleList>
|
||||
</runProgram>
|
||||
</actionList>
|
||||
</actionGroup>
|
||||
</preUninstallationActionList>
|
||||
<compressionAlgorithm>lzham-ultra</compressionAlgorithm>
|
||||
<createOsxBundleZip>1</createOsxBundleZip>
|
||||
<defaultInstallationMode>unattended</defaultInstallationMode>
|
||||
<deleteOnExit>1</deleteOnExit>
|
||||
<description>Rich Presence for web services.</description>
|
||||
<disableSplashScreen>1</disableSplashScreen>
|
||||
<enableRollback>0</enableRollback>
|
||||
<enableSslSupport>1</enableSslSupport>
|
||||
<enableTimestamp>1</enableTimestamp>
|
||||
<installationScope>user</installationScope>
|
||||
<installationType>upgrade</installationType>
|
||||
<licenseFileEncoding>utf-8</licenseFileEncoding>
|
||||
<osxApplicationBundleIcon>appIcon.icns</osxApplicationBundleIcon>
|
||||
<osxApplicationBundleIdentifier>eu.Timeraa.PreMiD</osxApplicationBundleIdentifier>
|
||||
<osxPlatforms>osx-intel osx-x86_64</osxPlatforms>
|
||||
<osxUninstallerApplicationBundleIcon>appIcon.icns</osxUninstallerApplicationBundleIcon>
|
||||
<outputDirectory>../dist/installer</outputDirectory>
|
||||
<overwritePolicy>onlyIfNewer</overwritePolicy>
|
||||
<productDisplayIcon>C:/Users/metzf/Documents/Development/PreMiD/PreMiD/installer_assets/appIcon.ico</productDisplayIcon>
|
||||
<productUrlHelpLink>https://discord.premid.app</productUrlHelpLink>
|
||||
<productUrlInfoAbout>https://premid.app</productUrlInfoAbout>
|
||||
<readmeFileEncoding>utf-8</readmeFileEncoding>
|
||||
<removeLogFile>1</removeLogFile>
|
||||
<removeUninstallationLogFile>1</removeUninstallationLogFile>
|
||||
<requestedExecutionLevel>asInvoker</requestedExecutionLevel>
|
||||
<saveRelativePaths>1</saveRelativePaths>
|
||||
<summary>Rich Presence for web services.</summary>
|
||||
<unattendedModeUI>minimalWithDialogs</unattendedModeUI>
|
||||
<vendor>Timeraa</vendor>
|
||||
<width>625</width>
|
||||
<windowsExecutableIcon>appIcon.ico</windowsExecutableIcon>
|
||||
<windowsResourceFileDescription>Rich Presence for web services.</windowsResourceFileDescription>
|
||||
<windowsResourceFileVersion>${product_version}</windowsResourceFileVersion>
|
||||
<windowsUninstallerExecutableIcon>appIcon.ico</windowsUninstallerExecutableIcon>
|
||||
<licenseFileList>
|
||||
<licenseFile>
|
||||
<code>en</code>
|
||||
<encoding>utf-8</encoding>
|
||||
<file>../LICENSE</file>
|
||||
</licenseFile>
|
||||
</licenseFileList>
|
||||
<parameterList>
|
||||
<parameterGroup>
|
||||
<name>post_install_page</name>
|
||||
<title>Installation Finished</title>
|
||||
<explanation></explanation>
|
||||
<value></value>
|
||||
<default></default>
|
||||
<insertAfter>installation</insertAfter>
|
||||
<parameterList>
|
||||
<labelParameter>
|
||||
<name>general</name>
|
||||
<description>General</description>
|
||||
<explanation></explanation>
|
||||
<image></image>
|
||||
</labelParameter>
|
||||
<booleanParameter>
|
||||
<name>addDesktop</name>
|
||||
<description>Create Desktop Icon</description>
|
||||
<explanation></explanation>
|
||||
<value>false</value>
|
||||
<default>false</default>
|
||||
<displayStyle>checkbox-left</displayStyle>
|
||||
</booleanParameter>
|
||||
<booleanParameter>
|
||||
<name>launchApp</name>
|
||||
<description>Launch App</description>
|
||||
<explanation></explanation>
|
||||
<value>true</value>
|
||||
<default>true</default>
|
||||
<displayStyle>checkbox-left</displayStyle>
|
||||
</booleanParameter>
|
||||
<labelParameter>
|
||||
<name>extra</name>
|
||||
<description>Extra</description>
|
||||
<explanation></explanation>
|
||||
<image></image>
|
||||
</labelParameter>
|
||||
<booleanParameter>
|
||||
<name>openStore</name>
|
||||
<description>Open Presence Store</description>
|
||||
<explanation></explanation>
|
||||
<value>true</value>
|
||||
<default>true</default>
|
||||
<displayStyle>checkbox-left</displayStyle>
|
||||
</booleanParameter>
|
||||
</parameterList>
|
||||
<postShowPageActionList>
|
||||
<createShortcuts>
|
||||
<destination>${windows_folder_desktopdirectory}</destination>
|
||||
<ruleList>
|
||||
<isTrue>
|
||||
<value>${addDesktop}</value>
|
||||
</isTrue>
|
||||
</ruleList>
|
||||
<shortcutList>
|
||||
<quickLaunchShortcut>
|
||||
<comment>Rich Presence for web services.</comment>
|
||||
<name>PreMiD</name>
|
||||
<runAsAdmin>0</runAsAdmin>
|
||||
<runInTerminal>0</runInTerminal>
|
||||
<windowsExec>${installdir}/PreMiD.${platform_exec_suffix}</windowsExec>
|
||||
<windowsExecArgs></windowsExecArgs>
|
||||
<windowsIcon></windowsIcon>
|
||||
<windowsPath></windowsPath>
|
||||
</quickLaunchShortcut>
|
||||
</shortcutList>
|
||||
</createShortcuts>
|
||||
<runProgram>
|
||||
<abortOnError>0</abortOnError>
|
||||
<program>PreMiD.exe</program>
|
||||
<programArguments>&</programArguments>
|
||||
<progressText>Launching PreMiD...</progressText>
|
||||
<showMessageOnError>0</showMessageOnError>
|
||||
<workingDirectory>${installdir}</workingDirectory>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows</type>
|
||||
</platformTest>
|
||||
<isTrue>
|
||||
<value>${launchApp}</value>
|
||||
</isTrue>
|
||||
</ruleList>
|
||||
</runProgram>
|
||||
<runProgram>
|
||||
<abortOnError>0</abortOnError>
|
||||
<program>open</program>
|
||||
<programArguments>${installdir}/PreMiD.app</programArguments>
|
||||
<progressText>Launching PreMiD...</progressText>
|
||||
<runAs>${env(USER)}</runAs>
|
||||
<showMessageOnError>0</showMessageOnError>
|
||||
<useMSDOSPath>0</useMSDOSPath>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
<isTrue>
|
||||
<value>${launchApp}</value>
|
||||
</isTrue>
|
||||
</ruleList>
|
||||
</runProgram>
|
||||
<launchBrowser>
|
||||
<url>https://premid.app/store</url>
|
||||
<ruleList>
|
||||
<isTrue>
|
||||
<value>${openStore}</value>
|
||||
</isTrue>
|
||||
</ruleList>
|
||||
</launchBrowser>
|
||||
<exit/>
|
||||
</postShowPageActionList>
|
||||
</parameterGroup>
|
||||
</parameterList>
|
||||
<platformOptionsList>
|
||||
<platformOptions>
|
||||
<platform>windows</platform>
|
||||
</platformOptions>
|
||||
</platformOptionsList>
|
||||
</project>
|
||||
|
||||
@@ -1,412 +0,0 @@
|
||||
<project>
|
||||
<shortName>PreMiD</shortName>
|
||||
<fullName>PreMiD</fullName>
|
||||
<version>latest</version>
|
||||
<installerFilename>${product_shortname}-installer.${platform_exec_suffix}</installerFilename>
|
||||
<debugLevel>0</debugLevel>
|
||||
<licenseFile>../LICENSE</licenseFile>
|
||||
<leftImage>leftSide.png</leftImage>
|
||||
<logoImage>logo.png</logoImage>
|
||||
<splashImage>logo.png</splashImage>
|
||||
<componentList>
|
||||
<component>
|
||||
<name>default</name>
|
||||
<description>Default Component</description>
|
||||
<canBeEdited>1</canBeEdited>
|
||||
<selected>1</selected>
|
||||
<show>1</show>
|
||||
<folderList>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfiles</name>
|
||||
<platforms>all</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfileslinux</name>
|
||||
<platforms>linux</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfileslinux64</name>
|
||||
<platforms>linux-x64</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfileswindows</name>
|
||||
<platforms>windows</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfileswindows64</name>
|
||||
<platforms>windows-x64</platforms>
|
||||
</folder>
|
||||
<folder>
|
||||
<description>Program Files</description>
|
||||
<destination>${installdir}</destination>
|
||||
<name>programfilesosx</name>
|
||||
<platforms>osx</platforms>
|
||||
</folder>
|
||||
</folderList>
|
||||
<startMenuShortcutList>
|
||||
<startMenuShortcut>
|
||||
<comment>Uninstall ${product_fullname}</comment>
|
||||
<name>Uninstall ${product_fullname}</name>
|
||||
<runAsAdmin>0</runAsAdmin>
|
||||
<runInTerminal>0</runInTerminal>
|
||||
<windowsExec>${installdir}/${uninstallerName}.exe</windowsExec>
|
||||
<windowsExecArgs></windowsExecArgs>
|
||||
<windowsIcon></windowsIcon>
|
||||
<windowsPath>${installdir}/</windowsPath>
|
||||
</startMenuShortcut>
|
||||
<startMenuShortcut>
|
||||
<comment>Rich Presence for web services.</comment>
|
||||
<name>PreMiD</name>
|
||||
<runAsAdmin>0</runAsAdmin>
|
||||
<runInTerminal>0</runInTerminal>
|
||||
<windowsExec>${installdir}/PreMiD.${platform_exec_suffix}</windowsExec>
|
||||
<windowsExecArgs></windowsExecArgs>
|
||||
<windowsIcon></windowsIcon>
|
||||
<windowsPath></windowsPath>
|
||||
</startMenuShortcut>
|
||||
</startMenuShortcutList>
|
||||
</component>
|
||||
</componentList>
|
||||
<initializationActionList>
|
||||
<setInstallerVariable>
|
||||
<name>installdir</name>
|
||||
<persist>1</persist>
|
||||
<value>${windows_folder_appdata}/PreMiD</value>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</setInstallerVariable>
|
||||
<setInstallerVariable>
|
||||
<name>installdir</name>
|
||||
<persist>1</persist>
|
||||
<value>${platform_install_prefix}/PreMiD</value>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</setInstallerVariable>
|
||||
</initializationActionList>
|
||||
<readyToInstallActionList>
|
||||
<actionGroup>
|
||||
<customErrorMessage>Couldn't download release... Try again later.</customErrorMessage>
|
||||
<progressText>Downloading latest release...</progressText>
|
||||
<actionList>
|
||||
<showProgressDialog>
|
||||
<title>Downloading latest release...</title>
|
||||
<width>450</width>
|
||||
<actionList>
|
||||
<httpGet>
|
||||
<customErrorMessage>${platform_name}</customErrorMessage>
|
||||
<filename>${system_temp_directory}/PreMiD-release.zip</filename>
|
||||
<url>https://github.com/PreMiD/PreMiD/releases/latest/download/PreMiD-win32-x64.zip</url>
|
||||
</httpGet>
|
||||
</actionList>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows-x64</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</showProgressDialog>
|
||||
<showProgressDialog>
|
||||
<title>Downloading latest release...</title>
|
||||
<actionList>
|
||||
<httpGet>
|
||||
<customErrorMessage>${platform_name}</customErrorMessage>
|
||||
<filename>${system_temp_directory}/PreMiD-release.zip</filename>
|
||||
<url>https://github.com/PreMiD/PreMiD/releases/latest/download/PreMiD-win32-ia32.zip</url>
|
||||
</httpGet>
|
||||
</actionList>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows-x86</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</showProgressDialog>
|
||||
<showProgressDialog>
|
||||
<title>Downloading latest release...</title>
|
||||
<actionList>
|
||||
<httpGet>
|
||||
<customErrorMessage>${platform_name}</customErrorMessage>
|
||||
<filename>${system_temp_directory}/PreMiD-release.zip</filename>
|
||||
<url>https://github.com/PreMiD/PreMiD/releases/latest/download/PreMiD-darwin-x64.zip</url>
|
||||
</httpGet>
|
||||
</actionList>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</showProgressDialog>
|
||||
</actionList>
|
||||
</actionGroup>
|
||||
<actionGroup>
|
||||
<actionList>
|
||||
<!-- Remove the old ARP Entry
|
||||
Get the old version -->
|
||||
<registryGet>
|
||||
<key>HKEY_LOCAL_MACHINE\Software\${project.windowsSoftwareRegistryPrefix}</key>
|
||||
<name>Version</name>
|
||||
<variable>oldVersion</variable>
|
||||
</registryGet>
|
||||
|
||||
<!-- Delete the old ARP registry keys -->
|
||||
<registryDelete>
|
||||
<key>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${project.fullName} ${oldVersion}</key>
|
||||
<name></name>
|
||||
</registryDelete>
|
||||
<registryDelete>
|
||||
<key>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Management\ARPCache\${project.fullName} ${oldVersion}</key>
|
||||
<name></name>
|
||||
</registryDelete>
|
||||
</actionList>
|
||||
<ruleList>
|
||||
<platformTest type="windows"/>
|
||||
<isTrue value="${isUpgradeMode}"/>
|
||||
</ruleList>
|
||||
</actionGroup>
|
||||
<actionGroup>
|
||||
<progressText>Killing ${product_fullname}...</progressText>
|
||||
<actionList>
|
||||
<kill>
|
||||
<abortOnError>0</abortOnError>
|
||||
<name>${product_fullname}.exe</name>
|
||||
<showMessageOnError>0</showMessageOnError>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</kill>
|
||||
<runProgram>
|
||||
<program>pkill</program>
|
||||
<programArguments>PreMiD</programArguments>
|
||||
<runAs>${env(USER)}</runAs>
|
||||
<useMSDOSPath>0</useMSDOSPath>
|
||||
<workingDirectory>${installdir}/</workingDirectory>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
<processTest>
|
||||
<logic>is_running</logic>
|
||||
<name>PreMiD</name>
|
||||
</processTest>
|
||||
</ruleList>
|
||||
</runProgram>
|
||||
</actionList>
|
||||
</actionGroup>
|
||||
</readyToInstallActionList>
|
||||
<postInstallationActionList>
|
||||
<unzip>
|
||||
<addToUninstaller>1</addToUninstaller>
|
||||
<destinationDirectory>${installdir}</destinationDirectory>
|
||||
<progressText>Extracting release...</progressText>
|
||||
<zipFile>${system_temp_directory}/PreMiD-release.zip</zipFile>
|
||||
</unzip>
|
||||
<addDirectoriesToUninstaller>
|
||||
<addContents>1</addContents>
|
||||
<files>${installdir}/</files>
|
||||
</addDirectoriesToUninstaller>
|
||||
</postInstallationActionList>
|
||||
<preUninstallationActionList>
|
||||
<actionGroup>
|
||||
<progressText>Killing ${product_fullname}...</progressText>
|
||||
<actionList>
|
||||
<kill>
|
||||
<abortOnError>0</abortOnError>
|
||||
<name>${product_fullname}.exe</name>
|
||||
<showMessageOnError>0</showMessageOnError>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows</type>
|
||||
</platformTest>
|
||||
</ruleList>
|
||||
</kill>
|
||||
<runProgram>
|
||||
<program>pkill</program>
|
||||
<programArguments>PreMiD</programArguments>
|
||||
<runAs>${env(USER)}</runAs>
|
||||
<useMSDOSPath>0</useMSDOSPath>
|
||||
<workingDirectory>${installdir}/</workingDirectory>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
<processTest>
|
||||
<logic>is_running</logic>
|
||||
<name>PreMiD</name>
|
||||
</processTest>
|
||||
</ruleList>
|
||||
</runProgram>
|
||||
</actionList>
|
||||
</actionGroup>
|
||||
</preUninstallationActionList>
|
||||
<compressionAlgorithm>lzham-ultra</compressionAlgorithm>
|
||||
<createOsxBundleZip>1</createOsxBundleZip>
|
||||
<deleteOnExit>1</deleteOnExit>
|
||||
<description>Rich Presence for web services.</description>
|
||||
<disableSplashScreen>1</disableSplashScreen>
|
||||
<enableRollback>0</enableRollback>
|
||||
<enableSslSupport>1</enableSslSupport>
|
||||
<enableTimestamp>1</enableTimestamp>
|
||||
<installationScope>user</installationScope>
|
||||
<licenseFileEncoding>utf-8</licenseFileEncoding>
|
||||
<osxApplicationBundleIcon>appIcon.icns</osxApplicationBundleIcon>
|
||||
<osxApplicationBundleIdentifier>eu.Timeraa.PreMiD</osxApplicationBundleIdentifier>
|
||||
<osxPlatforms>osx-intel osx-x86_64</osxPlatforms>
|
||||
<osxUninstallerApplicationBundleIcon>appIcon.icns</osxUninstallerApplicationBundleIcon>
|
||||
<outputDirectory>../dist/installer</outputDirectory>
|
||||
<overwritePolicy>onlyIfNewer</overwritePolicy>
|
||||
<productDisplayIcon>C:/Users/metzf/Documents/Development/PreMiD/PreMiD/installer_assets/appIcon.ico</productDisplayIcon>
|
||||
<productUrlHelpLink>https://discord.premid.app</productUrlHelpLink>
|
||||
<productUrlInfoAbout>https://premid.app</productUrlInfoAbout>
|
||||
<readmeFileEncoding>utf-8</readmeFileEncoding>
|
||||
<removeLogFile>1</removeLogFile>
|
||||
<removeUninstallationLogFile>1</removeUninstallationLogFile>
|
||||
<requestedExecutionLevel>asInvoker</requestedExecutionLevel>
|
||||
<saveRelativePaths>1</saveRelativePaths>
|
||||
<summary>Rich Presence for web services.</summary>
|
||||
<vendor>Timeraa</vendor>
|
||||
<windowsExecutableIcon>appIcon.ico</windowsExecutableIcon>
|
||||
<windowsResourceFileDescription>Rich Presence for web services.</windowsResourceFileDescription>
|
||||
<windowsResourceFileVersion>${product_version}</windowsResourceFileVersion>
|
||||
<windowsUninstallerExecutableIcon>appIcon.ico</windowsUninstallerExecutableIcon>
|
||||
<licenseFileList>
|
||||
<licenseFile>
|
||||
<code>en</code>
|
||||
<encoding>utf-8</encoding>
|
||||
<file>../LICENSE</file>
|
||||
</licenseFile>
|
||||
</licenseFileList>
|
||||
<parameterList>
|
||||
<parameterGroup>
|
||||
<name>post_install_page</name>
|
||||
<title>Installation Finished</title>
|
||||
<explanation></explanation>
|
||||
<value></value>
|
||||
<default></default>
|
||||
<insertAfter>installation</insertAfter>
|
||||
<parameterList>
|
||||
<labelParameter>
|
||||
<name>general</name>
|
||||
<description>General</description>
|
||||
<explanation></explanation>
|
||||
<image></image>
|
||||
</labelParameter>
|
||||
<booleanParameter>
|
||||
<name>addDesktop</name>
|
||||
<description>Create Desktop Icon</description>
|
||||
<explanation></explanation>
|
||||
<value>false</value>
|
||||
<default>false</default>
|
||||
<displayStyle>checkbox-left</displayStyle>
|
||||
</booleanParameter>
|
||||
<booleanParameter>
|
||||
<name>launchApp</name>
|
||||
<description>Launch App</description>
|
||||
<explanation></explanation>
|
||||
<value>true</value>
|
||||
<default>true</default>
|
||||
<displayStyle>checkbox-left</displayStyle>
|
||||
</booleanParameter>
|
||||
<labelParameter>
|
||||
<name>extra</name>
|
||||
<description>Extra</description>
|
||||
<explanation></explanation>
|
||||
<image></image>
|
||||
</labelParameter>
|
||||
<booleanParameter>
|
||||
<name>openStore</name>
|
||||
<description>Open Presence Store</description>
|
||||
<explanation></explanation>
|
||||
<value>true</value>
|
||||
<default>true</default>
|
||||
<displayStyle>checkbox-left</displayStyle>
|
||||
</booleanParameter>
|
||||
</parameterList>
|
||||
<postShowPageActionList>
|
||||
<createShortcuts>
|
||||
<destination>${windows_folder_desktopdirectory}</destination>
|
||||
<ruleList>
|
||||
<isTrue>
|
||||
<value>${addDesktop}</value>
|
||||
</isTrue>
|
||||
</ruleList>
|
||||
<shortcutList>
|
||||
<quickLaunchShortcut>
|
||||
<comment>Rich Presence for web services.</comment>
|
||||
<name>PreMiD</name>
|
||||
<runAsAdmin>0</runAsAdmin>
|
||||
<runInTerminal>0</runInTerminal>
|
||||
<windowsExec>${installdir}/PreMiD.${platform_exec_suffix}</windowsExec>
|
||||
<windowsExecArgs></windowsExecArgs>
|
||||
<windowsIcon></windowsIcon>
|
||||
<windowsPath></windowsPath>
|
||||
</quickLaunchShortcut>
|
||||
</shortcutList>
|
||||
</createShortcuts>
|
||||
<runProgram>
|
||||
<abortOnError>0</abortOnError>
|
||||
<program>PreMiD.exe</program>
|
||||
<programArguments>&</programArguments>
|
||||
<progressText>Launching PreMiD...</progressText>
|
||||
<showMessageOnError>0</showMessageOnError>
|
||||
<workingDirectory>${installdir}</workingDirectory>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>windows</type>
|
||||
</platformTest>
|
||||
<isTrue>
|
||||
<value>${launchApp}</value>
|
||||
</isTrue>
|
||||
</ruleList>
|
||||
</runProgram>
|
||||
<runProgram>
|
||||
<abortOnError>0</abortOnError>
|
||||
<program>open</program>
|
||||
<programArguments>${installdir}/PreMiD.app</programArguments>
|
||||
<progressText>Launching PreMiD...</progressText>
|
||||
<runAs>${env(USER)}</runAs>
|
||||
<showMessageOnError>0</showMessageOnError>
|
||||
<useMSDOSPath>0</useMSDOSPath>
|
||||
<ruleList>
|
||||
<platformTest>
|
||||
<type>osx</type>
|
||||
</platformTest>
|
||||
<isTrue>
|
||||
<value>${launchApp}</value>
|
||||
</isTrue>
|
||||
</ruleList>
|
||||
</runProgram>
|
||||
<launchBrowser>
|
||||
<url>https://premid.app/store</url>
|
||||
<ruleList>
|
||||
<isTrue>
|
||||
<value>${openStore}</value>
|
||||
</isTrue>
|
||||
</ruleList>
|
||||
</launchBrowser>
|
||||
<exit/>
|
||||
</postShowPageActionList>
|
||||
</parameterGroup>
|
||||
</parameterList>
|
||||
<platformOptionsList>
|
||||
<platformOptions>
|
||||
<platform>windows</platform>
|
||||
</platformOptions>
|
||||
</platformOptionsList>
|
||||
</project>
|
||||
|
||||
|
Before Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
83
package.json
@@ -1,56 +1,41 @@
|
||||
{
|
||||
"name": "premid",
|
||||
"productName": "PreMiD",
|
||||
"description": "Discord Rich Presence for web services.",
|
||||
"version": "2.2.1",
|
||||
"repository": "https://github.com/PreMiD/PreMiD",
|
||||
"scripts": {
|
||||
"init": "tsc --skipLibCheck && tsc pkg util/prepare util/zip && devScript --copyOnly",
|
||||
"start": "electron dist/app/. --unhandled-rejections=strict",
|
||||
"dev": "devscript --depCheck",
|
||||
"pkg": "rimraf dist && tsc --skipLibCheck && devScript --copyOnly && cd dist/app/ && yarn && cd ../../ && node pkg",
|
||||
"deploy": "tsc --skipLibCheck .github/deploy && cd .github && node deploy.js"
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"description": "Monorepo containing most of PreMiD's codebase",
|
||||
"author": {
|
||||
"name": "Recodive oHG",
|
||||
"email": "contact@recodive.com",
|
||||
"url": "https://recodive.com"
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"engines": {
|
||||
"node": "^20.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"build": "pnpm clean && tsc -b tsconfig.app.json",
|
||||
"build:watch": "pnpm clean && tsc -b tsconfig.app.json -w",
|
||||
"clean": "tsc -b tsconfig.app.json --clean",
|
||||
"test": "vitest --run",
|
||||
"test:ui": "vitest --ui"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "5.1.0",
|
||||
"@types/auto-launch": "5.0.1",
|
||||
"@types/discord-rpc": "3.0.4",
|
||||
"@types/fs-extra": "9.0.11",
|
||||
"@types/ini": "1.3.30",
|
||||
"@types/node": "14.14.41",
|
||||
"@types/prompts": "2.0.11",
|
||||
"@types/request-promise-native": "1.0.17",
|
||||
"@types/rimraf": "3.0.0",
|
||||
"@types/socket.io": "2.1.13",
|
||||
"@types/ssh2-sftp-client": "5.3.1",
|
||||
"@types/unzipper": "^0.10.3",
|
||||
"archiver": "5.2.0",
|
||||
"chalk": "4.1.1",
|
||||
"electron": "11.3.0",
|
||||
"electron-packager": "15.2.0",
|
||||
"fast-glob": "3.2.5",
|
||||
"fs-extra": "9.1.0",
|
||||
"nodemon": "2.0.7",
|
||||
"ora": "5.3.0",
|
||||
"prompts": "2.4.1",
|
||||
"rimraf": "3.0.2",
|
||||
"ssh2-sftp-client": "6.0.1",
|
||||
"ts-devscript": "^3.0.3",
|
||||
"typescript": "4.1.5",
|
||||
"unzipper": "0.10.11",
|
||||
"yarn": "1.22.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"auto-launch": "5.0.5",
|
||||
"axios": "0.21.1",
|
||||
"chokidar": "3.5.1",
|
||||
"discord-rpc": "github:discordjs/RPC",
|
||||
"electron-store": "7.0.3",
|
||||
"socket.io": "3.1.2",
|
||||
"source-map-support": "^0.5.19"
|
||||
},
|
||||
"devScript": {
|
||||
"out": "dist/app"
|
||||
"@antfu/eslint-config": "^2.23.0",
|
||||
"@commitlint/cli": "^19.3.0",
|
||||
"@commitlint/config-conventional": "^19.2.2",
|
||||
"@types/node": "^20.11.17",
|
||||
"@vitest/coverage-v8": "^2.0.2",
|
||||
"@vitest/ui": "^2.0.2",
|
||||
"bumpp": "^9.3.0",
|
||||
"eslint": "9.7.0",
|
||||
"eslint-plugin-format": "^0.1.2",
|
||||
"husky": "^9.0.10",
|
||||
"prettier": "^3.2.5",
|
||||
"typescript": "^5.5.4",
|
||||
"vitest": "^2.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
123
pkg.ts
@@ -1,123 +0,0 @@
|
||||
import * as electronPackager from "electron-packager";
|
||||
import { platform } from "os";
|
||||
import * as prompts from "prompts";
|
||||
import * as ora from "ora";
|
||||
|
||||
(async () => {
|
||||
let response = {
|
||||
os: "current",
|
||||
arch: "all"
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== "DePloY")
|
||||
response = await prompts([
|
||||
{
|
||||
type: "select",
|
||||
name: "arch",
|
||||
message: "What architecture?",
|
||||
choices: [
|
||||
{
|
||||
title: "current",
|
||||
value: "current"
|
||||
},
|
||||
{
|
||||
title: "all",
|
||||
value: "all"
|
||||
},
|
||||
{
|
||||
title: "arm64",
|
||||
value: "arm64"
|
||||
},
|
||||
{
|
||||
title: "armv7l",
|
||||
value: "armv7l"
|
||||
},
|
||||
{
|
||||
title: "ia32",
|
||||
value: "ia32"
|
||||
},
|
||||
{
|
||||
title: "mips64el",
|
||||
value: "mips64el"
|
||||
},
|
||||
{
|
||||
title: "x64",
|
||||
value: "x64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "os",
|
||||
message: "What operating system?",
|
||||
choices: [
|
||||
{
|
||||
title: "current",
|
||||
value: "current"
|
||||
},
|
||||
{
|
||||
title: "all",
|
||||
value: "all"
|
||||
},
|
||||
{
|
||||
title: "darwin",
|
||||
value: "darwin"
|
||||
},
|
||||
{
|
||||
title: "linux",
|
||||
value: "linux"
|
||||
},
|
||||
{
|
||||
title: "mas",
|
||||
value: "mas"
|
||||
},
|
||||
{
|
||||
title: "win32",
|
||||
value: "win32"
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
if (!response.os) {
|
||||
process.exit();
|
||||
}
|
||||
|
||||
let icon: string;
|
||||
|
||||
if (
|
||||
response.os == "darwin" ||
|
||||
(response.os === "current" && platform() === "darwin")
|
||||
)
|
||||
icon = "./installer_assets/appIcon.icns";
|
||||
if (["ia32", "x64"].includes(response.arch) || platform() === "win32")
|
||||
icon = "./installer_assets/appIcon.ico";
|
||||
|
||||
let spinner = ora("Packaging app").start(),
|
||||
packagingOptions: electronPackager.Options = {
|
||||
dir: "./dist/app",
|
||||
out: "./dist",
|
||||
darwinDarkModeSupport: true,
|
||||
icon: icon,
|
||||
overwrite: true,
|
||||
quiet: true,
|
||||
appBundleId: "eu.Timeraa.PreMiD",
|
||||
appCategoryType: "Utilities",
|
||||
appCopyright: `Timeraa 2018-${new Date().getFullYear()}`,
|
||||
prune: true,
|
||||
asar: true,
|
||||
// @ts-ignore
|
||||
arch: response.arch,
|
||||
// @ts-ignore
|
||||
platform: response.os
|
||||
};
|
||||
|
||||
if (response.arch === "current") delete packagingOptions.arch;
|
||||
if (response.os === "current") delete packagingOptions.platform;
|
||||
|
||||
electronPackager(packagingOptions).then(() => {
|
||||
spinner.text = "Done!";
|
||||
spinner.succeed();
|
||||
process.exit();
|
||||
});
|
||||
})();
|
||||
6072
pnpm-lock.yaml
generated
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- apps/*
|
||||
- packages/*
|
||||