Compare commits
55 Commits
schema-ser
...
pd-v1.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da3d007a35 | ||
|
|
80d9888b37 | ||
|
|
3ae265d6fe | ||
|
|
3c9e7708f8 | ||
|
|
7663eacb58 | ||
|
|
85d5fa14fa | ||
|
|
2b55d34c19 | ||
|
|
9d1c2167be | ||
|
|
ce52ce9a97 | ||
|
|
d73db7e5a9 | ||
|
|
98ce2ff939 | ||
|
|
f98d615e7b | ||
|
|
9c461c83ff | ||
|
|
dc8dacd5f5 | ||
|
|
db47ae9043 | ||
|
|
a0f4e141b5 | ||
|
|
5bc4c0e5c0 | ||
|
|
c4765b8eeb | ||
|
|
ebc953b549 | ||
|
|
b407003450 | ||
|
|
3857702040 | ||
|
|
e997e946a0 | ||
|
|
2bcb87ec9c | ||
|
|
1d5af93390 | ||
|
|
8a0e10eab0 | ||
|
|
13e413329c | ||
|
|
d0fac8c495 | ||
|
|
74525de586 | ||
|
|
115923b935 | ||
|
|
b7af6bb80e | ||
|
|
b053e6395c | ||
|
|
698ea34848 | ||
|
|
d689324e62 | ||
|
|
fee8133965 | ||
|
|
b93db93236 | ||
|
|
1efa4b9bb0 | ||
|
|
4cf00f5ca9 | ||
|
|
94303a9bca | ||
|
|
fa611923b4 | ||
|
|
c57d4db5a3 | ||
|
|
5d7760caa3 | ||
|
|
0d5fe8e5ab | ||
|
|
1ac552f3cd | ||
|
|
4cf4e01286 | ||
|
|
e96e2c0cb6 | ||
|
|
8fb93a09f9 | ||
|
|
4d20ba95b1 | ||
|
|
85f428a295 | ||
|
|
a3018f528d | ||
|
|
3d8431c4d5 | ||
|
|
074d55b6d4 | ||
|
|
d22d88b3a6 | ||
|
|
f55a83977b | ||
|
|
4ccc02f16c | ||
|
|
759b2abef9 |
1
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1 @@
|
||||
FROM mcr.microsoft.com/devcontainers/base:bullseye
|
||||
24
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,24 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose
|
||||
{
|
||||
"name": "PreMiD",
|
||||
"dockerComposeFile": ["docker-compose.yml"],
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "lts",
|
||||
"nvmVersion": "latest"
|
||||
},
|
||||
"ghcr.io/joshuanianji/devcontainer-features/mount-pnpm-store:1": {},
|
||||
"ghcr.io/dhoeric/features/act:1": {}
|
||||
},
|
||||
"overrideFeatureInstallOrder": ["ghcr.io/devcontainers/features/node:1", "ghcr.io/joshuanianji/devcontainer-features/mount-pnpm-store:1"],
|
||||
"postCreateCommand": "pnpm i --frozen-lockfile",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["Gruntfuggly.todo-tree", "YoavBls.pretty-ts-errors", "EditorConfig.EditorConfig", "DeepScan.vscode-deepscan", "esbenp.prettier-vscode"]
|
||||
}
|
||||
},
|
||||
"shutdownAction": "stopCompose"
|
||||
}
|
||||
32
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
# Update this to the name of the service you want to work with in your docker-compose.yml file
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
# Uncomment if you want to override the service's Dockerfile to one in the .devcontainer
|
||||
# folder. Note that the path of the Dockerfile and context is relative to the *primary*
|
||||
# docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile"
|
||||
# array). The sample below assumes your primary file is in the root of your project.
|
||||
#
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: .devcontainer/Dockerfile
|
||||
|
||||
volumes:
|
||||
# Update this to wherever you want VS Code to mount the folder of your project
|
||||
- ..:/workspaces:cached
|
||||
|
||||
# Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
|
||||
# cap_add:
|
||||
# - SYS_PTRACE
|
||||
# security_opt:
|
||||
# - seccomp:unconfined
|
||||
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
7
.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
.DS_Store
|
||||
.Trashes
|
||||
.nuxt
|
||||
dist
|
||||
node_modules
|
||||
.env
|
||||
3
.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
node_modules
|
||||
coverage
|
||||
12
.eslintrc.cjs
Normal file
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
extends: ["@recodive/eslint-config"],
|
||||
overrides: [
|
||||
{
|
||||
extends: ["plugin:@typescript-eslint/disable-type-checked"],
|
||||
files: ["./**/*.{cjs,js,jsx}"],
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
};
|
||||
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
|
||||
}
|
||||
17
.github/workflows/cd-pd.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Build pd
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "pd-v*"
|
||||
permissions:
|
||||
packages: write
|
||||
jobs:
|
||||
build:
|
||||
name: Build pd
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build and Push Docker Image
|
||||
uses: premid/premid/.github/actions/build-and-push-docker@monorepo
|
||||
with:
|
||||
app: pd
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
17
.github/workflows/cd-schemas.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Build schema-server
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "schema-server-v*"
|
||||
permissions:
|
||||
packages: write
|
||||
jobs:
|
||||
build:
|
||||
name: Build schema-server
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build and Push Docker Image
|
||||
uses: premid/premid/.github/actions/build-and-push-docker@monorepo
|
||||
with:
|
||||
app: schema-server
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
44
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Build, Lint and Test
|
||||
on: [push, 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: 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:
|
||||
matrix:
|
||||
project: [pd, schema-server]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Build Docker Image
|
||||
uses: premid/premid/.github/actions/build-docker@monorepo
|
||||
with:
|
||||
dockerfile: ./apps/pd/Dockerfile
|
||||
tags: ${{ matrix.project }}
|
||||
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
|
||||
3
.gitignore
vendored
@@ -18,3 +18,6 @@ src/update.ini
|
||||
*.app
|
||||
*.xml.backup
|
||||
*.js
|
||||
|
||||
coverage
|
||||
*.tsbuildinfo
|
||||
2
.husky/commit-msg
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
pnpm exec commitlint --edit $1
|
||||
2
.husky/post-checkout
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
if ! diff --quiet HEAD@{1} HEAD -- package.json; then pnpm i --frozen-lockfile; fi
|
||||
2
.husky/pre-commit
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
pnpm run lint
|
||||
2
.husky/pre-push
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
pnpm run test
|
||||
8
.prettierignore
Normal file
@@ -0,0 +1,8 @@
|
||||
.pnpm-store
|
||||
|
||||
*.js
|
||||
*.cjs
|
||||
*.ts
|
||||
*.vue
|
||||
pnpm-lock.yaml
|
||||
cache
|
||||
1
.prettierrc.cjs
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require("@recodive/configs").default.prettier;
|
||||
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.
|
||||
|
||||
64
README.md
@@ -1,46 +1,50 @@
|
||||
<div align="center">
|
||||
|
||||
<img src=".github/Logo.png" width="150px" draggable="false"><br>
|
||||
<img src="https://cdn.rcd.gg/PreMiD.png" width="150px" />
|
||||
|
||||
# PreMiD
|
||||
|
||||
## Your Rich Presence for web services!
|
||||
[](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/PreMiD/PreMiD)
|
||||
|
||||

|
||||

|
||||

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

|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FPreMiD%2FPreMiD?ref=badge_shield)
|
||||
This is the monorepo for PreMiD. PreMiD is a simple, configurable utility that allows you to show what you're watching/listening to on your Discord profile.
|
||||
|
||||
<img src=".github/example.png" draggable="false"><br>
|
||||
## Getting Started
|
||||
|
||||
# About
|
||||
**If you are a user looking to install PreMiD, please visit the [official website](https://premid.app).**
|
||||
|
||||
**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.
|
||||
If you are a developer looking to contribute to PreMiD, read along.
|
||||
|
||||
# Features
|
||||
## Table of Contents
|
||||
|
||||
· 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!_
|
||||
- [Packages](#packages)
|
||||
- [License](#license)
|
||||
|
||||
# Installation/Troubleshooting
|
||||
## Packages
|
||||
|
||||
### Installation instructions, Troubleshooting guides etc. can be located at our [**docs**](https://docs.premid.app).
|
||||
This monorepo is split into multiple packages / projects. Here's a list of them:
|
||||
|
||||
# Support us
|
||||
- [apps/docs](apps/docs) - The official documentation for PreMiD.
|
||||
- [apps/pd](apps/pd/README.md) - A simple url shortener service to shorten urls longer than 256 characters.
|
||||
- [apps/schema-server](apps/schema-server) - Simple Schema server for the Presence manifest.
|
||||
|
||||
<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>
|
||||
## Development
|
||||
|
||||
### 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).
|
||||
|
||||
1
apps/docs/.vitepress/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
cache
|
||||
129
apps/docs/.vitepress/config.mts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: "Documentation",
|
||||
description: "Official Documentation",
|
||||
locales: {
|
||||
root: {
|
||||
label: "English",
|
||||
lang: "en-US",
|
||||
},
|
||||
de: {
|
||||
label: "Deutsch",
|
||||
lang: "de-DE",
|
||||
},
|
||||
},
|
||||
themeConfig: {
|
||||
nav: [
|
||||
{
|
||||
text: "Presence Development",
|
||||
link: "/presence-development/",
|
||||
},
|
||||
{
|
||||
text: "Reference",
|
||||
link: "/reference/presence",
|
||||
},
|
||||
],
|
||||
sidebar: {
|
||||
"/default": {
|
||||
base: "/",
|
||||
items: [
|
||||
{
|
||||
text: "Getting Started",
|
||||
link: "/",
|
||||
items: [
|
||||
{
|
||||
text: "Introduction",
|
||||
link: "/introduction/",
|
||||
},
|
||||
{
|
||||
text: "Installation",
|
||||
link: "/installation/",
|
||||
},
|
||||
{
|
||||
text: "Setup",
|
||||
link: "/setup/",
|
||||
},
|
||||
{
|
||||
text: "FAQ",
|
||||
link: "/faq/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Development",
|
||||
link: "/development/",
|
||||
items: [
|
||||
{
|
||||
text: "Presence Development",
|
||||
link: "/presence-development/",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Getting Started",
|
||||
link: "/presence-development/getting-started/",
|
||||
},
|
||||
{
|
||||
text: "Creating a Presence",
|
||||
link: "/presence-development/creating-a-presence/",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Contribute",
|
||||
link: "/contribute/",
|
||||
items: [
|
||||
{
|
||||
text: "Report a Bug",
|
||||
link: "https://github.com/PreMiD",
|
||||
},
|
||||
{
|
||||
text: "Submit a Feature",
|
||||
link: "https://discord.premid.app",
|
||||
},
|
||||
{
|
||||
text: "Donate",
|
||||
link: "https://patreon.com/Timeraa",
|
||||
},
|
||||
{
|
||||
text: "Translate",
|
||||
link: "https://crowdin.com/project/premid",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
"/reference/": {
|
||||
base: "/reference",
|
||||
items: [
|
||||
{
|
||||
text: "Reference",
|
||||
link: "/presence",
|
||||
items: [
|
||||
{
|
||||
text: "Presence",
|
||||
link: "/presence",
|
||||
},
|
||||
{
|
||||
text: "Iframe",
|
||||
link: "/iframe",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
socialLinks: [
|
||||
{ icon: "github", link: "https://github.com/PreMiD" },
|
||||
{ icon: "discord", link: "https://discord.premid.app" },
|
||||
{ icon: "x", link: "https://x.com/PreMiDapp" },
|
||||
],
|
||||
i18nRouting: true,
|
||||
logo: "/logo.svg",
|
||||
search: { provider: "local" },
|
||||
},
|
||||
lastUpdated: true,
|
||||
});
|
||||
5
apps/docs/getting-started.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Getting Started
|
||||
|
||||
Welcome to the official documentation for PreMiD! This guide will help you get started with PreMiD.
|
||||
|
||||
## Installation
|
||||
51
apps/docs/index.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: "PreMiD"
|
||||
text: "Documentation"
|
||||
tagline: "The official documentation for PreMiD."
|
||||
image:
|
||||
src: /logo.svg
|
||||
alt: PreMiD Logo
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Get Started
|
||||
link: /getting-started
|
||||
- theme: alt
|
||||
text: Presence Development
|
||||
link: /presence-development
|
||||
features:
|
||||
- icon: 🛠️
|
||||
title: Extensible
|
||||
details: Add Presences for your favorite websites and services. Or create your own!
|
||||
- icon: 🌐
|
||||
title: Cross-Platform
|
||||
details: PreMiD is available for all major browsers and platforms.
|
||||
- icon: 🚀
|
||||
title: Lightweight
|
||||
details: PreMiD is designed to be as lightweight as possible, so it won't slow down your system.
|
||||
---
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, rgb(209, 122, 254) 30%, rgb(89, 195, 246));
|
||||
|
||||
--vp-home-hero-image-background-image: linear-gradient(-45deg, rgb(209, 122, 254) 50%, rgb(89, 195, 246) 50%);
|
||||
--vp-home-hero-image-filter: blur(44px);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(56px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(68px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
14
apps/docs/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@premid/docs",
|
||||
"version": "0.0.0",
|
||||
"description": "Documentation for Premid",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docs:dev": "vitepress dev",
|
||||
"docs:build": "vitepress build",
|
||||
"docs:preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "1.0.0-rc.42"
|
||||
}
|
||||
}
|
||||
1
apps/docs/public/logo.svg
Normal file
|
After Width: | Height: | Size: 288 KiB |
68
apps/docs/reference/presence.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Presence Class
|
||||
|
||||
The `Presence` class is the main class used to create a Presence.
|
||||
|
||||
## Overview
|
||||
|
||||
The `Presence` class is the main class used to create a Presence. It is used to interact with the PreMiD Extension.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const presence = new Presence({
|
||||
clientId: "<Your Client ID>",
|
||||
});
|
||||
|
||||
presence.on("UpdateData", () => {
|
||||
// Logic to update the presence data
|
||||
|
||||
presence.setActivity({
|
||||
details: "Example Presence",
|
||||
state: "Example State",
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Constructor
|
||||
|
||||
### `new Presence(options: PresenceOptions)`
|
||||
|
||||
Creates a new `Presence` instance.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `options` (`PresenceOptions`): The options for the Presence.
|
||||
|
||||
#### Returns
|
||||
|
||||
- `Presence`: The new `Presence` instance.
|
||||
|
||||
## Properties
|
||||
|
||||
### `clientId: string`
|
||||
|
||||
The Client ID of the Presence.
|
||||
|
||||
## Methods
|
||||
|
||||
### `setActivity(activity: PresenceActivity)`
|
||||
|
||||
Sets the activity of the Presence.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `activity` (`PresenceActivity`): The activity to set.
|
||||
|
||||
### `clearActivity()`
|
||||
|
||||
Clears the activity of the Presence.
|
||||
|
||||
### `on(event: string, listener: Function)`
|
||||
|
||||
Adds a listener to an event.
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `event` (`string`): The event to listen to.
|
||||
|
||||
- `listener` (`Function`): The listener to add.
|
||||
26
apps/pd/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM node:20-alpine as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
COPY package.json pnpm-lock.yaml /app/
|
||||
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch --frozen-lockfile
|
||||
|
||||
COPY . /app
|
||||
|
||||
RUN pnpm install --frozen-lockfile --offline
|
||||
|
||||
RUN pnpm --filter @premid/pd build && \
|
||||
pnpm deploy --filter @premid/pd deploy
|
||||
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/deploy /app
|
||||
|
||||
USER node
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
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.
|
||||
10
apps/pd/environment.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/* eslint-disable unicorn/prevent-abbreviations */
|
||||
declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
NODE_ENV?: "development" | "production" | "test";
|
||||
REDIS_URL?: string;
|
||||
MAX_FILE_SIZE?: number;
|
||||
PORT?: string;
|
||||
HOST?: string;
|
||||
}
|
||||
}
|
||||
BIN
apps/pd/fixtures/1x1.png
Normal file
|
After Width: | Height: | Size: 95 B |
BIN
apps/pd/fixtures/test.mp4
Normal file
35
apps/pd/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@premid/pd",
|
||||
"version": "1.1.1",
|
||||
"description": "A small service to shorten image urls to get around Discord's 256 character limit",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "node --enable-source-maps .",
|
||||
"dev": "node --watch --enable-source-maps .",
|
||||
"build": "tsc -p tsconfig.app.json"
|
||||
},
|
||||
"private": true,
|
||||
"license": "MPL-2.0",
|
||||
"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, test } from "vitest";
|
||||
|
||||
import createKeyv from "./createKeyv.js";
|
||||
|
||||
test("should return keyv instance", async () => {
|
||||
const keyv = await createKeyv();
|
||||
|
||||
expect(keyv).toStrictEqual(expect.any(Object));
|
||||
});
|
||||
26
apps/pd/src/functions/createKeyv.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import KeyvRedis from "@keyv/redis";
|
||||
import Keyv from "keyv";
|
||||
|
||||
import redis from "../redis.js";
|
||||
|
||||
export default async function createKeyv() {
|
||||
let options: Keyv.Options<string> | undefined;
|
||||
|
||||
/* c8 ignore next 8 */
|
||||
if (process.env.REDIS_URL) {
|
||||
options = {
|
||||
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;
|
||||
}
|
||||
22
apps/pd/src/functions/createRedis.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { hostname } from "node:os";
|
||||
|
||||
import { Redis } from "ioredis";
|
||||
|
||||
/* c8 ignore start */
|
||||
export default function createRedis(): Redis {
|
||||
const redis = new Redis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
|
||||
name: `pd-${hostname()}`,
|
||||
});
|
||||
|
||||
/* 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();
|
||||
});
|
||||
});
|
||||
61
apps/pd/src/functions/createServer.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import cors from "@fastify/cors";
|
||||
import fastifyMultipart from "@fastify/multipart";
|
||||
import ratelimit from "@fastify/rate-limit";
|
||||
import fastify from "fastify";
|
||||
import { 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: 25,
|
||||
nameSpace: "pd-ratelimit-",
|
||||
redis,
|
||||
timeWindow: "1 minute",
|
||||
});
|
||||
|
||||
await server.register(fastifyMultipart, {
|
||||
limits: {
|
||||
fileSize: process.env.MAX_FILE_SIZE ?? 5 * 1024 * 1024,
|
||||
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", async () => {
|
||||
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 { CIDR } from "./isInCidRange.js";
|
||||
|
||||
export default async function getGoogleAddresses(): Promise<CIDR> {
|
||||
const { body } = await got.get("https://www.gstatic.com/ipranges/cloud.json"),
|
||||
result: GoogleResult = JSON.parse(body);
|
||||
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, test } from "vitest";
|
||||
|
||||
import isInCIDRRange from "./isInCidRange.js";
|
||||
|
||||
test("isInCIDRRange - IPv4 - in range", async () => {
|
||||
const CIDRs = [{ ipv4Prefix: "192.0.2.0/24" }],
|
||||
ip = "192.0.2.123",
|
||||
result = isInCIDRRange(CIDRs, ip);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("isInCIDRRange - IPv4 - not in range", async () => {
|
||||
const CIDRs = [{ ipv4Prefix: "192.0.2.0/24" }],
|
||||
ip = "192.0.3.123",
|
||||
result = isInCIDRRange(CIDRs, ip);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test("isInCIDRRange - IPv6 - in range", async () => {
|
||||
const CIDRs = [{ ipv6Prefix: "2001:db8::/32" }],
|
||||
ip = "2001:db8::1234",
|
||||
result = isInCIDRRange(CIDRs, ip);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("isInCIDRRange - IPv6 - not in range", async () => {
|
||||
const CIDRs = [{ ipv6Prefix: "2001:db8::/32" }],
|
||||
ip = "2001:db9::1234",
|
||||
result = isInCIDRRange(CIDRs, ip);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
26
apps/pd/src/functions/isInCidRange.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
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;
|
||||
}
|
||||
)[];
|
||||
4
apps/pd/src/googleCIDRs.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/* eslint-disable unicorn/filename-case */
|
||||
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, test } from "vitest";
|
||||
|
||||
import { createServer } from "./functions/createServer.js";
|
||||
|
||||
test("/health", async () => {
|
||||
const server = await createServer(),
|
||||
result = await server.inject({
|
||||
method: "GET",
|
||||
url: "/health",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(204);
|
||||
expect(result.body).toBe("");
|
||||
});
|
||||
15
apps/pd/src/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/* c8 ignore start */
|
||||
import { createServer } from "./functions/createServer.js";
|
||||
import redis from "./redis.js";
|
||||
|
||||
if (!process.env.REDIS_URL) console.log("WARNING: No REDIS_URL environment variable set");
|
||||
|
||||
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
|
||||
3
apps/pd/src/keyv.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createKeyv from "./functions/createKeyv.js";
|
||||
|
||||
export default await createKeyv();
|
||||
3
apps/pd/src/redis.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createRedis from "./functions/createRedis.js";
|
||||
|
||||
export default createRedis();
|
||||
98
apps/pd/src/routes/createFromBase64.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
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\"");
|
||||
});
|
||||
|
||||
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/svg+xml;base64,s",
|
||||
url: "/create/base64",
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(400);
|
||||
expect(result.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));
|
||||
});
|
||||
|
||||
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));
|
||||
});
|
||||
});
|
||||
47
apps/pd/src/routes/createFromBase64.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { RouteHandlerMethod } from "fastify";
|
||||
import mime from "mime-types";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import keyv 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) as string);
|
||||
|
||||
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 url = await keyv.get(body);
|
||||
|
||||
if (url) {
|
||||
await Promise.all([
|
||||
keyv.set(url, body, 30 * 60 * 1000),
|
||||
keyv.set(body, url, 30 * 60 * 1000),
|
||||
]);
|
||||
|
||||
reply.header("Cache-control", `public, max-age=${30 * 60}`);
|
||||
return reply.send(process.env.HOST + url);
|
||||
}
|
||||
|
||||
const uniqueId = `${nanoid(10)}.${type}`;
|
||||
|
||||
await Promise.all([
|
||||
keyv.set(body, uniqueId, 30 * 60 * 1000),
|
||||
keyv.set(uniqueId, body, 30 * 60 * 1000),
|
||||
]);
|
||||
|
||||
reply.header("Cache-control", `public, max-age=${30 * 60}`);
|
||||
return reply.send(process.env.BASE_URL + uniqueId);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
107
apps/pd/src/routes/createFromImage.test.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { RequestOptions } from "node:http";
|
||||
import { 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(),
|
||||
form = new FormData(),
|
||||
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(() => {
|
||||
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\"");
|
||||
});
|
||||
|
||||
it("should return a 400 status code when the file is invalid", async ({ expect }) => {
|
||||
form.set("file", new Blob([new Uint8Array(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\"");
|
||||
});
|
||||
|
||||
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));
|
||||
});
|
||||
});
|
||||
52
apps/pd/src/routes/createFromImage.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { RouteHandlerMethod } from "fastify";
|
||||
import { fileTypeFromBuffer } from "file-type";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import keyv 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(),
|
||||
body = `data:${type.mime};base64,${buffer.toString("base64")}`,
|
||||
url = await keyv.get(body);
|
||||
|
||||
if (url) {
|
||||
await Promise.all([
|
||||
keyv.set(url, body, 30 * 60 * 1000),
|
||||
keyv.set(body, url, 30 * 60 * 1000),
|
||||
]);
|
||||
|
||||
reply.header("Cache-control", `public, max-age=${30 * 60}`);
|
||||
return reply.send(process.env.HOST + url);
|
||||
}
|
||||
|
||||
const uniqueId = `${nanoid(10)}.${type.ext}`;
|
||||
|
||||
await Promise.all([
|
||||
keyv.set(body, uniqueId, 30 * 60 * 1000),
|
||||
keyv.set(uniqueId, body, 30 * 60 * 1000),
|
||||
]);
|
||||
|
||||
reply.header("Cache-control", `public, max-age=${30 * 60}`);
|
||||
return reply.send(process.env.BASE_URL + uniqueId);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
64
apps/pd/src/routes/createShortenedLink.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
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,
|
||||
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);
|
||||
});
|
||||
});
|
||||
33
apps/pd/src/routes/createShortenedLink.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { RouteHandlerMethod } from "fastify";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import keyv 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");
|
||||
|
||||
const keyvUrl = (await keyv.get(url)) as string | undefined;
|
||||
|
||||
void reply.header("Cache-control", "public, max-age=1800");
|
||||
|
||||
if (keyvUrl) {
|
||||
await Promise.all([keyv.set(url, keyvUrl, 1800), keyv.set(keyvUrl, url, 1800)]);
|
||||
|
||||
return reply.send(process.env.BASE_URL + keyvUrl);
|
||||
}
|
||||
|
||||
const uniqueId = nanoid(10);
|
||||
|
||||
await Promise.all([keyv.set(url, uniqueId, 1800), keyv.set(uniqueId, url, 1800)]);
|
||||
|
||||
return reply.send(process.env.BASE_URL + uniqueId);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
100
apps/pd/src/routes/getFullLink.test.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
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)),
|
||||
imageBase64 = `data:image/png;base64,${imageBuffer.toString("base64")}`,
|
||||
|
||||
{ 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);
|
||||
});
|
||||
});
|
||||
42
apps/pd/src/routes/getFullLink.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { RouteHandlerMethod } from "fastify";
|
||||
|
||||
import isInCIDRRange from "../functions/isInCidRange.js";
|
||||
import googleCIDRs from "../googleCIDRs.js";
|
||||
import keyv from "../keyv.js";
|
||||
|
||||
const handler: RouteHandlerMethod = async (request, reply) => {
|
||||
/* c8 ignore next 1 */
|
||||
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");
|
||||
|
||||
await keyv.set(url, id, 1800);
|
||||
reply.header("Cache-control", "public, max-age=1800");
|
||||
|
||||
//* If it is not a base64 string, redirect to it
|
||||
if (!url.startsWith("data:image")) return reply.redirect(url);
|
||||
|
||||
const image = Buffer.from(
|
||||
url.replace(/^data:image\/\w+;base64,/, ""),
|
||||
"base64",
|
||||
),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
mime = url.split(";")[0]!.split(":")[1]!;
|
||||
|
||||
return reply.type(mime).send(image);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
8
apps/pd/tsconfig.app.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
}
|
||||
}
|
||||
8
apps/pd/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"include": ["environment.d.ts", "src"],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"types": ["@types/node"]
|
||||
}
|
||||
}
|
||||
26
apps/schema-server/Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM node:20-alpine as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
COPY package.json pnpm-lock.yaml /app/
|
||||
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm fetch --frozen-lockfile
|
||||
|
||||
COPY . /app
|
||||
|
||||
RUN pnpm install --frozen-lockfile --offline
|
||||
|
||||
RUN pnpm --filter @premid/schema-server build && \
|
||||
pnpm deploy --filter @premid/schema-server deploy
|
||||
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/deploy /app
|
||||
|
||||
USER node
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
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.
|
||||
8
apps/schema-server/environment.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/* eslint-disable unicorn/prevent-abbreviations */
|
||||
declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
NODE_ENV?: "development" | "production" | "test";
|
||||
PORT?: string;
|
||||
HOST?: string;
|
||||
}
|
||||
}
|
||||
23
apps/schema-server/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@premid/schema-server",
|
||||
"version": "1.0.0",
|
||||
"description": "A small service to serve the JSON schemas for PreMiD",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist",
|
||||
"schemas"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "node --enable-source-maps .",
|
||||
"dev": "node --watch --enable-source-maps .",
|
||||
"build": "tsc -p tsconfig.app.json"
|
||||
},
|
||||
"private": true,
|
||||
"license": "MPL-2.0",
|
||||
"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"]
|
||||
}
|
||||
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, test } from "vitest";
|
||||
|
||||
import { app } from "./index.js";
|
||||
|
||||
describe("schemas", () => {
|
||||
test("/ should return date", async () => {
|
||||
const result = await app.inject({ url: "/" });
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.json()).toEqual({ date: expect.any(String) });
|
||||
});
|
||||
|
||||
test("/metadata/1.0 should return metadata schema", async () => {
|
||||
const result = await app.inject({ url: "/metadata/1.0" }),
|
||||
schema = await import(resolve(import.meta.dirname, "../schemas/metadata/1.0.json"));
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.json()).toEqual(schema.default);
|
||||
});
|
||||
|
||||
test("/metadata/1.0 should return cached metadata schema", async () => {
|
||||
const result = await app.inject({ url: "/metadata/1.0" }),
|
||||
schema = await import(resolve(import.meta.dirname, "../schemas/metadata/1.0.json"));
|
||||
|
||||
expect(result.statusCode).toBe(200);
|
||||
expect(result.json()).toEqual(schema.default);
|
||||
});
|
||||
|
||||
test("/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." });
|
||||
});
|
||||
});
|
||||
61
apps/schema-server/src/index.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { extname, resolve } from "node:path";
|
||||
|
||||
import helmet from "@fastify/helmet";
|
||||
import fastify, { RequestGenericInterface } from "fastify";
|
||||
import { globby } from "globby";
|
||||
|
||||
export const app = fastify();
|
||||
|
||||
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" } });
|
||||
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": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"composite": true
|
||||
}
|
||||
}
|
||||
8
apps/schema-server/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"include": ["environment.d.ts", "src"],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"types": ["@types/node"]
|
||||
}
|
||||
}
|
||||
2
commitlint.config.cjs
Normal file
@@ -0,0 +1,2 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
module.exports = require("@recodive/configs").default.commitlint;
|
||||
@@ -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>
|
||||
|
||||