wip: repo refactor

This commit is contained in:
Florian Metz
2024-02-08 23:42:28 +01:00
parent f7f04213de
commit 759b2abef9
97 changed files with 4654 additions and 5232 deletions

3
.eslintignore Normal file
View File

@@ -0,0 +1,3 @@
dist
node_modules
coverage

12
.eslintrc.cjs Normal file
View 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",
},
};

View File

@@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 KiB

View File

@@ -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.

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

BIN
.github/Patreon.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

1
.github/PayPal.svg vendored

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.1 KiB

13
.github/SUPPORT.md vendored
View File

@@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 KiB

5
.github/renovate.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>Recodive/Recodive:renovate-config"],
"automerge": false
}

25
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Build, Lint and Test
on: [push, pull_request]
jobs:
build:
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
#TODO: Add Docker Build Step
#build-docker:

View File

@@ -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

2
.gitignore vendored
View File

@@ -18,3 +18,5 @@ src/update.ini
*.app
*.xml.backup
*.js
coverage

2
.husky/post-checkout Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
if diff --quiet HEAD@{1} HEAD -- package.json; then else pnpm i --frozen-lockfile; fi

2
.husky/pre-commit Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
pnpm run lint

2
.husky/pre-push Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
pnpm run test

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
*.js
*.cjs
*.ts
*.vue

1
.prettierrc.cjs Normal file
View File

@@ -0,0 +1 @@
module.exports = require("@recodive/configs").default.prettier;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -1 +1 @@
* @Timeraa
* @Timeraa

View File

@@ -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.

View File

@@ -1,46 +0,0 @@
<div align="center">
<img src=".github/Logo.png" width="150px" draggable="false"><br>
# PreMiD
## Your Rich Presence for web services!
![GitHub](https://img.shields.io/github/license/PreMiD/PreMiD?style=for-the-badge)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/premid/premid?label=Application&style=for-the-badge)
![Chrome Web Store](https://img.shields.io/chrome-web-store/v/agjnjboanicjcpenljmaaigopkgdnihi?label=Extension&style=for-the-badge)
[![Chrome Web Store](https://img.shields.io/chrome-web-store/d/agjnjboanicjcpenljmaaigopkgdnihi.svg?label=Chrome&logo=google%20chrome&logoColor=white&colorA=4285F4&style=for-the-badge)](https://chrome.google.com/webstore/detail/premid/agjnjboanicjcpenljmaaigopkgdnihi)
![Website](https://img.shields.io/website?down_message=offline&label=PreMiD.app&style=for-the-badge&up_message=online&url=https%3A%2F%2Fpremid.app)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FPreMiD%2FPreMiD.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FPreMiD%2FPreMiD?ref=badge_shield)
<img src=".github/example.png" draggable="false"><br>
# About
**PreMiD** is a simple, configurable utility that allows you to show what you're doing on the web in your Discord **now playing status**. It supports many different websites, and will support multiple users watching the same content simultaneously in an upcoming update.
# Features
· 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!_
# Installation/Troubleshooting
### Installation instructions, Troubleshooting guides etc. can be located at our [**docs**](https://docs.premid.app).
# Support us
<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>
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FPreMiD%2FPreMiD.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FPreMiD%2FPreMiD?ref=badge_large)

23
apps/pd/Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM node:20-alpine as build
WORKDIR /app
COPY package.json pnpm-lock.yaml /app/
RUN corepack enable && \
pnpm install --frozen-lockfile
COPY . /app
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"]

10
apps/pd/environment.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
/* eslint-disable unicorn/prevent-abbreviations */
// env.d.ts
declare namespace NodeJS {
export interface ProcessEnv {
NODE_ENV?: "development" | "production";
REDIS_URL?: string;
PORT?: string;
HOST?: string;
}
}

25
apps/pd/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "@premid/pd",
"version": "1.0.0",
"description": "A small service to shorten image urls to get around Discord's 256 character limit",
"main": "dist/index.js",
"type": "module",
"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/rate-limit": "^9.1.0",
"@keyv/redis": "^2.8.4",
"fastify": "^4.26.0",
"got": "^14.2.0",
"ioredis": "^5.3.2",
"ipaddr.js": "^2.1.0",
"keyv": "^4.5.4",
"nanoid": "^5.0.5"
}
}

3
apps/pd/src/constants.ts Normal file
View File

@@ -0,0 +1,3 @@
import createKeyv from "./functions/createKeyv.js";
export const keyv = createKeyv();

View File

@@ -0,0 +1,9 @@
import { expect, test } from "vitest";
test("should return keyv instance", async () => {
const { default: createKeyv } = await import("../functions/createKeyv.js"),
keyv = createKeyv();
expect(keyv).toStrictEqual(expect.any(Object));
});

View File

@@ -0,0 +1,11 @@
import Keyv from "keyv";
export default function createKeyv() {
const keyv = new Keyv();
keyv.on("error", (error) => {
console.error("Keyv connection error:", error);
});
return keyv;
}

View File

@@ -0,0 +1,53 @@
import cors from "@fastify/cors";
import ratelimit from "@fastify/rate-limit";
import fastify from "fastify";
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() {
const server = fastify({
trustProxy: true,
});
await server.register(cors, {
methods: ["GET"],
origin: "*",
});
await server.register(ratelimit, {
max: 25,
nameSpace: "pd",
// TODO
/* redis, */
timeWindow: "1 minute",
});
server.get("/create/image", createFromImage);
server.get("/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;
}

View File

@@ -0,0 +1,31 @@
import got from "got";
import { describe, expect, it, vi } from "vitest";
describe("getCloudFlareAddresses", async () => {
it("should return an array of CIDR objects", async () => {
const { default: getCloudFlareAddr } = await import("../functions/getCloudFlareAddresses.js");
vi.spyOn(got, "get").mockResolvedValue({
body: "0.0.0.0\n0.0.0.0",
});
const result = await getCloudFlareAddr();
expect(result).toMatchInlineSnapshot(`
[
{
"ipv4Prefix": "0.0.0.0",
},
{
"ipv4Prefix": "0.0.0.0",
},
{
"ipv6Prefix": "0.0.0.0",
},
{
"ipv6Prefix": "0.0.0.0",
},
]
`);
});
});

View File

@@ -0,0 +1,9 @@
import got from "got";
import { CIDR } from "./isInCidRange.js";
export default async function getCloudFlareAddr(): Promise<CIDR> {
const [resv4, resv6] = await Promise.all([got.get("https://www.cloudflare.com/ips-v4"), got.get("https://www.cloudflare.com/ips-v6")]);
return [...resv4.body.split("\n").map(r => ({ ipv4Prefix: r })), ...resv6.body.split("\n").map(r => ({ ipv6Prefix: r }))];
}

View 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);
});

View 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;
}
)[];

14
apps/pd/src/index.test.ts Normal file
View 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("");
});

12
apps/pd/src/index.ts Normal file
View File

@@ -0,0 +1,12 @@
import { createServer } from "./functions/createServer.js";
if (!process.env.REDIS_URL) console.log("WARNING: No REDIS_URL environment variable set");
export const server = await createServer();
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}`);

View File

@@ -0,0 +1,7 @@
import { RouteHandlerMethod } from "fastify";
const handler: RouteHandlerMethod = async (request, reply) => {
console.log("createFromBase64");
};
export default handler;

View File

@@ -0,0 +1,7 @@
import { RouteHandlerMethod } from "fastify";
const handler: RouteHandlerMethod = async (request, reply) => {
console.log("createFromImage");
};
export default handler;

View File

@@ -0,0 +1,64 @@
import { describe, expect, it } from "vitest";
import { createServer } from "../functions/createServer.js";
describe("/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);
});
});

View File

@@ -0,0 +1,33 @@
import { RouteHandlerMethod } from "fastify";
import { nanoid } from "nanoid";
import { keyv } from "../constants.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;

View File

@@ -0,0 +1,7 @@
import { RouteHandlerMethod } from "fastify";
const handler: RouteHandlerMethod = async (request, reply) => {
console.log("getFullLink");
};
export default handler;

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"composite": true
}
}

File diff suppressed because one or more lines are too long

7
apps/pd/tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.app.json",
"include": ["environment.d.ts"],
"compilerOptions": {
"noEmit": true
}
}

6
docker-compose.yaml Normal file
View File

@@ -0,0 +1,6 @@
name: premid
services:
redis:
image: redis:alpine
ports:
- 6379:6379

View File

@@ -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>&amp;</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>

View File

@@ -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>&amp;</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>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,56 +1,38 @@
{
"name": "premid",
"productName": "PreMiD",
"description": "Discord Rich Presence for web services.",
"version": "2.2.1",
"repository": "https://github.com/PreMiD/PreMiD",
"version": "1.0.0",
"description": "Monorepo containing most of PreMiD's codebase",
"type": "module",
"scripts": {
"init": "tsc --skipLibCheck && tsc pkg util/prepare util/zip && devScript --copyOnly",
"start": "electron dist/app/. --unhandled-rejections=strict",
"dev": "devscript --depCheck",
"pkg": "rimraf dist && tsc --skipLibCheck && devScript --copyOnly && cd dist/app/ && yarn && cd ../../ && node pkg",
"deploy": "tsc --skipLibCheck .github/deploy && cd .github && node deploy.js"
"prepare": "husky",
"lint": "eslint . --ext .ts,.js && prettier --check .",
"lint:fix": "eslint . --ext .ts,.js --fix && prettier --write .",
"build": "pnpm clean && tsc -b tsconfig.app.json",
"build:watch": "pnpm clean && tsc -b tsconfig.app.json -w",
"clean": "tsc -b tsconfig.app.json --clean",
"test": "vitest --run",
"test:ui": "vitest --ui"
},
"author": {
"name": "Recodive oHG",
"email": "contact@recodive.com",
"url": "https://recodive.com"
},
"license": "MPL-2.0",
"devDependencies": {
"@types/archiver": "5.1.0",
"@types/auto-launch": "5.0.1",
"@types/discord-rpc": "3.0.4",
"@types/fs-extra": "9.0.11",
"@types/ini": "1.3.30",
"@types/node": "14.14.41",
"@types/prompts": "2.0.11",
"@types/request-promise-native": "1.0.17",
"@types/rimraf": "3.0.0",
"@types/socket.io": "2.1.13",
"@types/ssh2-sftp-client": "5.3.1",
"@types/unzipper": "^0.10.3",
"archiver": "5.2.0",
"chalk": "4.1.1",
"electron": "11.3.0",
"electron-packager": "15.2.0",
"fast-glob": "3.2.5",
"fs-extra": "9.1.0",
"nodemon": "2.0.7",
"ora": "5.3.0",
"prompts": "2.4.1",
"rimraf": "3.0.2",
"ssh2-sftp-client": "6.0.1",
"ts-devscript": "^3.0.3",
"typescript": "4.1.5",
"unzipper": "0.10.11",
"yarn": "1.22.10"
"@recodive/configs": "^1.7.1",
"@recodive/eslint-config": "^1.7.1",
"@rushstack/eslint-patch": "^1.7.2",
"@vitest/coverage-v8": "^1.2.2",
"@vitest/ui": "^1.2.2",
"eslint": "^8.56.0",
"husky": "^9.0.10",
"prettier": "^3.2.5",
"typescript": "^5.3.3",
"vitest": "^1.2.2"
},
"dependencies": {
"auto-launch": "5.0.5",
"axios": "0.21.1",
"chokidar": "3.5.1",
"discord-rpc": "github:discordjs/RPC",
"electron-store": "7.0.3",
"socket.io": "3.1.2",
"source-map-support": "^0.5.19"
},
"devScript": {
"out": "dist/app"
"packageManager": "pnpm@8.14.3",
"engines": {
"node": "^20.0.0"
}
}

123
pkg.ts
View File

@@ -1,123 +0,0 @@
import * as electronPackager from "electron-packager";
import { platform } from "os";
import * as prompts from "prompts";
import * as ora from "ora";
(async () => {
let response = {
os: "current",
arch: "all"
};
if (process.env.NODE_ENV !== "DePloY")
response = await prompts([
{
type: "select",
name: "arch",
message: "What architecture?",
choices: [
{
title: "current",
value: "current"
},
{
title: "all",
value: "all"
},
{
title: "arm64",
value: "arm64"
},
{
title: "armv7l",
value: "armv7l"
},
{
title: "ia32",
value: "ia32"
},
{
title: "mips64el",
value: "mips64el"
},
{
title: "x64",
value: "x64"
}
]
},
{
type: "select",
name: "os",
message: "What operating system?",
choices: [
{
title: "current",
value: "current"
},
{
title: "all",
value: "all"
},
{
title: "darwin",
value: "darwin"
},
{
title: "linux",
value: "linux"
},
{
title: "mas",
value: "mas"
},
{
title: "win32",
value: "win32"
}
]
}
]);
if (!response.os) {
process.exit();
}
let icon: string;
if (
response.os == "darwin" ||
(response.os === "current" && platform() === "darwin")
)
icon = "./installer_assets/appIcon.icns";
if (["ia32", "x64"].includes(response.arch) || platform() === "win32")
icon = "./installer_assets/appIcon.ico";
let spinner = ora("Packaging app").start(),
packagingOptions: electronPackager.Options = {
dir: "./dist/app",
out: "./dist",
darwinDarkModeSupport: true,
icon: icon,
overwrite: true,
quiet: true,
appBundleId: "eu.Timeraa.PreMiD",
appCategoryType: "Utilities",
appCopyright: `Timeraa 2018-${new Date().getFullYear()}`,
prune: true,
asar: true,
// @ts-ignore
arch: response.arch,
// @ts-ignore
platform: response.os
};
if (response.arch === "current") delete packagingOptions.arch;
if (response.os === "current") delete packagingOptions.platform;
electronPackager(packagingOptions).then(() => {
spinner.text = "Done!";
spinner.succeed();
process.exit();
});
})();

4085
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,2 @@
packages:
- apps/*

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 481 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" id="Слой_1" data-name="Слой 1" viewBox="0 0 512 512"><defs><style>.cls-1{fill:#fff;fill-rule:evenodd}</style></defs><title>pmd_logo_1</title><path d="M459.17,105H52.83a30.35,30.35,0,0,0-30.34,30.34V376.66A30.35,30.35,0,0,0,52.83,407H459.17a30.35,30.35,0,0,0,30.34-30.34V135.34A30.35,30.35,0,0,0,459.17,105Zm-185,94.4a13.93,13.93,0,0,1,9.9-4.11h90.29a14,14,0,0,1,0,28H284a14,14,0,0,1-9.9-23.91Zm-63.64,63.11L92.14,330.83a7.51,7.51,0,0,1-11.27-6.51V187.68a7.51,7.51,0,0,1,11.27-6.51l118.34,68.32A7.52,7.52,0,0,1,210.48,262.51ZM441.81,312.6a13.93,13.93,0,0,1-9.9,4.11H284a14,14,0,0,1,0-28H431.91a14,14,0,0,1,9.9,23.91Zm0-46.7a13.93,13.93,0,0,1-9.9,4.11H284a14,14,0,0,1,0-28H431.91a14,14,0,0,1,9.9,23.91Z" class="cls-1"/></svg>

Before

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

View File

@@ -1,34 +0,0 @@
import "source-map-support/register";
import { app } from "electron";
import { platform } from "os";
import { update as initAutoLaunch } from "./managers/launchManager";
import { init as initSocket } from "./managers/socketManager";
import { TrayManager } from "./managers/trayManager";
import { checkForUpdate } from "./util/updateChecker";
export let trayManager: TrayManager;
//* Define and set it to null
//* Set AppUserModelId for task manager etc
//* When app is ready
export let updateCheckerInterval = null;
//* Attempt to get lock to prevent multiple instances of PreMiD from running
let singleInstanceLock = app.requestSingleInstanceLock();
//* Application already running?
if (!singleInstanceLock) app.quit();
app.setAppUserModelId("Timeraa.PreMiD");
app.whenReady().then(async () => {
trayManager = new TrayManager();
await Promise.all([checkForUpdate(true), initAutoLaunch(), initSocket()]);
app.isPackaged
? (updateCheckerInterval = setInterval(checkForUpdate, 15 * 1000 * 60))
: undefined;
if (platform() === "darwin") app.dock.hide();
});

View File

@@ -1,123 +0,0 @@
import { Client } from "discord-rpc";
import { app } from "electron";
import { trayManager } from "../";
//* Import custom types
import PresenceData from "../../@types/PreMiD/PresenceData";
import { info } from "../util/debug";
export let rpcClients: Array<RPCClient> = [];
class RPCClient {
clientId: string;
currentPresence: PresenceData;
client: Client;
clientReady: boolean = false;
constructor(clientId: string) {
rpcClients.push(this);
this.clientId = clientId;
this.client = new Client({
transport: "ipc"
});
this.client.once("ready", () => {
this.clientReady = true;
this.setActivity();
});
this.client.once(
// @ts-ignore
"disconnected",
() =>
(rpcClients = rpcClients.filter(
client => client.clientId !== this.clientId
))
);
this.client.login({ clientId: this.clientId }).catch(() => this.destroy());
info(`Create RPC client (${this.clientId})`);
}
setActivity(presenceData?: PresenceData) {
presenceData = presenceData ? presenceData : this.currentPresence;
if (!this.clientReady || !presenceData) return;
if (presenceData.trayTitle)
trayManager.tray.setTitle(presenceData.trayTitle);
this.client
.setActivity(presenceData.presenceData)
.catch(() => this.destroy());
info("setActivity");
}
clearActivity() {
this.currentPresence = null;
if (!this.clientReady) return;
this.client.clearActivity().catch(() => this.destroy());
trayManager.tray.setTitle("");
}
async destroy() {
try {
info(`Destroy RPC client (${this.clientId})`);
if (this.clientReady) {
this.client.clearActivity();
this.client.destroy();
}
trayManager.tray.setTitle("");
rpcClients = rpcClients.filter(
client => client.clientId !== this.clientId
);
} catch (err) {}
}
}
/**
* Sets the user's activity
* @param presence PresenceData to set activity
*/
export function setActivity(presence: PresenceData) {
let client = rpcClients.find(c => c.clientId === presence.clientId);
if (!client) {
client = new RPCClient(presence.clientId);
client.currentPresence = presence;
} else client.setActivity(presence);
}
/**
* Clear a user's activity
* @param clientId clientId of presence to clear
*/
export function clearActivity(clientId: string = undefined) {
info("clearActivity");
if (clientId) {
let client = rpcClients.find(c => c.clientId === clientId);
client.clearActivity();
} else rpcClients.forEach(c => c.clearActivity());
}
export async function getDiscordUser() {
return new Promise((resolve, reject) => {
const c = new Client({ transport: "ipc" });
c.login({
clientId: "503557087041683458"
})
.then(({ user }) => c.destroy().then(() => resolve(user)))
.catch(reject);
});
}
app.once(
"will-quit",
async () => await Promise.all(rpcClients.map(c => c.destroy()))
);

View File

@@ -1,29 +0,0 @@
import AutoLaunch from "auto-launch";
import { app } from "electron";
import { settings } from "./settingsManager";
import { info } from "../util/debug";
//* Create autoLaunch object
let autoLaunch = new AutoLaunch({
name: app.name,
isHidden: true
});
/**
* Updates autoLaunch
*/
export async function update() {
//* If app not packaged return
//* Either enable/disable autolaunch
if (!app.isPackaged) {
//* Show debug
//* Return
info("Skipping autoLaunch.");
return;
}
if (settings.get("autoLaunch", true))
//* Enable if not enabled
autoLaunch.enable();
//* Disable if enabled
else autoLaunch.disable();
}

View File

@@ -1,82 +0,0 @@
import { readdirSync, readFileSync, unwatchFile } from "fs";
import { dialog, app } from "electron";
import { socket } from "./socketManager";
import { extname } from "path";
import { info } from "../util/debug";
import chokidar from "chokidar";
let presenceDevWatchedFiles = [],
currWatchPath = "",
currWatcher: chokidar.FSWatcher = null;
export async function watchDir(path: string) {
currWatchPath = path + "/";
let files = readdirSync(path);
if (currWatcher) await currWatcher.close();
currWatcher = chokidar.watch(currWatchPath, {
ignoreInitial: true,
ignored: ["*.ts"]
});
currWatcher.on("all", eventName => {
files = readdirSync(currWatchPath);
console.log(eventName, currWatchPath, files);
readFiles(files, currWatchPath);
});
readFiles(files, path);
}
async function readFiles(files, path) {
//* Send files to extension
socket.emit("localPresence", {
files: await Promise.all(
files.map(f => {
if (extname(f) === ".json")
return {
file: f,
contents: JSON.parse(readFileSync(`${path}/${f}`).toString())
};
else if (extname(f) === ".js")
return {
file: f,
contents: readFileSync(`${path}/${f}`).toString()
};
else return;
})
)
});
}
export async function openFileDialog() {
//* Open file dialog
//* If user cancels
//* Unwatch all still watched files
//* Watch directory
app.focus();
let oDialog = await dialog.showOpenDialog(null, {
title: "Select Presence Folder",
message:
"Please select the folder that contains the presence you want to load.\n(metadata.json, presence.js, iframe.js)",
buttonLabel: "Load Presence",
properties: ["openDirectory"]
});
if (oDialog.canceled) {
//* Show debug
//* return
info("Presence load canceled.");
return;
}
info(`Watching ${oDialog.filePaths[0]}`);
if (presenceDevWatchedFiles.length > 0)
await Promise.all(
presenceDevWatchedFiles.map(f => unwatchFile(currWatchPath + f))
);
watchDir(oDialog.filePaths[0]);
}

View File

@@ -1,33 +0,0 @@
import ElectronStore from "electron-store";
import { update as updateAutoLaunch } from "./launchManager";
import { platform } from "os";
import { info } from "../util/debug";
//* Import custom types
import ExtensionSettings from "../../@types/PreMiD/ExtensionSettings";
import { trayManager } from "..";
//* Export and set default settings
export let settings = new ElectronStore({
defaults: {
autoLaunch: true
}
});
/**
* Update settings of app
* @param extensionSettings Settings from extension
*/
export function update(extensionSettings: ExtensionSettings) {
//* Show debug
//* remove title if disabled
//* Update autolaunch if updated
//* Save Settings
info("Updated settings");
if (!extensionSettings.titleMenubar && platform() === "darwin")
trayManager.tray.setTitle("");
if (settings.get("autoLaunch") != extensionSettings.autoLaunch) {
settings.set("autoLaunch", extensionSettings.autoLaunch);
updateAutoLaunch();
}
}

View File

@@ -1,104 +0,0 @@
import { app, dialog } from "electron";
import { createServer, Server } from "http";
import socketIo from "socket.io";
import { trayManager } from "../";
import { error, success } from "../util/debug";
import {
clearActivity,
getDiscordUser,
rpcClients,
setActivity
} from "./discordManager";
import { openFileDialog } from "./presenceDevManager";
import { update as updateSettings } from "./settingsManager";
export let io: socketIo.Server;
export let socket: socketIo.Socket;
export let server: Server;
export let connected: boolean = false;
export function init() {
return new Promise<void>(resolve => {
//* Create server
//* create SocketIo server, don't server client
//* Try to listen to port 3020
//* If that fails/some other error happens run socketError
//* If someone connects to socket socketConnection
server = createServer();
io = new socketIo.Server(server, {
serveClient: false,
allowEIO3: true,
allowRequest: (req, callback) => {
if (req.headers.origin === undefined) return callback(null, true);
//* 2.5.0+ only allows extensions to connect
if (
req.headers.origin.startsWith("chrome-extension://") ||
req.headers.origin.startsWith("moz-extension://")
)
return callback(null, true);
callback("Origin not allowed", false);
}
});
server.listen(3020, () => {
//* Resolve promise
//* Debug info
resolve();
success("Opened socket");
});
server.on("error", socketError);
io.on("connection", socketConnection);
});
}
function socketConnection(cSocket: socketIo.Socket) {
//* Show debug
//* Set exported socket letiable to current socket
//* Handle setActivity event
//* Handle clearActivity event
//* Handle settingsUpdate
//* Handle presenceDev
//* Handle version request
//* Once socket user disconnects run cleanup
success("Socket connection");
socket = cSocket;
getDiscordUser()
.then(user => socket.emit("discordUser", user))
.catch(_ => socket.emit("discordUser", null));
socket.on("setActivity", setActivity);
socket.on("clearActivity", clearActivity);
socket.on("settingUpdate", updateSettings);
socket.on("selectLocalPresence", openFileDialog);
socket.on("getVersion", () =>
socket.emit("receiveVersion", app.getVersion().replace(/[\D]/g, ""))
);
socket.once("disconnect", () => {
connected = false;
trayManager.update();
//* Show debug
//* Destroy all open RPC connections
error("Socket disconnection.");
rpcClients.forEach(c => c.destroy());
});
connected = true;
trayManager.update();
}
//* Runs on socket errors
function socketError(e: any) {
//* Show debug
//* If port in use
error(e.message);
if (e.code === "EADDRINUSE") {
//* Focus app
//* Show error dialog
//* Exit app afterwards
app.focus();
dialog.showErrorBox(
"Oh noes! Port error...",
`${app.name} could not bind to port ${e.port}.\nIs ${app.name} running already?`
);
app.quit();
}
}

View File

@@ -1,85 +0,0 @@
import { app, Menu, shell, Tray } from "electron";
import { platform } from "os";
import { join } from "path";
import { trayManager } from "../";
import { checkForUpdate, update, updateAvailable } from "../util/updateChecker";
import { connected } from "./socketManager";
let trayIcon;
switch (platform()) {
case "darwin":
trayIcon = join(__dirname, "../assets/tray/IconTemplate.png");
break;
case "win32":
trayIcon = join(__dirname, "../assets/tray/Icon.ico");
break;
default:
trayIcon = join(__dirname, "../assets/tray/Icon@4x.png");
break;
}
export class TrayManager {
tray: Tray;
constructor() {
this.tray = new Tray(trayIcon);
this.tray.setToolTip(app.name);
this.tray.on("right-click", () => this.update());
}
update() {
this.tray.setContextMenu(
Menu.buildFromTemplate([
{
icon:
platform() === "darwin"
? join(__dirname, "../assets/tray/IconTemplate.png")
: join(__dirname, "../assets/tray/Icon@4x.png"),
label: `${app.name} v${app.getVersion()}`,
enabled: false
},
{
id: "connectInfo",
label: `Extension - ${connected ? "Connected" : "Not connected"}`,
enabled: false
},
{
type: "separator"
},
{
label: "Presence Store",
click: () => shell.openExternal("https://premid.app/store")
},
{
type: "separator"
},
{
label: `Update ${app.name}!`,
visible: updateAvailable,
click: () => update()
},
{
label: "Check for Updates",
click: () => checkForUpdate(false, true),
visible: !updateAvailable
},
{
label: "Contributors",
click: () => shell.openExternal("https://premid.app/contributors")
},
{
type: "separator"
},
{
label: `Quit ${app.name}`,
role: "quit"
}
])
);
}
}
app.once("quit", () => trayManager?.tray.destroy());

View File

@@ -1,32 +0,0 @@
import { app } from "electron";
if (!app.isPackaged) var chalk = require("chalk");
/**
* Show info message in console
* */
export function info(message: string) {
//* Return if app packaged
//* Show debug
if (app.isPackaged) return;
console.log(`${chalk.bgBlue(chalk.white(" INFO "))} ${message}`);
}
/**
* Show success message in console
* */
export function success(message: string) {
//* Return if app packaged
//* Show debug
if (app.isPackaged) return;
console.log(`${chalk.bgGreen(" SUCCESS ")} ${message}`);
}
/**
* Show error message in console
* */
export function error(message: string) {
//* Return if app packaged
//* Show debug
if (app.isPackaged) return;
console.log(`${chalk.bgRed(" ERROR ")} ${message}`);
}

View File

@@ -1,114 +0,0 @@
import { exec } from "child_process";
import { resolve } from "path";
import { error, info } from "./debug";
import { app, dialog, shell } from "electron";
import { platform } from "os";
import axios from "axios";
import { Notification } from "electron";
import { trayManager } from "..";
import { createWriteStream, existsSync, unlinkSync } from "fs";
export let updateAvailable = false;
let initialNotification = true;
export async function checkForUpdate(autoUpdate = false, manual = false) {
//* Skip Update checker if unsupported OS / not packaged
if (!app.isPackaged || !["darwin", "win32"].includes(platform())) {
//* Show debug
//* return
info("Skipping UpdateChecker");
return;
}
axios
.get("https://api.premid.app/v2/versions")
.then(({ data }) => {
if (!data?.app) return;
const latestAppVersion = data.app;
if (app.getVersion() >= latestAppVersion) {
if (manual)
dialog.showMessageBox(null, {
message: "There are currently no updates available.",
type: "info"
});
return;
}
if (autoUpdate) {
updateTray();
update();
return;
}
if (initialNotification) {
const updateNotification = new Notification({
title: "Update available!",
body: "A new version of PreMiD is available! Click here to update."
});
updateNotification.once("click", update);
updateNotification.show();
updateTray();
initialNotification = false;
}
})
.catch(err => {
error(err.message);
});
}
export async function update() {
if (!["win32", "darwin"].includes(platform())) return;
const updaterPath = resolve(
app.getPath("temp"),
`PreMiD-Updater${
platform() === "win32" ? ".exe" : ".app/Contents/MacOS/installbuilder.sh"
}`
);
if (existsSync(updaterPath)) unlinkSync(updaterPath);
if (existsSync(resolve(app.getPath("temp"), "PreMiD-release.zip")))
unlinkSync(resolve(app.getPath("temp"), "PreMiD-release.zip"));
const response = await axios({
url: `http://dl.premid.app/upgrader${
platform() === "win32" ? ".exe" : ".app"
}`,
method: "GET",
responseType: "stream"
});
const writer = createWriteStream(updaterPath);
response.data.pipe(writer);
new Promise((resolve, reject) => {
writer.on("finish", resolve);
writer.on("error", reject);
})
.then(() => exec(updaterPath, errorHandler))
.catch(errorHandler);
}
function errorHandler(err: Error) {
dialog
.showMessageBox({
title: `Error while updating ${app.name}`,
message: `If this error persists reinstall the app.`,
detail: err.message,
buttons: ["Okay", "Reinstall Application"],
type: "error"
})
.then(value => {
if (value.response === 1)
shell.openExternal("https://premid.app/downloads");
});
updateTray();
}
function updateTray() {
updateAvailable = true;
trayManager.update();
}

8
tsconfig.app.json Normal file
View File

@@ -0,0 +1,8 @@
{
"files": [],
"references": [
{
"path": "./apps/pd/tsconfig.app.json"
}
]
}

34
tsconfig.base.json Normal file
View File

@@ -0,0 +1,34 @@
// Credit: https://www.totaltypescript.com/tsconfig-cheat-sheet
{
"exclude": ["**/*/node_modules", "**/*/dist"],
"compilerOptions": {
/* Base Options: */
"esModuleInterop": true,
"skipLibCheck": true,
"target": "es2022",
"allowJs": true,
"resolveJsonModule": true,
"moduleDetection": "force",
"isolatedModules": true,
/* Strictness */
"strict": true,
"noUncheckedIndexedAccess": true,
/* If transpiling with TypeScript: */
"moduleResolution": "NodeNext",
"module": "NodeNext",
"outDir": "dist",
"sourceMap": true,
/* AND if you're building for a library: */
"declaration": true,
/* AND if you're building for a library in a monorepo: */
"composite": true,
"declarationMap": true,
/* If your code doesn't run in the DOM: */
"lib": ["es2022"]
}
}

View File

@@ -1,22 +1,7 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"target": "es2018",
"moduleResolution": "node",
"inlineSourceMap": true,
"outDir": "dist/app",
"removeComments": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noUnusedParameters": true,
"noUnusedLocals": true
},
"include": ["src/**/*"],
"exclude": [
"**/node_modules/",
"pkg.ts",
"util/**/*",
"installer_assets/builder.ts",
".github/uploadFile.ts"
]
"noEmit": true,
"types": ["vitest"]
}
}

View File

@@ -1,7 +0,0 @@
import { readFileSync, writeFileSync } from "fs";
let file = readFileSync("installer_assets/PreMiD-Upgrade.xml", "utf-8");
file = file.replace("VERSION", require("../package.json").version);
writeFileSync("installer_assets/PreMiD-Upgrade.xml", file);

View File

@@ -1,18 +0,0 @@
import * as Client from "ssh2-sftp-client";
let sftp = new Client();
sftp
.connect({
host: process.env.SSHHOST,
username: process.env.SSH_USERNAME,
privateKey: process.env.SSH_KEY
})
.then(async () => {
sftp
.fastPut(process.argv[2], process.argv[3])
.then(() => {
sftp.end();
})
.catch(console.error);
});

View File

@@ -1,16 +0,0 @@
import * as archiver from "archiver";
import { Extract } from "unzipper";
import { createWriteStream, createReadStream } from "fs";
import { basename } from "path";
if (process.argv.includes("--zip")) {
const archive = archiver("zip"),
output = createWriteStream(process.argv[3]);
archive.pipe(output);
archive.directory(process.argv[2], basename(process.argv[3], ".zip"));
archive.finalize();
} else {
createReadStream(process.argv[2]).pipe(Extract({ path: process.cwd() }));
}

16
vitest.config.ts Normal file
View File

@@ -0,0 +1,16 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
all: true,
enabled: true,
reportOnFailure: true,
thresholds: {
100: true,
},
},
isolate: false,
passWithNoTests: true,
},
});

3
vitest.workspace.ts Normal file
View File

@@ -0,0 +1,3 @@
import { defineWorkspace } from "vitest/config";
export default defineWorkspace(["apps/*"]);

3166
yarn.lock

File diff suppressed because it is too large Load Diff