Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d9c4a4a428 | ||
|
77dcea14cc | ||
|
dcbbfde4a5 | ||
|
685c2dc210 | ||
|
3d67f6d5ae |
52
.github/workflows/build-and-release.yml
vendored
52
.github/workflows/build-and-release.yml
vendored
@@ -31,6 +31,9 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
|
- name: Build (ia32)
|
||||||
|
run: npm run build -- --arch ia32
|
||||||
|
|
||||||
- name: Build (x64)
|
- name: Build (x64)
|
||||||
run: npm run build -- --arch x64
|
run: npm run build -- --arch x64
|
||||||
|
|
||||||
@@ -39,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download appimagelint and its deps
|
- name: Download appimagelint and its deps
|
||||||
run: |
|
run: |
|
||||||
sudo apt install fuse -y
|
sudo apt update && sudo apt install fuse -y
|
||||||
wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage
|
wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage
|
||||||
chmod +x appimagelint-x86_64.AppImage
|
chmod +x appimagelint-x86_64.AppImage
|
||||||
|
|
||||||
@@ -48,7 +51,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate checksum
|
- name: Generate checksum
|
||||||
run: |
|
run: |
|
||||||
sha256sum dist/*.AppImage > sha256sum.txt
|
sha256sum dist/*.AppImage > dist/sha256sum.txt
|
||||||
|
sha256sum dist/*.deb >> dist/sha256sum.txt
|
||||||
|
sha256sum dist/*.zip >> dist/sha256sum.txt
|
||||||
|
|
||||||
- name: Upload Linux Artifacts
|
- name: Upload Linux Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -57,14 +62,15 @@ jobs:
|
|||||||
name: linux-artifacts
|
name: linux-artifacts
|
||||||
path: |
|
path: |
|
||||||
dist/*.AppImage
|
dist/*.AppImage
|
||||||
|
dist/*.deb
|
||||||
|
dist/*.zip
|
||||||
dist/latest*.yml
|
dist/latest*.yml
|
||||||
sha256sum.txt
|
dist/sha256sum.txt
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
name: Build bsky-desktop (Windows)
|
name: Build bsky-desktop (Windows)
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
env:
|
env:
|
||||||
ext: "exe"
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -78,16 +84,22 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
|
- name: Build (ia32)
|
||||||
|
run: npm run build -- --arch ia32
|
||||||
|
|
||||||
- name: Build (x64)
|
- name: Build (x64)
|
||||||
run: npm run build -- --arch x64
|
run: npm run build -- --arch x64
|
||||||
|
|
||||||
- name: Build (arm64)
|
- name: Build (arm64)
|
||||||
run: npm run build -- --arch arm64
|
run: npm run build -- --arch arm64
|
||||||
|
|
||||||
- name: Generate checksum
|
- name: Generate checksum
|
||||||
run: |
|
run: |
|
||||||
sha256sum dist/*.exe > sha256sum.txt
|
sha256sum dist/*.exe > dist/sha256sum.txt
|
||||||
|
sha256sum dist/*.msi >> dist/sha256sum.txt
|
||||||
|
sha256sum dist/*.appx >> dist/sha256sum.txt
|
||||||
|
sha256sum dist/*.zip >> dist/sha256sum.txt
|
||||||
|
|
||||||
- name: Upload Windows Artifacts
|
- name: Upload Windows Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -96,14 +108,16 @@ jobs:
|
|||||||
name: windows-artifacts
|
name: windows-artifacts
|
||||||
path: |
|
path: |
|
||||||
dist/*.exe
|
dist/*.exe
|
||||||
|
dist/*.msi
|
||||||
|
dist/*.appx
|
||||||
|
dist/*.zip
|
||||||
dist/latest*.yml
|
dist/latest*.yml
|
||||||
sha256sum.txt
|
dist/sha256sum.txt
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
name: Build bsky-desktop (macOS)
|
name: Build bsky-desktop (macOS)
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
env:
|
env:
|
||||||
ext: "dmg"
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -126,8 +140,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate checksum
|
- name: Generate checksum
|
||||||
run: |
|
run: |
|
||||||
shasum -a 256 dist/*.dmg > sha256sum.txt
|
shasum -a 256 dist/*.dmg > dist/sha256sum.txt
|
||||||
# shasum -a 256 dist/*.pkg >> sha256sum.txt
|
shasum -a 256 dist/*.pkg >> dist/sha256sum.txt
|
||||||
|
shasum -a 256 dist/*.zip >> dist/sha256sum.txt
|
||||||
|
|
||||||
- name: Upload macOS Artifacts
|
- name: Upload macOS Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -136,8 +151,10 @@ jobs:
|
|||||||
name: macos-artifacts
|
name: macos-artifacts
|
||||||
path: |
|
path: |
|
||||||
dist/*.dmg
|
dist/*.dmg
|
||||||
|
dist/*.pkg
|
||||||
|
dist/*.zip
|
||||||
dist/latest*.yml
|
dist/latest*.yml
|
||||||
sha256sum.txt
|
dist/sha256sum.txt
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Create Release
|
name: Create Release
|
||||||
@@ -190,8 +207,15 @@ jobs:
|
|||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
dist/linux/*.AppImage
|
dist/linux/*.AppImage
|
||||||
|
dist/linux/*.deb
|
||||||
|
dist/linux/*.zip
|
||||||
dist/windows/*.exe
|
dist/windows/*.exe
|
||||||
|
dist/windows/*.msi
|
||||||
|
dist/windows/*.appx
|
||||||
|
dist/windows/*.zip
|
||||||
dist/macos/*.dmg
|
dist/macos/*.dmg
|
||||||
|
dist/macos/*.pkg
|
||||||
|
dist/macos/*.zip
|
||||||
sha256sums.txt
|
sha256sums.txt
|
||||||
|
|
||||||
aur:
|
aur:
|
||||||
@@ -224,14 +248,14 @@ jobs:
|
|||||||
- name: Extract checksum from sha256sum.txt and change build version
|
- name: Extract checksum from sha256sum.txt and change build version
|
||||||
run: |
|
run: |
|
||||||
new_checksum=$(awk 'NR==1 { print $1 }' ./dist/linux/sha256sum.txt)
|
new_checksum=$(awk 'NR==1 { print $1 }' ./dist/linux/sha256sum.txt)
|
||||||
sed -i "s|sha256sums=('SKIP' 'SKIP')|sha256sums=('$new_checksum' 'SKIP')|" ./build/PKGBUILD
|
sed -i "s|sha256sums=('SKIP' 'SKIP')|sha256sums=('$new_checksum' 'SKIP')|" ./build/arch-pkg/PKGBUILD
|
||||||
sed -i "s/^pkgver=.*$/pkgver=${{ steps.version.outputs.version }}/" ./build/PKGBUILD
|
sed -i "s/^pkgver=.*$/pkgver=${{ steps.version.outputs.version }}/" ./build/arch-pkg/PKGBUILD
|
||||||
|
|
||||||
- name: Publish AUR package
|
- name: Publish AUR package
|
||||||
uses: KSXGitHub/github-actions-deploy-aur@v3.0.1
|
uses: KSXGitHub/github-actions-deploy-aur@v3.0.1
|
||||||
with:
|
with:
|
||||||
pkgname: bskydesktop
|
pkgname: bskydesktop
|
||||||
pkgbuild: ./build/PKGBUILD
|
pkgbuild: ./build/arch-pkg/PKGBUILD
|
||||||
commit_username: ${{ secrets.AUR_USERNAME }}
|
commit_username: ${{ secrets.AUR_USERNAME }}
|
||||||
commit_email: ${{ secrets.AUR_EMAIL }}
|
commit_email: ${{ secrets.AUR_EMAIL }}
|
||||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||||
|
57
README.md
57
README.md
@@ -1,3 +1,58 @@
|
|||||||
# Bsky Desktop
|
# Bsky Desktop
|
||||||
|
|
||||||
Bsky Desktop is an application for Bsky built using Electron. It allows users to manage their Bsky account and feeds from the app instead of the web.
|
Bsky Desktop is an Electron-based application for Bsky that allows users to manage their accounts and feeds directly from the app, rather than through the web interface.
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
- Support for user styles (work in progress; currently only LESS preprocessor is supported)
|
||||||
|
- Compatibility with both Manifest V2 and V3 Chrome extensions, though only a limited set of Chrome extension APIs are supported. For more information, visit: [Electron Extensions API Documentation](https://www.electronjs.org/docs/latest/api/extensions#supported-extensions-apis)
|
||||||
|
|
||||||
|
### Working on:
|
||||||
|
- Auto updates (for all platforms)
|
||||||
|
|
||||||
|
### Build and release status:
|
||||||
|
[](https://github.com/oxmc/bsky-desktop/actions/workflows/build-and-release.yml)
|
||||||
|
|
||||||
|
[](https://repology.org/project/bskydesktop/versions)
|
||||||
|
|
||||||
|
#### Windows install options:
|
||||||
|
- Zip (x64, arm64, ia32)
|
||||||
|
- Setup (exe, msi, appx) (x64, arm64, ia32)
|
||||||
|
|
||||||
|
#### Mac install options:
|
||||||
|
- Zip (x64, arm64)
|
||||||
|
- Dmg (x64, arm64)
|
||||||
|
- Pkg (x64, arm64)
|
||||||
|
|
||||||
|
#### Linux install options:
|
||||||
|
- Zip (x64, arm64, ia32)
|
||||||
|
- AppImage (x64, arm64, ia32)
|
||||||
|
- Deb (x64, arm64, ia32)
|
||||||
|
|
||||||
|
### Build Instructions for Bsky Desktop
|
||||||
|
|
||||||
|
To build and run Bsky Desktop locally, follow these steps:
|
||||||
|
|
||||||
|
1. **Clone the repository:**
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/oxmc/bsky-desktop.git
|
||||||
|
cd bsky-desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install dependencies:**
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
**(Optional) Run the application locally:**
|
||||||
|
If you want to test the application locally before building it, use the following command:
|
||||||
|
```sh
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
This step is **not required for building** but is useful if you want to see the app in action during development.
|
||||||
|
|
||||||
|
3. **Build the application:**
|
||||||
|
To compile the application, run:
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
This will generate the necessary files for the app.
|
116
build-app.js
116
build-app.js
@@ -1,116 +0,0 @@
|
|||||||
const { spawn } = require('child_process');
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs/promises');
|
|
||||||
const process = require('process');
|
|
||||||
const packageJson = require('./package.json');
|
|
||||||
|
|
||||||
// Electron builder:
|
|
||||||
const electronBuilderPath = path.join('node_modules', 'electron-builder', 'cli.js');
|
|
||||||
|
|
||||||
// Parse command-line arguments:
|
|
||||||
const supportedPlatforms = ['win', 'mac', 'linux', 'mwl'];
|
|
||||||
const supportedArchitectures = ['x64', 'armv7l', 'arm64', 'ia32', 'universal'];
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
const carch = args.includes('--arch') ? args[args.indexOf('--arch') + 1] : null;
|
|
||||||
const cplatform = args.includes('--platform') ? args[args.indexOf('--platform') + 1] : null;
|
|
||||||
const pack = args.includes('--pack') || null;
|
|
||||||
|
|
||||||
let platform, arch, build_args;
|
|
||||||
|
|
||||||
//console.log(supportedPlatforms, supportedArchitectures, cplatform, carch);
|
|
||||||
|
|
||||||
// Platform Name:
|
|
||||||
if (cplatform == null) {
|
|
||||||
switch (process.platform) {
|
|
||||||
case "win32":
|
|
||||||
platform = "win";
|
|
||||||
break;
|
|
||||||
case "darwin":
|
|
||||||
platform = "mac";
|
|
||||||
break;
|
|
||||||
case "linux":
|
|
||||||
platform = "linux";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
platform = "mwl"; // Build for all
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!supportedPlatforms.includes(cplatform)) {
|
|
||||||
console.error(`Invalid platform specified. Supported platforms: ${supportedPlatforms.join(', ')}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
platform = cplatform;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Platform Arch:
|
|
||||||
if (carch == null) {
|
|
||||||
arch = process.arch === "arm" ? "armv7l" : process.arch;
|
|
||||||
} else {
|
|
||||||
arch = carch;
|
|
||||||
if (!supportedArchitectures.includes(arch)) {
|
|
||||||
console.error(`Invalid arch specified. Supported architectures: ${supportedArchitectures.join(', ')}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate artifact name:
|
|
||||||
const artifactname = `${packageJson.build.productName}-${packageJson.version}-${platform}-${arch}`;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
// Additional build arguments: (started wiht --eb-nmehere)
|
|
||||||
const additionalArgs = args.filter((arg, index) => arg.startsWith('--eb-') && index % 2 !== 0);
|
|
||||||
|
|
||||||
// CLI Args:
|
|
||||||
build_args = [
|
|
||||||
electronBuilderPath,
|
|
||||||
`--${platform}`,
|
|
||||||
`--${arch}`,
|
|
||||||
`-c.artifactName="${artifactname}.\${ext}"`,
|
|
||||||
];
|
|
||||||
|
|
||||||
// If additional args are present:
|
|
||||||
if (additionalArgs.length > 0) {
|
|
||||||
build_args.push(...additionalArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If pack is true:
|
|
||||||
if (pack) {
|
|
||||||
build_args.push(`--dir`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make CLI:
|
|
||||||
const cli = `node ${build_args.join(' ')}`;
|
|
||||||
|
|
||||||
console.info(`CLI: ${cli}`);
|
|
||||||
console.info(`Building ${artifactname}`);
|
|
||||||
const process = spawn(cli, { shell: true });
|
|
||||||
|
|
||||||
// Log stdout as it comes
|
|
||||||
process.stdout.on('data', (data) => {
|
|
||||||
console.log(data.toString().trim());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Log stderr as it comes
|
|
||||||
process.stderr.on('data', (data) => {
|
|
||||||
console.error(data.toString().trim());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle process exit
|
|
||||||
process.on('close', (code) => {
|
|
||||||
if (code === 0) {
|
|
||||||
console.info(`Build completed successfully!`);
|
|
||||||
} else {
|
|
||||||
console.error(`Process exited with code ${code}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('error', (error) => {
|
|
||||||
console.error(`Failed to start process: ${error.message}`);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('An error occurred:', error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
})();
|
|
@@ -16,14 +16,17 @@ icon_name="bsky-desktop.png"
|
|||||||
|
|
||||||
prepare() {
|
prepare() {
|
||||||
latest_tag=$(curl -s "https://api.github.com/repos/oxmc/bsky-desktop/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")')
|
latest_tag=$(curl -s "https://api.github.com/repos/oxmc/bsky-desktop/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")')
|
||||||
|
latest_sha256=$(curl -Ls "https://github.com/oxmc/bsky-desktop/releases/download/$latest_tag/sha256sums.txt" | grep "AppImage")
|
||||||
echo "Latest release tag: $latest_tag"
|
echo "Latest release tag: $latest_tag"
|
||||||
|
|
||||||
case "$CARCH" in
|
case "$CARCH" in
|
||||||
x86_64)
|
x86_64)
|
||||||
appimage_name="bskyDesktop-${latest_tag:1}-linux-x64.AppImage"
|
appimage_name="bskyDesktop-${latest_tag:1}-linux-x64.AppImage"
|
||||||
|
sha256sum=$(echo "$latest_sha256" | grep "x64" | cut -d' ' -f1)
|
||||||
;;
|
;;
|
||||||
aarch64)
|
aarch64)
|
||||||
appimage_name="bskyDesktop-${latest_tag:1}-linux-arm64.AppImage"
|
appimage_name="bskyDesktop-${latest_tag:1}-linux-arm64.AppImage"
|
||||||
|
sha256sum=$(echo "$latest_sha256" | grep "arm64" | cut -d' ' -f1)
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unsupported architecture: $CARCH"
|
echo "Unsupported architecture: $CARCH"
|
||||||
@@ -36,7 +39,7 @@ prepare() {
|
|||||||
"$icon_url"
|
"$icon_url"
|
||||||
)
|
)
|
||||||
echo "AppImage source: ${source[0]}"
|
echo "AppImage source: ${source[0]}"
|
||||||
sha256sums=('SKIP' 'SKIP')
|
sha256sums=("$sha256sum" 'SKIP')
|
||||||
curl -L "${source[0]}" -o "$srcdir/bskyDesktop.appimage"
|
curl -L "${source[0]}" -o "$srcdir/bskyDesktop.appimage"
|
||||||
curl -L "${source[1]}" -o "$srcdir/$icon_name"
|
curl -L "${source[1]}" -o "$srcdir/$icon_name"
|
||||||
}
|
}
|
104
build/build-app.js
Normal file
104
build/build-app.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const process = require('process');
|
||||||
|
const builder = require('electron-builder');
|
||||||
|
const { Platform, Arch } = builder;
|
||||||
|
|
||||||
|
// Parse command-line arguments
|
||||||
|
const supportedPlatforms = ['win', 'mac', 'linux'];
|
||||||
|
const supportedArchitectures = ['x64', 'armv7l', 'arm64', 'ia32', 'universal'];
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const carch = args.includes('--arch') ? args[args.indexOf('--arch') + 1] : null;
|
||||||
|
const cplatform = args.includes('--platform') ? args[args.indexOf('--platform') + 1] : null;
|
||||||
|
const pack = args.includes('--pack'); // Keep track of --pack as a boolean flag
|
||||||
|
|
||||||
|
// Determine platform
|
||||||
|
let platform;
|
||||||
|
if (!cplatform) {
|
||||||
|
platform = process.platform === 'win32' ? 'win' :
|
||||||
|
process.platform === 'darwin' ? 'mac' :
|
||||||
|
process.platform === 'linux' ? 'linux' : null;
|
||||||
|
} else if (!supportedPlatforms.includes(cplatform)) {
|
||||||
|
console.error(`Invalid platform specified. Supported platforms: ${supportedPlatforms.join(', ')}`);
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
platform = cplatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine architecture
|
||||||
|
let arch = carch || (process.arch === 'arm' ? 'armv7l' : process.arch);
|
||||||
|
if (!supportedArchitectures.includes(arch)) {
|
||||||
|
console.error(`Invalid architecture specified. Supported architectures: ${supportedArchitectures.join(', ')}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map platform and architecture to electron-builder enums
|
||||||
|
const platformEnum = {
|
||||||
|
win: Platform.WINDOWS,
|
||||||
|
mac: Platform.MAC,
|
||||||
|
linux: Platform.LINUX
|
||||||
|
}[platform];
|
||||||
|
|
||||||
|
const archEnum = {
|
||||||
|
x64: Arch.x64,
|
||||||
|
armv7l: Arch.armv7l,
|
||||||
|
arm64: Arch.arm64,
|
||||||
|
ia32: Arch.ia32,
|
||||||
|
universal: Arch.universal
|
||||||
|
}[arch];
|
||||||
|
|
||||||
|
// Additional build arguments: (starting with --eb-)
|
||||||
|
const buildArgs = args.filter((arg) => arg.startsWith('--eb-'));
|
||||||
|
|
||||||
|
// If additional args are present, add them to the buildArgs array
|
||||||
|
if (buildArgs.length > 0) {
|
||||||
|
console.log('Additional build arguments:', buildArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If pack is true, add --dir to buildArgs
|
||||||
|
if (pack) {
|
||||||
|
buildArgs.push('--dir');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read build-config.json
|
||||||
|
const buildConfigPath = path.join(__dirname, 'build-config.json');
|
||||||
|
let buildConfig;
|
||||||
|
try {
|
||||||
|
buildConfig = require(buildConfigPath);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to read build-config.json: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate artifact name
|
||||||
|
const packageJson = require('../package.json');
|
||||||
|
const artifactName = `${buildConfig.productName}-${packageJson.version}-${platform}-${arch}.\${ext}`;
|
||||||
|
|
||||||
|
// Electron Builder configuration
|
||||||
|
/**
|
||||||
|
* @type {import('electron-builder').Configuration}
|
||||||
|
*/
|
||||||
|
const options = {
|
||||||
|
artifactName,
|
||||||
|
nsis: {
|
||||||
|
deleteAppDataOnUninstall: true,
|
||||||
|
oneClick: true,
|
||||||
|
perMachine: false
|
||||||
|
},
|
||||||
|
dmg: {
|
||||||
|
contents: [
|
||||||
|
{ type: 'file', x: 255, y: 85 },
|
||||||
|
{ type: 'link', path: '/Applications', x: 253, y: 325 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
...buildConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build process
|
||||||
|
builder.build({
|
||||||
|
targets: platformEnum.createTarget(null, archEnum),
|
||||||
|
config: options
|
||||||
|
}).then(result => {
|
||||||
|
console.log("Build completed:", JSON.stringify(result, null, 2));
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("Build failed:", error);
|
||||||
|
});
|
55
build/build-config.json
Normal file
55
build/build-config.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"appId": "com.oxmc.bskyDesktop",
|
||||||
|
"productName": "bskyDesktop",
|
||||||
|
"asarUnpack": [
|
||||||
|
"./node_modules/node-notifier/**/*"
|
||||||
|
],
|
||||||
|
"removePackageScripts": true,
|
||||||
|
"mac": {
|
||||||
|
"target": [
|
||||||
|
"dmg",
|
||||||
|
"pkg",
|
||||||
|
"zip"
|
||||||
|
],
|
||||||
|
"icon": "src/ui/images/mac.icns",
|
||||||
|
"category": "Network"
|
||||||
|
},
|
||||||
|
"pkg": {
|
||||||
|
"installLocation": "/Applications",
|
||||||
|
"allowAnywhere": true,
|
||||||
|
"allowCurrentUserHome": true,
|
||||||
|
"allowRootDirectory": true,
|
||||||
|
"isVersionChecked": true,
|
||||||
|
"isRelocatable": false,
|
||||||
|
"overwriteAction": "upgrade"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"target": [
|
||||||
|
"appimage",
|
||||||
|
"deb",
|
||||||
|
"zip"
|
||||||
|
],
|
||||||
|
"icon": "src/ui/images/icons",
|
||||||
|
"category": "Network"
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": [
|
||||||
|
"nsis",
|
||||||
|
"appx",
|
||||||
|
"msi",
|
||||||
|
"zip"
|
||||||
|
],
|
||||||
|
"icon": "src/ui/images/logo.ico"
|
||||||
|
},
|
||||||
|
"appx": {
|
||||||
|
"identityName": "BskyDesktop"
|
||||||
|
},
|
||||||
|
"protocols": [
|
||||||
|
{
|
||||||
|
"name": "bsky",
|
||||||
|
"schemes": [
|
||||||
|
"bsky"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
295
package-lock.json
generated
295
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "bsky-desktop",
|
"name": "bsky-desktop",
|
||||||
"version": "1.0.6",
|
"version": "1.1.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bsky-desktop",
|
"name": "bsky-desktop",
|
||||||
"version": "1.0.6",
|
"version": "1.1.0",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.2.17",
|
"@electron/asar": "^3.2.17",
|
||||||
@@ -14,9 +14,11 @@
|
|||||||
"adm-zip": "^0.5.12",
|
"adm-zip": "^0.5.12",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
|
"less": "^4.2.1",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
"node-notifier": "^10.0.0",
|
"node-notifier": "^10.0.0",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
|
"usercss-meta": "^0.12.0",
|
||||||
"v8-compile-cache": "^2.3.0"
|
"v8-compile-cache": "^2.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1324,6 +1326,18 @@
|
|||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/copy-anything": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-what": "^3.14.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/mesqueeb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/core-util-is": {
|
"node_modules/core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
@@ -1711,6 +1725,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/errno": {
|
||||||
|
"version": "0.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
|
||||||
|
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"prr": "~1.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"errno": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
@@ -2265,7 +2292,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
@@ -2295,6 +2322,19 @@
|
|||||||
],
|
],
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/image-size": {
|
||||||
|
"version": "0.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||||
|
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"bin": {
|
||||||
|
"image-size": "bin/image-size.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/inflight": {
|
"node_modules/inflight": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@@ -2343,6 +2383,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-what": {
|
||||||
|
"version": "3.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
|
||||||
|
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-wsl": {
|
"node_modules/is-wsl": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -2522,6 +2568,45 @@
|
|||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/less": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/less/-/less-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"copy-anything": "^2.0.1",
|
||||||
|
"parse-node-version": "^1.0.1",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"lessc": "bin/lessc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"errno": "^0.1.1",
|
||||||
|
"graceful-fs": "^4.1.2",
|
||||||
|
"image-size": "~0.5.0",
|
||||||
|
"make-dir": "^2.1.0",
|
||||||
|
"mime": "^1.4.1",
|
||||||
|
"needle": "^3.1.0",
|
||||||
|
"source-map": "~0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/less/node_modules/mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
@@ -2604,6 +2689,30 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/make-dir": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pify": "^4.0.1",
|
||||||
|
"semver": "^5.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/make-dir/node_modules/semver": {
|
||||||
|
"version": "5.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||||
|
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"optional": true,
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/matcher": {
|
"node_modules/matcher": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||||
@@ -2723,6 +2832,23 @@
|
|||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/needle": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"sax": "^1.2.4"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"needle": "bin/needle"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.4.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-addon-api": {
|
"node_modules/node-addon-api": {
|
||||||
"version": "1.7.2",
|
"version": "1.7.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2797,6 +2923,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0"
|
"license": "BlueOak-1.0.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/parse-node-version": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-is-absolute": {
|
"node_modules/path-is-absolute": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -2842,6 +2977,16 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pify": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/plist": {
|
"node_modules/plist": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
||||||
@@ -2894,6 +3039,13 @@
|
|||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/prr": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/pump": {
|
"node_modules/pump": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||||
@@ -3063,7 +3215,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/safer-buffer": {
|
"node_modules/safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/sanitize-filename": {
|
"node_modules/sanitize-filename": {
|
||||||
@@ -3080,7 +3232,7 @@
|
|||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
@@ -3189,7 +3341,7 @@
|
|||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -3466,6 +3618,12 @@
|
|||||||
"utf8-byte-length": "^1.0.1"
|
"utf8-byte-length": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/type-fest": {
|
"node_modules/type-fest": {
|
||||||
"version": "0.13.1",
|
"version": "0.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||||
@@ -3517,6 +3675,15 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/usercss-meta": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/usercss-meta/-/usercss-meta-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-zKrXCKdpeIwtVe87omxGo9URf+7mbozduMZEg79dmT4KB3XJwfIkEi/Uk0PcTwR/nZLtAK1+k7isgbGB/g6E7Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/utf8-byte-length": {
|
"node_modules/utf8-byte-length": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||||
@@ -4647,6 +4814,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"copy-anything": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
|
||||||
|
"requires": {
|
||||||
|
"is-what": "^3.14.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
@@ -4914,6 +5089,15 @@
|
|||||||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"errno": {
|
||||||
|
"version": "0.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
|
||||||
|
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"prr": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"es-define-property": {
|
"es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
@@ -5289,7 +5473,7 @@
|
|||||||
},
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
}
|
}
|
||||||
@@ -5300,6 +5484,12 @@
|
|||||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"image-size": {
|
||||||
|
"version": "0.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||||
|
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"inflight": {
|
"inflight": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -5328,6 +5518,11 @@
|
|||||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"is-what": {
|
||||||
|
"version": "3.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
|
||||||
|
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA=="
|
||||||
|
},
|
||||||
"is-wsl": {
|
"is-wsl": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"requires": {
|
"requires": {
|
||||||
@@ -5462,6 +5657,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"less": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/less/-/less-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-CasaJidTIhWmjcqv0Uj5vccMI7pJgfD9lMkKtlnTHAdJdYK/7l8pM9tumLyJ0zhbD4KJLo/YvTj+xznQd5NBhg==",
|
||||||
|
"requires": {
|
||||||
|
"copy-anything": "^2.0.1",
|
||||||
|
"errno": "^0.1.1",
|
||||||
|
"graceful-fs": "^4.1.2",
|
||||||
|
"image-size": "~0.5.0",
|
||||||
|
"make-dir": "^2.1.0",
|
||||||
|
"mime": "^1.4.1",
|
||||||
|
"needle": "^3.1.0",
|
||||||
|
"parse-node-version": "^1.0.1",
|
||||||
|
"source-map": "~0.6.0",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
@@ -5527,6 +5747,24 @@
|
|||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"make-dir": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"pify": "^4.0.1",
|
||||||
|
"semver": "^5.6.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": {
|
||||||
|
"version": "5.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||||
|
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||||
@@ -5605,6 +5843,16 @@
|
|||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2"
|
"version": "2.1.2"
|
||||||
},
|
},
|
||||||
|
"needle": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"sax": "^1.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node-addon-api": {
|
"node-addon-api": {
|
||||||
"version": "1.7.2",
|
"version": "1.7.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -5656,6 +5904,11 @@
|
|||||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"parse-node-version": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA=="
|
||||||
|
},
|
||||||
"path-is-absolute": {
|
"path-is-absolute": {
|
||||||
"version": "1.0.1"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
@@ -5686,6 +5939,12 @@
|
|||||||
"pend": {
|
"pend": {
|
||||||
"version": "1.2.0"
|
"version": "1.2.0"
|
||||||
},
|
},
|
||||||
|
"pify": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"plist": {
|
"plist": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
|
||||||
@@ -5724,6 +5983,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
},
|
},
|
||||||
|
"prr": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"pump": {
|
"pump": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||||
@@ -5851,7 +6116,7 @@
|
|||||||
},
|
},
|
||||||
"safer-buffer": {
|
"safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"sanitize-filename": {
|
"sanitize-filename": {
|
||||||
"version": "1.6.3",
|
"version": "1.6.3",
|
||||||
@@ -5866,7 +6131,7 @@
|
|||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "7.6.3",
|
"version": "7.6.3",
|
||||||
@@ -5938,7 +6203,7 @@
|
|||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"dev": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"source-map-support": {
|
"source-map-support": {
|
||||||
"version": "0.5.21",
|
"version": "0.5.21",
|
||||||
@@ -6135,6 +6400,11 @@
|
|||||||
"utf8-byte-length": "^1.0.1"
|
"utf8-byte-length": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||||
|
},
|
||||||
"type-fest": {
|
"type-fest": {
|
||||||
"version": "0.13.1",
|
"version": "0.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||||
@@ -6167,6 +6437,11 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"usercss-meta": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/usercss-meta/-/usercss-meta-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-zKrXCKdpeIwtVe87omxGo9URf+7mbozduMZEg79dmT4KB3XJwfIkEi/Uk0PcTwR/nZLtAK1+k7isgbGB/g6E7Q=="
|
||||||
|
},
|
||||||
"utf8-byte-length": {
|
"utf8-byte-length": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||||
|
43
package.json
43
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bsky-desktop",
|
"name": "bsky-desktop",
|
||||||
"version": "1.0.9",
|
"version": "1.1.1",
|
||||||
"description": "A desktop app of bsky.app",
|
"description": "A desktop app of bsky.app",
|
||||||
"main": "src/app/main.js",
|
"main": "src/app/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"pack": "electron-builder --dir",
|
"pack": "electron-builder --dir",
|
||||||
"dist": "electron-builder",
|
"dist": "electron-builder",
|
||||||
"rebuild": "electron-rebuild",
|
"rebuild": "electron-rebuild",
|
||||||
"build": "node ./build-app.js"
|
"build": "node ./build/build-app.js"
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"name": "oxmc",
|
"name": "oxmc",
|
||||||
@@ -25,46 +25,11 @@
|
|||||||
"adm-zip": "^0.5.12",
|
"adm-zip": "^0.5.12",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
|
"less": "^4.2.1",
|
||||||
"log4js": "^6.9.1",
|
"log4js": "^6.9.1",
|
||||||
"node-notifier": "^10.0.0",
|
"node-notifier": "^10.0.0",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
|
"usercss-meta": "^0.12.0",
|
||||||
"v8-compile-cache": "^2.3.0"
|
"v8-compile-cache": "^2.3.0"
|
||||||
},
|
|
||||||
"build": {
|
|
||||||
"appId": "com.oxmc.bskyDesktop",
|
|
||||||
"productName": "bskyDesktop",
|
|
||||||
"asarUnpack": [
|
|
||||||
"./node_modules/node-notifier/**/*"
|
|
||||||
],
|
|
||||||
"artifactName": "bsky-desktop.${ext}",
|
|
||||||
"mac": {
|
|
||||||
"target": [
|
|
||||||
"dmg",
|
|
||||||
"pkg"
|
|
||||||
],
|
|
||||||
"icon": "src/ui/images/logo.icns",
|
|
||||||
"category": "Network"
|
|
||||||
},
|
|
||||||
"linux": {
|
|
||||||
"target": [
|
|
||||||
"appimage"
|
|
||||||
],
|
|
||||||
"icon": "src/ui/images/icons",
|
|
||||||
"category": "Network"
|
|
||||||
},
|
|
||||||
"win": {
|
|
||||||
"target": [
|
|
||||||
"nsis"
|
|
||||||
],
|
|
||||||
"icon": "src/ui/images/logo.ico"
|
|
||||||
},
|
|
||||||
"protocols": [
|
|
||||||
{
|
|
||||||
"name": "bsky",
|
|
||||||
"schemes": [
|
|
||||||
"bsky"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
src/app/contributors.json
Normal file
17
src/app/contributors.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "oxmc",
|
||||||
|
"email": "contact@oxmc.is-a.dev",
|
||||||
|
"url": "https://oxmc.is-a.dev"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PlOszukiwaczDEV",
|
||||||
|
"email": "ploszukiwacz1@duck.com",
|
||||||
|
"url": "https://ploszukiwacz.pl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GizzyUwU",
|
||||||
|
"email": "me@gizzy.pro",
|
||||||
|
"url": "https://gizzy.pro"
|
||||||
|
}
|
||||||
|
]
|
21
src/app/discord-rpc/LICENSE
Normal file
21
src/app/discord-rpc/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 devsnek
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
660
src/app/discord-rpc/client.js
Normal file
660
src/app/discord-rpc/client.js
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
const { setTimeout, clearTimeout } = require('timers');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const transports = require('./transports');
|
||||||
|
const { RPCCommands, RPCEvents, RelationshipTypes } = require('./constants');
|
||||||
|
const { pid: getPid, uuid } = require('./util');
|
||||||
|
|
||||||
|
function subKey(event, args) {
|
||||||
|
return `${event}${JSON.stringify(args)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {RPCClientOptions}
|
||||||
|
* @extends {ClientOptions}
|
||||||
|
* @prop {string} transport RPC transport. one of `ipc` or `websocket`
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main hub for interacting with Discord RPC
|
||||||
|
* @extends {BaseClient}
|
||||||
|
*/
|
||||||
|
class RPCClient extends EventEmitter {
|
||||||
|
/**
|
||||||
|
* @param {RPCClientOptions} [options] Options for the client.
|
||||||
|
* You must provide a transport
|
||||||
|
*/
|
||||||
|
constructor(options = {}) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
this.accessToken = null;
|
||||||
|
this.clientId = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application used in this client
|
||||||
|
* @type {?ClientApplication}
|
||||||
|
*/
|
||||||
|
this.application = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User used in this application
|
||||||
|
* @type {?User}
|
||||||
|
*/
|
||||||
|
this.user = null;
|
||||||
|
|
||||||
|
const Transport = transports[options.transport];
|
||||||
|
if (!Transport) {
|
||||||
|
throw new TypeError('RPC_INVALID_TRANSPORT', options.transport);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetch = (method, path, { data, query } = {}) =>
|
||||||
|
fetch(`${this.fetch.endpoint}${path}${query ? new URLSearchParams(query) : ''}`, {
|
||||||
|
method,
|
||||||
|
body: data,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.accessToken}`,
|
||||||
|
},
|
||||||
|
}).then(async (r) => {
|
||||||
|
const body = await r.json();
|
||||||
|
if (!r.ok) {
|
||||||
|
const e = new Error(r.status);
|
||||||
|
e.body = body;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fetch.endpoint = 'https://discord.com/api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw transport userd
|
||||||
|
* @type {RPCTransport}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.transport = new Transport(this);
|
||||||
|
this.transport.on('message', this._onRpcMessage.bind(this));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of nonces being expected from the transport
|
||||||
|
* @type {Map}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._expecting = new Map();
|
||||||
|
|
||||||
|
this._connectPromise = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search and connect to RPC
|
||||||
|
*/
|
||||||
|
connect(clientId) {
|
||||||
|
if (this._connectPromise) {
|
||||||
|
return this._connectPromise;
|
||||||
|
}
|
||||||
|
this._connectPromise = new Promise((resolve, reject) => {
|
||||||
|
this.clientId = clientId;
|
||||||
|
const timeout = setTimeout(() => reject(new Error('RPC_CONNECTION_TIMEOUT')), 10e3);
|
||||||
|
timeout.unref();
|
||||||
|
this.once('connected', () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve(this);
|
||||||
|
});
|
||||||
|
this.transport.once('close', () => {
|
||||||
|
this._expecting.forEach((e) => {
|
||||||
|
e.reject(new Error('connection closed'));
|
||||||
|
});
|
||||||
|
this.emit('disconnected');
|
||||||
|
reject(new Error('connection closed'));
|
||||||
|
});
|
||||||
|
this.transport.connect().catch(reject);
|
||||||
|
});
|
||||||
|
return this._connectPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {RPCLoginOptions}
|
||||||
|
* @param {string} clientId Client ID
|
||||||
|
* @param {string} [clientSecret] Client secret
|
||||||
|
* @param {string} [accessToken] Access token
|
||||||
|
* @param {string} [rpcToken] RPC token
|
||||||
|
* @param {string} [tokenEndpoint] Token endpoint
|
||||||
|
* @param {string[]} [scopes] Scopes to authorize with
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs authentication flow. Automatically calls Client#connect if needed.
|
||||||
|
* @param {RPCLoginOptions} options Options for authentication.
|
||||||
|
* At least one property must be provided to perform login.
|
||||||
|
* @example client.login({ clientId: '1234567', clientSecret: 'abcdef123' });
|
||||||
|
* @returns {Promise<RPCClient>}
|
||||||
|
*/
|
||||||
|
async login(options = {}) {
|
||||||
|
let { clientId, accessToken } = options;
|
||||||
|
await this.connect(clientId);
|
||||||
|
if (!options.scopes) {
|
||||||
|
this.emit('ready');
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (!accessToken) {
|
||||||
|
accessToken = await this.authorize(options);
|
||||||
|
}
|
||||||
|
return this.authenticate(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request
|
||||||
|
* @param {string} cmd Command
|
||||||
|
* @param {Object} [args={}] Arguments
|
||||||
|
* @param {string} [evt] Event
|
||||||
|
* @returns {Promise}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
request(cmd, args, evt) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const nonce = uuid();
|
||||||
|
this.transport.send({ cmd, args, evt, nonce });
|
||||||
|
this._expecting.set(nonce, { resolve, reject });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message handler
|
||||||
|
* @param {Object} message message
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onRpcMessage(message) {
|
||||||
|
if (message.cmd === RPCCommands.DISPATCH && message.evt === RPCEvents.READY) {
|
||||||
|
if (message.data.user) {
|
||||||
|
this.user = message.data.user;
|
||||||
|
}
|
||||||
|
this.emit('connected');
|
||||||
|
} else if (this._expecting.has(message.nonce)) {
|
||||||
|
const { resolve, reject } = this._expecting.get(message.nonce);
|
||||||
|
if (message.evt === 'ERROR') {
|
||||||
|
const e = new Error(message.data.message);
|
||||||
|
e.code = message.data.code;
|
||||||
|
e.data = message.data;
|
||||||
|
reject(e);
|
||||||
|
} else {
|
||||||
|
resolve(message.data);
|
||||||
|
}
|
||||||
|
this._expecting.delete(message.nonce);
|
||||||
|
} else {
|
||||||
|
this.emit(message.evt, message.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorize
|
||||||
|
* @param {Object} options options
|
||||||
|
* @returns {Promise}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async authorize({ scopes, clientSecret, rpcToken, redirectUri, prompt } = {}) {
|
||||||
|
if (clientSecret && rpcToken === true) {
|
||||||
|
const body = await this.fetch('POST', '/oauth2/token/rpc', {
|
||||||
|
data: new URLSearchParams({
|
||||||
|
client_id: this.clientId,
|
||||||
|
client_secret: clientSecret,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
rpcToken = body.rpc_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { code } = await this.request('AUTHORIZE', {
|
||||||
|
scopes,
|
||||||
|
client_id: this.clientId,
|
||||||
|
prompt,
|
||||||
|
rpc_token: rpcToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.fetch('POST', '/oauth2/token', {
|
||||||
|
data: new URLSearchParams({
|
||||||
|
client_id: this.clientId,
|
||||||
|
client_secret: clientSecret,
|
||||||
|
code,
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate
|
||||||
|
* @param {string} accessToken access token
|
||||||
|
* @returns {Promise}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
authenticate(accessToken) {
|
||||||
|
return this.request('AUTHENTICATE', { access_token: accessToken })
|
||||||
|
.then(({ application, user }) => {
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.application = application;
|
||||||
|
this.user = user;
|
||||||
|
this.emit('ready');
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a guild
|
||||||
|
* @param {Snowflake} id Guild ID
|
||||||
|
* @param {number} [timeout] Timeout request
|
||||||
|
* @returns {Promise<Guild>}
|
||||||
|
*/
|
||||||
|
getGuild(id, timeout) {
|
||||||
|
return this.request(RPCCommands.GET_GUILD, { guild_id: id, timeout });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all guilds
|
||||||
|
* @param {number} [timeout] Timeout request
|
||||||
|
* @returns {Promise<Collection<Snowflake, Guild>>}
|
||||||
|
*/
|
||||||
|
getGuilds(timeout) {
|
||||||
|
return this.request(RPCCommands.GET_GUILDS, { timeout });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a channel
|
||||||
|
* @param {Snowflake} id Channel ID
|
||||||
|
* @param {number} [timeout] Timeout request
|
||||||
|
* @returns {Promise<Channel>}
|
||||||
|
*/
|
||||||
|
getChannel(id, timeout) {
|
||||||
|
return this.request(RPCCommands.GET_CHANNEL, { channel_id: id, timeout });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all channels
|
||||||
|
* @param {Snowflake} [id] Guild ID
|
||||||
|
* @param {number} [timeout] Timeout request
|
||||||
|
* @returns {Promise<Collection<Snowflake, Channel>>}
|
||||||
|
*/
|
||||||
|
async getChannels(id, timeout) {
|
||||||
|
const { channels } = await this.request(RPCCommands.GET_CHANNELS, {
|
||||||
|
timeout,
|
||||||
|
guild_id: id,
|
||||||
|
});
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {CertifiedDevice}
|
||||||
|
* @prop {string} type One of `AUDIO_INPUT`, `AUDIO_OUTPUT`, `VIDEO_INPUT`
|
||||||
|
* @prop {string} uuid This device's Windows UUID
|
||||||
|
* @prop {object} vendor Vendor information
|
||||||
|
* @prop {string} vendor.name Vendor's name
|
||||||
|
* @prop {string} vendor.url Vendor's url
|
||||||
|
* @prop {object} model Model information
|
||||||
|
* @prop {string} model.name Model's name
|
||||||
|
* @prop {string} model.url Model's url
|
||||||
|
* @prop {string[]} related Array of related product's Windows UUIDs
|
||||||
|
* @prop {boolean} echoCancellation If the device has echo cancellation
|
||||||
|
* @prop {boolean} noiseSuppression If the device has noise suppression
|
||||||
|
* @prop {boolean} automaticGainControl If the device has automatic gain control
|
||||||
|
* @prop {boolean} hardwareMute If the device has a hardware mute
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell discord which devices are certified
|
||||||
|
* @param {CertifiedDevice[]} devices Certified devices to send to discord
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
setCertifiedDevices(devices) {
|
||||||
|
return this.request(RPCCommands.SET_CERTIFIED_DEVICES, {
|
||||||
|
devices: devices.map((d) => ({
|
||||||
|
type: d.type,
|
||||||
|
id: d.uuid,
|
||||||
|
vendor: d.vendor,
|
||||||
|
model: d.model,
|
||||||
|
related: d.related,
|
||||||
|
echo_cancellation: d.echoCancellation,
|
||||||
|
noise_suppression: d.noiseSuppression,
|
||||||
|
automatic_gain_control: d.automaticGainControl,
|
||||||
|
hardware_mute: d.hardwareMute,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {UserVoiceSettings}
|
||||||
|
* @prop {Snowflake} id ID of the user these settings apply to
|
||||||
|
* @prop {?Object} [pan] Pan settings, an object with `left` and `right` set between
|
||||||
|
* 0.0 and 1.0, inclusive
|
||||||
|
* @prop {?number} [volume=100] The volume
|
||||||
|
* @prop {bool} [mute] If the user is muted
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the voice settings for a user, by id
|
||||||
|
* @param {Snowflake} id ID of the user to set
|
||||||
|
* @param {UserVoiceSettings} settings Settings
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
setUserVoiceSettings(id, settings) {
|
||||||
|
return this.request(RPCCommands.SET_USER_VOICE_SETTINGS, {
|
||||||
|
user_id: id,
|
||||||
|
pan: settings.pan,
|
||||||
|
mute: settings.mute,
|
||||||
|
volume: settings.volume,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the user to a voice channel
|
||||||
|
* @param {Snowflake} id ID of the voice channel
|
||||||
|
* @param {Object} [options] Options
|
||||||
|
* @param {number} [options.timeout] Timeout for the command
|
||||||
|
* @param {boolean} [options.force] Force this move. This should only be done if you
|
||||||
|
* have explicit permission from the user.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
selectVoiceChannel(id, { timeout, force = false } = {}) {
|
||||||
|
return this.request(RPCCommands.SELECT_VOICE_CHANNEL, { channel_id: id, timeout, force });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the user to a text channel
|
||||||
|
* @param {Snowflake} id ID of the voice channel
|
||||||
|
* @param {Object} [options] Options
|
||||||
|
* @param {number} [options.timeout] Timeout for the command
|
||||||
|
* have explicit permission from the user.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
selectTextChannel(id, { timeout } = {}) {
|
||||||
|
return this.request(RPCCommands.SELECT_TEXT_CHANNEL, { channel_id: id, timeout });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current voice settings
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
getVoiceSettings() {
|
||||||
|
return this.request(RPCCommands.GET_VOICE_SETTINGS)
|
||||||
|
.then((s) => ({
|
||||||
|
automaticGainControl: s.automatic_gain_control,
|
||||||
|
echoCancellation: s.echo_cancellation,
|
||||||
|
noiseSuppression: s.noise_suppression,
|
||||||
|
qos: s.qos,
|
||||||
|
silenceWarning: s.silence_warning,
|
||||||
|
deaf: s.deaf,
|
||||||
|
mute: s.mute,
|
||||||
|
input: {
|
||||||
|
availableDevices: s.input.available_devices,
|
||||||
|
device: s.input.device_id,
|
||||||
|
volume: s.input.volume,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
availableDevices: s.output.available_devices,
|
||||||
|
device: s.output.device_id,
|
||||||
|
volume: s.output.volume,
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: s.mode.type,
|
||||||
|
autoThreshold: s.mode.auto_threshold,
|
||||||
|
threshold: s.mode.threshold,
|
||||||
|
shortcut: s.mode.shortcut,
|
||||||
|
delay: s.mode.delay,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set current voice settings, overriding the current settings until this session disconnects.
|
||||||
|
* This also locks the settings for any other rpc sessions which may be connected.
|
||||||
|
* @param {Object} args Settings
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
setVoiceSettings(args) {
|
||||||
|
return this.request(RPCCommands.SET_VOICE_SETTINGS, {
|
||||||
|
automatic_gain_control: args.automaticGainControl,
|
||||||
|
echo_cancellation: args.echoCancellation,
|
||||||
|
noise_suppression: args.noiseSuppression,
|
||||||
|
qos: args.qos,
|
||||||
|
silence_warning: args.silenceWarning,
|
||||||
|
deaf: args.deaf,
|
||||||
|
mute: args.mute,
|
||||||
|
input: args.input ? {
|
||||||
|
device_id: args.input.device,
|
||||||
|
volume: args.input.volume,
|
||||||
|
} : undefined,
|
||||||
|
output: args.output ? {
|
||||||
|
device_id: args.output.device,
|
||||||
|
volume: args.output.volume,
|
||||||
|
} : undefined,
|
||||||
|
mode: args.mode ? {
|
||||||
|
type: args.mode.type,
|
||||||
|
auto_threshold: args.mode.autoThreshold,
|
||||||
|
threshold: args.mode.threshold,
|
||||||
|
shortcut: args.mode.shortcut,
|
||||||
|
delay: args.mode.delay,
|
||||||
|
} : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture a shortcut using the client
|
||||||
|
* The callback takes (key, stop) where `stop` is a function that will stop capturing.
|
||||||
|
* This `stop` function must be called before disconnecting or else the user will have
|
||||||
|
* to restart their client.
|
||||||
|
* @param {Function} callback Callback handling keys
|
||||||
|
* @returns {Promise<Function>}
|
||||||
|
*/
|
||||||
|
captureShortcut(callback) {
|
||||||
|
const subid = subKey(RPCEvents.CAPTURE_SHORTCUT_CHANGE);
|
||||||
|
const stop = () => {
|
||||||
|
this._subscriptions.delete(subid);
|
||||||
|
return this.request(RPCCommands.CAPTURE_SHORTCUT, { action: 'STOP' });
|
||||||
|
};
|
||||||
|
this._subscriptions.set(subid, ({ shortcut }) => {
|
||||||
|
callback(shortcut, stop);
|
||||||
|
});
|
||||||
|
return this.request(RPCCommands.CAPTURE_SHORTCUT, { action: 'START' })
|
||||||
|
.then(() => stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the presence for the logged in user.
|
||||||
|
* @param {object} args The rich presence to pass.
|
||||||
|
* @param {number} [pid] The application's process ID. Defaults to the executing process' PID.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
setActivity(args = {}, pid = getPid()) {
|
||||||
|
let timestamps;
|
||||||
|
let assets;
|
||||||
|
let party;
|
||||||
|
let secrets;
|
||||||
|
if (args.startTimestamp || args.endTimestamp) {
|
||||||
|
timestamps = {
|
||||||
|
start: args.startTimestamp,
|
||||||
|
end: args.endTimestamp,
|
||||||
|
};
|
||||||
|
if (timestamps.start instanceof Date) {
|
||||||
|
timestamps.start = Math.round(timestamps.start.getTime());
|
||||||
|
}
|
||||||
|
if (timestamps.end instanceof Date) {
|
||||||
|
timestamps.end = Math.round(timestamps.end.getTime());
|
||||||
|
}
|
||||||
|
if (timestamps.start > 2147483647000) {
|
||||||
|
throw new RangeError('timestamps.start must fit into a unix timestamp');
|
||||||
|
}
|
||||||
|
if (timestamps.end > 2147483647000) {
|
||||||
|
throw new RangeError('timestamps.end must fit into a unix timestamp');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
args.largeImageKey || args.largeImageText
|
||||||
|
|| args.smallImageKey || args.smallImageText
|
||||||
|
) {
|
||||||
|
assets = {
|
||||||
|
large_image: args.largeImageKey,
|
||||||
|
large_text: args.largeImageText,
|
||||||
|
small_image: args.smallImageKey,
|
||||||
|
small_text: args.smallImageText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (args.partySize || args.partyId || args.partyMax) {
|
||||||
|
party = { id: args.partyId };
|
||||||
|
if (args.partySize || args.partyMax) {
|
||||||
|
party.size = [args.partySize, args.partyMax];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (args.matchSecret || args.joinSecret || args.spectateSecret) {
|
||||||
|
secrets = {
|
||||||
|
match: args.matchSecret,
|
||||||
|
join: args.joinSecret,
|
||||||
|
spectate: args.spectateSecret,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.request(RPCCommands.SET_ACTIVITY, {
|
||||||
|
pid,
|
||||||
|
activity: {
|
||||||
|
state: args.state,
|
||||||
|
details: args.details,
|
||||||
|
timestamps,
|
||||||
|
assets,
|
||||||
|
party,
|
||||||
|
secrets,
|
||||||
|
buttons: args.buttons,
|
||||||
|
instance: !!args.instance,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the currently set presence, if any. This will hide the "Playing X" message
|
||||||
|
* displayed below the user's name.
|
||||||
|
* @param {number} [pid] The application's process ID. Defaults to the executing process' PID.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
clearActivity(pid = getPid()) {
|
||||||
|
return this.request(RPCCommands.SET_ACTIVITY, {
|
||||||
|
pid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invite a user to join the game the RPC user is currently playing
|
||||||
|
* @param {User} user The user to invite
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
sendJoinInvite(user) {
|
||||||
|
return this.request(RPCCommands.SEND_ACTIVITY_JOIN_INVITE, {
|
||||||
|
user_id: user.id || user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request to join the game the user is playing
|
||||||
|
* @param {User} user The user whose game you want to request to join
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
sendJoinRequest(user) {
|
||||||
|
return this.request(RPCCommands.SEND_ACTIVITY_JOIN_REQUEST, {
|
||||||
|
user_id: user.id || user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reject a join request from a user
|
||||||
|
* @param {User} user The user whose request you wish to reject
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
closeJoinRequest(user) {
|
||||||
|
return this.request(RPCCommands.CLOSE_ACTIVITY_JOIN_REQUEST, {
|
||||||
|
user_id: user.id || user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createLobby(type, capacity, metadata) {
|
||||||
|
return this.request(RPCCommands.CREATE_LOBBY, {
|
||||||
|
type,
|
||||||
|
capacity,
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLobby(lobby, { type, owner, capacity, metadata } = {}) {
|
||||||
|
return this.request(RPCCommands.UPDATE_LOBBY, {
|
||||||
|
id: lobby.id || lobby,
|
||||||
|
type,
|
||||||
|
owner_id: (owner && owner.id) || owner,
|
||||||
|
capacity,
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteLobby(lobby) {
|
||||||
|
return this.request(RPCCommands.DELETE_LOBBY, {
|
||||||
|
id: lobby.id || lobby,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToLobby(id, secret) {
|
||||||
|
return this.request(RPCCommands.CONNECT_TO_LOBBY, {
|
||||||
|
id,
|
||||||
|
secret,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToLobby(lobby, data) {
|
||||||
|
return this.request(RPCCommands.SEND_TO_LOBBY, {
|
||||||
|
id: lobby.id || lobby,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectFromLobby(lobby) {
|
||||||
|
return this.request(RPCCommands.DISCONNECT_FROM_LOBBY, {
|
||||||
|
id: lobby.id || lobby,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLobbyMember(lobby, user, metadata) {
|
||||||
|
return this.request(RPCCommands.UPDATE_LOBBY_MEMBER, {
|
||||||
|
lobby_id: lobby.id || lobby,
|
||||||
|
user_id: user.id || user,
|
||||||
|
metadata,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getRelationships() {
|
||||||
|
const types = Object.keys(RelationshipTypes);
|
||||||
|
return this.request(RPCCommands.GET_RELATIONSHIPS)
|
||||||
|
.then((o) => o.relationships.map((r) => ({
|
||||||
|
...r,
|
||||||
|
type: types[r.type],
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to an event
|
||||||
|
* @param {string} event Name of event e.g. `MESSAGE_CREATE`
|
||||||
|
* @param {Object} [args] Args for event e.g. `{ channel_id: '1234' }`
|
||||||
|
* @returns {Promise<Object>}
|
||||||
|
*/
|
||||||
|
async subscribe(event, args) {
|
||||||
|
await this.request(RPCCommands.SUBSCRIBE, args, event);
|
||||||
|
return {
|
||||||
|
unsubscribe: () => this.request(RPCCommands.UNSUBSCRIBE, args, event),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the client
|
||||||
|
*/
|
||||||
|
async destroy() {
|
||||||
|
await this.transport.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RPCClient;
|
178
src/app/discord-rpc/constants.js
Normal file
178
src/app/discord-rpc/constants.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
function keyMirror(arr) {
|
||||||
|
const tmp = {};
|
||||||
|
for (const value of arr) {
|
||||||
|
tmp[value] = value;
|
||||||
|
}
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exports.browser = typeof window !== 'undefined';
|
||||||
|
|
||||||
|
exports.RPCCommands = keyMirror([
|
||||||
|
'DISPATCH',
|
||||||
|
'AUTHORIZE',
|
||||||
|
'AUTHENTICATE',
|
||||||
|
'GET_GUILD',
|
||||||
|
'GET_GUILDS',
|
||||||
|
'GET_CHANNEL',
|
||||||
|
'GET_CHANNELS',
|
||||||
|
'CREATE_CHANNEL_INVITE',
|
||||||
|
'GET_RELATIONSHIPS',
|
||||||
|
'GET_USER',
|
||||||
|
'SUBSCRIBE',
|
||||||
|
'UNSUBSCRIBE',
|
||||||
|
'SET_USER_VOICE_SETTINGS',
|
||||||
|
'SET_USER_VOICE_SETTINGS_2',
|
||||||
|
'SELECT_VOICE_CHANNEL',
|
||||||
|
'GET_SELECTED_VOICE_CHANNEL',
|
||||||
|
'SELECT_TEXT_CHANNEL',
|
||||||
|
'GET_VOICE_SETTINGS',
|
||||||
|
'SET_VOICE_SETTINGS_2',
|
||||||
|
'SET_VOICE_SETTINGS',
|
||||||
|
'CAPTURE_SHORTCUT',
|
||||||
|
'SET_ACTIVITY',
|
||||||
|
'SEND_ACTIVITY_JOIN_INVITE',
|
||||||
|
'CLOSE_ACTIVITY_JOIN_REQUEST',
|
||||||
|
'ACTIVITY_INVITE_USER',
|
||||||
|
'ACCEPT_ACTIVITY_INVITE',
|
||||||
|
'INVITE_BROWSER',
|
||||||
|
'DEEP_LINK',
|
||||||
|
'CONNECTIONS_CALLBACK',
|
||||||
|
'BRAINTREE_POPUP_BRIDGE_CALLBACK',
|
||||||
|
'GIFT_CODE_BROWSER',
|
||||||
|
'GUILD_TEMPLATE_BROWSER',
|
||||||
|
'OVERLAY',
|
||||||
|
'BROWSER_HANDOFF',
|
||||||
|
'SET_CERTIFIED_DEVICES',
|
||||||
|
'GET_IMAGE',
|
||||||
|
'CREATE_LOBBY',
|
||||||
|
'UPDATE_LOBBY',
|
||||||
|
'DELETE_LOBBY',
|
||||||
|
'UPDATE_LOBBY_MEMBER',
|
||||||
|
'CONNECT_TO_LOBBY',
|
||||||
|
'DISCONNECT_FROM_LOBBY',
|
||||||
|
'SEND_TO_LOBBY',
|
||||||
|
'SEARCH_LOBBIES',
|
||||||
|
'CONNECT_TO_LOBBY_VOICE',
|
||||||
|
'DISCONNECT_FROM_LOBBY_VOICE',
|
||||||
|
'SET_OVERLAY_LOCKED',
|
||||||
|
'OPEN_OVERLAY_ACTIVITY_INVITE',
|
||||||
|
'OPEN_OVERLAY_GUILD_INVITE',
|
||||||
|
'OPEN_OVERLAY_VOICE_SETTINGS',
|
||||||
|
'VALIDATE_APPLICATION',
|
||||||
|
'GET_ENTITLEMENT_TICKET',
|
||||||
|
'GET_APPLICATION_TICKET',
|
||||||
|
'START_PURCHASE',
|
||||||
|
'GET_SKUS',
|
||||||
|
'GET_ENTITLEMENTS',
|
||||||
|
'GET_NETWORKING_CONFIG',
|
||||||
|
'NETWORKING_SYSTEM_METRICS',
|
||||||
|
'NETWORKING_PEER_METRICS',
|
||||||
|
'NETWORKING_CREATE_TOKEN',
|
||||||
|
'SET_USER_ACHIEVEMENT',
|
||||||
|
'GET_USER_ACHIEVEMENTS',
|
||||||
|
]);
|
||||||
|
|
||||||
|
exports.RPCEvents = keyMirror([
|
||||||
|
'CURRENT_USER_UPDATE',
|
||||||
|
'GUILD_STATUS',
|
||||||
|
'GUILD_CREATE',
|
||||||
|
'CHANNEL_CREATE',
|
||||||
|
'RELATIONSHIP_UPDATE',
|
||||||
|
'VOICE_CHANNEL_SELECT',
|
||||||
|
'VOICE_STATE_CREATE',
|
||||||
|
'VOICE_STATE_DELETE',
|
||||||
|
'VOICE_STATE_UPDATE',
|
||||||
|
'VOICE_SETTINGS_UPDATE',
|
||||||
|
'VOICE_SETTINGS_UPDATE_2',
|
||||||
|
'VOICE_CONNECTION_STATUS',
|
||||||
|
'SPEAKING_START',
|
||||||
|
'SPEAKING_STOP',
|
||||||
|
'GAME_JOIN',
|
||||||
|
'GAME_SPECTATE',
|
||||||
|
'ACTIVITY_JOIN',
|
||||||
|
'ACTIVITY_JOIN_REQUEST',
|
||||||
|
'ACTIVITY_SPECTATE',
|
||||||
|
'ACTIVITY_INVITE',
|
||||||
|
'NOTIFICATION_CREATE',
|
||||||
|
'MESSAGE_CREATE',
|
||||||
|
'MESSAGE_UPDATE',
|
||||||
|
'MESSAGE_DELETE',
|
||||||
|
'LOBBY_DELETE',
|
||||||
|
'LOBBY_UPDATE',
|
||||||
|
'LOBBY_MEMBER_CONNECT',
|
||||||
|
'LOBBY_MEMBER_DISCONNECT',
|
||||||
|
'LOBBY_MEMBER_UPDATE',
|
||||||
|
'LOBBY_MESSAGE',
|
||||||
|
'CAPTURE_SHORTCUT_CHANGE',
|
||||||
|
'OVERLAY',
|
||||||
|
'OVERLAY_UPDATE',
|
||||||
|
'ENTITLEMENT_CREATE',
|
||||||
|
'ENTITLEMENT_DELETE',
|
||||||
|
'USER_ACHIEVEMENT_UPDATE',
|
||||||
|
'READY',
|
||||||
|
'ERROR',
|
||||||
|
]);
|
||||||
|
|
||||||
|
exports.RPCErrors = {
|
||||||
|
CAPTURE_SHORTCUT_ALREADY_LISTENING: 5004,
|
||||||
|
GET_GUILD_TIMED_OUT: 5002,
|
||||||
|
INVALID_ACTIVITY_JOIN_REQUEST: 4012,
|
||||||
|
INVALID_ACTIVITY_SECRET: 5005,
|
||||||
|
INVALID_CHANNEL: 4005,
|
||||||
|
INVALID_CLIENTID: 4007,
|
||||||
|
INVALID_COMMAND: 4002,
|
||||||
|
INVALID_ENTITLEMENT: 4015,
|
||||||
|
INVALID_EVENT: 4004,
|
||||||
|
INVALID_GIFT_CODE: 4016,
|
||||||
|
INVALID_GUILD: 4003,
|
||||||
|
INVALID_INVITE: 4011,
|
||||||
|
INVALID_LOBBY: 4013,
|
||||||
|
INVALID_LOBBY_SECRET: 4014,
|
||||||
|
INVALID_ORIGIN: 4008,
|
||||||
|
INVALID_PAYLOAD: 4000,
|
||||||
|
INVALID_PERMISSIONS: 4006,
|
||||||
|
INVALID_TOKEN: 4009,
|
||||||
|
INVALID_USER: 4010,
|
||||||
|
LOBBY_FULL: 5007,
|
||||||
|
NO_ELIGIBLE_ACTIVITY: 5006,
|
||||||
|
OAUTH2_ERROR: 5000,
|
||||||
|
PURCHASE_CANCELED: 5008,
|
||||||
|
PURCHASE_ERROR: 5009,
|
||||||
|
RATE_LIMITED: 5011,
|
||||||
|
SELECT_CHANNEL_TIMED_OUT: 5001,
|
||||||
|
SELECT_VOICE_FORCE_REQUIRED: 5003,
|
||||||
|
SERVICE_UNAVAILABLE: 1001,
|
||||||
|
TRANSACTION_ABORTED: 1002,
|
||||||
|
UNAUTHORIZED_FOR_ACHIEVEMENT: 5010,
|
||||||
|
UNKNOWN_ERROR: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.RPCCloseCodes = {
|
||||||
|
CLOSE_NORMAL: 1000,
|
||||||
|
CLOSE_UNSUPPORTED: 1003,
|
||||||
|
CLOSE_ABNORMAL: 1006,
|
||||||
|
INVALID_CLIENTID: 4000,
|
||||||
|
INVALID_ORIGIN: 4001,
|
||||||
|
RATELIMITED: 4002,
|
||||||
|
TOKEN_REVOKED: 4003,
|
||||||
|
INVALID_VERSION: 4004,
|
||||||
|
INVALID_ENCODING: 4005,
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.LobbyTypes = {
|
||||||
|
PRIVATE: 1,
|
||||||
|
PUBLIC: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.RelationshipTypes = {
|
||||||
|
NONE: 0,
|
||||||
|
FRIEND: 1,
|
||||||
|
BLOCKED: 2,
|
||||||
|
PENDING_INCOMING: 3,
|
||||||
|
PENDING_OUTGOING: 4,
|
||||||
|
IMPLICIT: 5,
|
||||||
|
};
|
10
src/app/discord-rpc/index.js
Normal file
10
src/app/discord-rpc/index.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const util = require('./util');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Client: require('./client'),
|
||||||
|
register(id) {
|
||||||
|
return util.register(`discord-${id}`);
|
||||||
|
},
|
||||||
|
};
|
6
src/app/discord-rpc/transports/index.js
Normal file
6
src/app/discord-rpc/transports/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ipc: require('./ipc'),
|
||||||
|
websocket: require('./websocket'),
|
||||||
|
};
|
173
src/app/discord-rpc/transports/ipc.js
Normal file
173
src/app/discord-rpc/transports/ipc.js
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const net = require('net');
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const { uuid } = require('../util');
|
||||||
|
|
||||||
|
const OPCodes = {
|
||||||
|
HANDSHAKE: 0,
|
||||||
|
FRAME: 1,
|
||||||
|
CLOSE: 2,
|
||||||
|
PING: 3,
|
||||||
|
PONG: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getIPCPath(id) {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
return `\\\\?\\pipe\\discord-ipc-${id}`;
|
||||||
|
}
|
||||||
|
const { env: { XDG_RUNTIME_DIR, TMPDIR, TMP, TEMP } } = process;
|
||||||
|
const prefix = XDG_RUNTIME_DIR || TMPDIR || TMP || TEMP || '/tmp';
|
||||||
|
return `${prefix.replace(/\/$/, '')}/discord-ipc-${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIPC(id = 0) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const path = getIPCPath(id);
|
||||||
|
const onerror = () => {
|
||||||
|
if (id < 10) {
|
||||||
|
resolve(getIPC(id + 1));
|
||||||
|
} else {
|
||||||
|
reject(new Error('Could not connect'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const sock = net.createConnection(path, () => {
|
||||||
|
sock.removeListener('error', onerror);
|
||||||
|
resolve(sock);
|
||||||
|
});
|
||||||
|
sock.once('error', onerror);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findEndpoint(tries = 0) {
|
||||||
|
if (tries > 30) {
|
||||||
|
throw new Error('Could not find endpoint');
|
||||||
|
}
|
||||||
|
const endpoint = `http://127.0.0.1:${6463 + (tries % 10)}`;
|
||||||
|
try {
|
||||||
|
const r = await fetch(endpoint);
|
||||||
|
if (r.status === 404) {
|
||||||
|
return endpoint;
|
||||||
|
}
|
||||||
|
return findEndpoint(tries + 1);
|
||||||
|
} catch (e) {
|
||||||
|
return findEndpoint(tries + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function encode(op, data) {
|
||||||
|
data = JSON.stringify(data);
|
||||||
|
const len = Buffer.byteLength(data);
|
||||||
|
const packet = Buffer.alloc(8 + len);
|
||||||
|
packet.writeInt32LE(op, 0);
|
||||||
|
packet.writeInt32LE(len, 4);
|
||||||
|
packet.write(data, 8, len);
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const working = {
|
||||||
|
full: '',
|
||||||
|
op: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
function decode(socket, callback) {
|
||||||
|
const packet = socket.read();
|
||||||
|
if (!packet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { op } = working;
|
||||||
|
let raw;
|
||||||
|
if (working.full === '') {
|
||||||
|
op = working.op = packet.readInt32LE(0);
|
||||||
|
const len = packet.readInt32LE(4);
|
||||||
|
raw = packet.slice(8, len + 8);
|
||||||
|
} else {
|
||||||
|
raw = packet.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(working.full + raw);
|
||||||
|
callback({ op, data }); // eslint-disable-line callback-return
|
||||||
|
working.full = '';
|
||||||
|
working.op = undefined;
|
||||||
|
} catch (err) {
|
||||||
|
working.full += raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
decode(socket, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
class IPCTransport extends EventEmitter {
|
||||||
|
constructor(client) {
|
||||||
|
super();
|
||||||
|
this.client = client;
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
const socket = this.socket = await getIPC();
|
||||||
|
socket.on('close', this.onClose.bind(this));
|
||||||
|
socket.on('error', this.onClose.bind(this));
|
||||||
|
this.emit('open');
|
||||||
|
socket.write(encode(OPCodes.HANDSHAKE, {
|
||||||
|
v: 1,
|
||||||
|
client_id: this.client.clientId,
|
||||||
|
}));
|
||||||
|
socket.pause();
|
||||||
|
socket.on('readable', () => {
|
||||||
|
decode(socket, ({ op, data }) => {
|
||||||
|
switch (op) {
|
||||||
|
case OPCodes.PING:
|
||||||
|
this.send(data, OPCodes.PONG);
|
||||||
|
break;
|
||||||
|
case OPCodes.FRAME:
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.cmd === 'AUTHORIZE' && data.evt !== 'ERROR') {
|
||||||
|
findEndpoint()
|
||||||
|
.then((endpoint) => {
|
||||||
|
this.client.request.endpoint = endpoint;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.client.emit('error', e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.emit('message', data);
|
||||||
|
break;
|
||||||
|
case OPCodes.CLOSE:
|
||||||
|
this.emit('close', data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(e) {
|
||||||
|
this.emit('close', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data, op = OPCodes.FRAME) {
|
||||||
|
this.socket.write(encode(op, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
return new Promise((r) => {
|
||||||
|
this.once('close', r);
|
||||||
|
this.send({}, OPCodes.CLOSE);
|
||||||
|
this.socket.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ping() {
|
||||||
|
this.send(uuid(), OPCodes.PING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = IPCTransport;
|
||||||
|
module.exports.encode = encode;
|
||||||
|
module.exports.decode = decode;
|
77
src/app/discord-rpc/transports/websocket.js
Normal file
77
src/app/discord-rpc/transports/websocket.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const EventEmitter = require('events');
|
||||||
|
const { browser } = require('../constants');
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const WebSocket = browser ? window.WebSocket : require('ws');
|
||||||
|
|
||||||
|
const pack = (d) => JSON.stringify(d);
|
||||||
|
const unpack = (s) => JSON.parse(s);
|
||||||
|
|
||||||
|
class WebSocketTransport extends EventEmitter {
|
||||||
|
constructor(client) {
|
||||||
|
super();
|
||||||
|
this.client = client;
|
||||||
|
this.ws = null;
|
||||||
|
this.tries = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
const port = 6463 + (this.tries % 10);
|
||||||
|
this.tries += 1;
|
||||||
|
|
||||||
|
this.ws = new WebSocket(
|
||||||
|
`ws://127.0.0.1:${port}/?v=1&client_id=${this.client.clientId}`,
|
||||||
|
browser ? undefined : { origin: this.client.options.origin },
|
||||||
|
);
|
||||||
|
this.ws.onopen = this.onOpen.bind(this);
|
||||||
|
this.ws.onclose = this.onClose.bind(this);
|
||||||
|
this.ws.onerror = this.onError.bind(this);
|
||||||
|
this.ws.onmessage = this.onMessage.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
this.emit('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(event) {
|
||||||
|
if (!event.wasClean) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.emit('close', event);
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(event) {
|
||||||
|
try {
|
||||||
|
this.ws.close();
|
||||||
|
} catch {} // eslint-disable-line no-empty
|
||||||
|
|
||||||
|
if (this.tries > 20) {
|
||||||
|
this.emit('error', event.error);
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.connect();
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(event) {
|
||||||
|
this.emit('message', unpack(event.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
send(data) {
|
||||||
|
this.ws.send(pack(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
ping() {} // eslint-disable-line no-empty-function
|
||||||
|
|
||||||
|
close() {
|
||||||
|
return new Promise((r) => {
|
||||||
|
this.once('close', r);
|
||||||
|
this.ws.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WebSocketTransport;
|
50
src/app/discord-rpc/util.js
Normal file
50
src/app/discord-rpc/util.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
let register;
|
||||||
|
try {
|
||||||
|
const { app } = require('electron');
|
||||||
|
register = app.setAsDefaultProtocolClient.bind(app);
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
register = require('register-scheme');
|
||||||
|
} catch (e) {} // eslint-disable-line no-empty
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof register !== 'function') {
|
||||||
|
register = () => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pid() {
|
||||||
|
if (typeof process !== 'undefined') {
|
||||||
|
return process.pid;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid4122 = () => {
|
||||||
|
let uuid = '';
|
||||||
|
for (let i = 0; i < 32; i += 1) {
|
||||||
|
if (i === 8 || i === 12 || i === 16 || i === 20) {
|
||||||
|
uuid += '-';
|
||||||
|
}
|
||||||
|
let n;
|
||||||
|
if (i === 12) {
|
||||||
|
n = 4;
|
||||||
|
} else {
|
||||||
|
const random = Math.random() * 16 | 0;
|
||||||
|
if (i === 16) {
|
||||||
|
n = (random & 3) | 0;
|
||||||
|
} else {
|
||||||
|
n = random;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uuid += n.toString(16);
|
||||||
|
}
|
||||||
|
return uuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
pid,
|
||||||
|
register,
|
||||||
|
uuid: uuid4122,
|
||||||
|
};
|
119
src/app/main.js
119
src/app/main.js
@@ -1,4 +1,4 @@
|
|||||||
const { app, BrowserWindow, BrowserView, globalShortcut, ipcMain, Tray, Menu, protocol, session } = require("electron");
|
const { app, BrowserWindow, BrowserView, globalShortcut, ipcMain, Tray, Menu, protocol, session, dialog } = require("electron");
|
||||||
const electronremote = require("@electron/remote/main");
|
const electronremote = require("@electron/remote/main");
|
||||||
//const asar = require('@electron/asar');
|
//const asar = require('@electron/asar');
|
||||||
const windowStateKeeper = require("electron-window-state");
|
const windowStateKeeper = require("electron-window-state");
|
||||||
@@ -8,6 +8,7 @@ const badge = require('./badge');
|
|||||||
const contextMenu = require('./context-menu');
|
const contextMenu = require('./context-menu');
|
||||||
const autoUpdater = require('./utils/auto-update');
|
const autoUpdater = require('./utils/auto-update');
|
||||||
const loadCRX = require('./utils/loadCRX');
|
const loadCRX = require('./utils/loadCRX');
|
||||||
|
const userStyles = require('./utils/userStyles');
|
||||||
const log4js = require("log4js");
|
const log4js = require("log4js");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@@ -15,6 +16,14 @@ const os = require("os");
|
|||||||
require('v8-compile-cache');
|
require('v8-compile-cache');
|
||||||
|
|
||||||
const packageJson = require(path.join(__dirname, '..', '..', 'package.json'));
|
const packageJson = require(path.join(__dirname, '..', '..', 'package.json'));
|
||||||
|
const contributors = require(path.join(__dirname, 'contributors.json'));
|
||||||
|
|
||||||
|
// Check if app is ran from the installer dmg (macOS)
|
||||||
|
if (process.platform === 'darwin' && app.isPackaged && app.isInApplicationsFolder()) {
|
||||||
|
// Creeate a dialog to ask the user to move the app to the Applications folder
|
||||||
|
dialog.showErrorBox('Move to Applications folder', 'Please move the app to the Applications folder to ensure it works correctly.');
|
||||||
|
app.quit();
|
||||||
|
};
|
||||||
|
|
||||||
// isUpdaing:
|
// isUpdaing:
|
||||||
global.isUpdating = false;
|
global.isUpdating = false;
|
||||||
@@ -37,6 +46,7 @@ global.paths = {
|
|||||||
};
|
};
|
||||||
global.paths.updateDir = path.join(global.paths.data, 'update');
|
global.paths.updateDir = path.join(global.paths.data, 'update');
|
||||||
global.paths.extensions = path.join(global.paths.data, 'extensions');
|
global.paths.extensions = path.join(global.paths.data, 'extensions');
|
||||||
|
global.paths.userstyles = path.join(global.paths.data, 'userstyles');
|
||||||
|
|
||||||
// URLs:
|
// URLs:
|
||||||
global.urls = {
|
global.urls = {
|
||||||
@@ -100,7 +110,7 @@ if (fs.existsSync(logFile) && !fs.existsSync(path.join(global.paths.data, 'lockf
|
|||||||
fs.renameSync(logFile, path.join(global.paths.data, `${logFileName}.${mtime.toISOString().split('T')[0]}.log`));
|
fs.renameSync(logFile, path.join(global.paths.data, `${logFileName}.${mtime.toISOString().split('T')[0]}.log`));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
logger.log("Starting Bsky Desktop");
|
logger.log(`Starting Bsky Desktop v${packageJson.version} on ${os.platform()} ${os.arch()}`);
|
||||||
|
|
||||||
// Create data directory if it does not exist:
|
// Create data directory if it does not exist:
|
||||||
if (!fs.existsSync(global.paths.data)) {
|
if (!fs.existsSync(global.paths.data)) {
|
||||||
@@ -126,6 +136,12 @@ if (!fs.existsSync(global.paths.extensions)) {
|
|||||||
fs.mkdirSync(global.paths.extensions, { recursive: true });
|
fs.mkdirSync(global.paths.extensions, { recursive: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create userstyles directory if it does not exist:
|
||||||
|
if (!fs.existsSync(global.paths.userstyles)) {
|
||||||
|
logger.info("Creating Userstyles Directory");
|
||||||
|
fs.mkdirSync(global.paths.userstyles, { recursive: true });
|
||||||
|
};
|
||||||
|
|
||||||
// improve performance on linux?
|
// improve performance on linux?
|
||||||
if (process.platform !== "win32" && process.platform !== "darwin") {
|
if (process.platform !== "win32" && process.platform !== "darwin") {
|
||||||
logger.log("Disabling Hardware Acceleration and Transparent Visuals");
|
logger.log("Disabling Hardware Acceleration and Transparent Visuals");
|
||||||
@@ -247,34 +263,6 @@ function createWindow() {
|
|||||||
// Badge count: (use mainWindow as that shows the badge on the taskbar)
|
// Badge count: (use mainWindow as that shows the badge on the taskbar)
|
||||||
new badge(mainWindow, badgeOptions);
|
new badge(mainWindow, badgeOptions);
|
||||||
|
|
||||||
// Load extensions (.crx files):
|
|
||||||
logger.log("Checking for extensions");
|
|
||||||
const extensions = fs.readdirSync(global.paths.extensions).filter((file) => file.endsWith('.crx'));
|
|
||||||
if (extensions.length > 0) {
|
|
||||||
logger.log(`Unpacking ${extensions.length} extensions and loading them`);
|
|
||||||
extensions.forEach((extension) => {
|
|
||||||
loadCRX(path.join(global.paths.extensions, extension));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Check for unpacked extensions:
|
|
||||||
const unpackedExtensions = fs.readdirSync(global.paths.extensions).filter((file) => fs.lstatSync(path.join(global.paths.extensions, file)).isDirectory());
|
|
||||||
|
|
||||||
// Check if the directory contains a manifest.json file
|
|
||||||
unpackedExtensions.forEach((extension) => {
|
|
||||||
const manifestPath = path.join(global.paths.extensions, extension, 'manifest.json');
|
|
||||||
if (fs.existsSync(manifestPath)) {
|
|
||||||
logger.log(`Loading unpacked extension: ${extension}`);
|
|
||||||
session.defaultSession.loadExtension(path.join(global.paths.extensions, extension)).then(({ id }) => {
|
|
||||||
logger.log(`Extension loaded with ID: ${id}`);
|
|
||||||
}).catch((error) => {
|
|
||||||
logger.error(`Failed to load extension: ${error}`);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logger.warn(`Skipping directory ${extension} as it does not contain a manifest.json file`);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.log("Main Window Created, Showing splashscreen");
|
logger.log("Main Window Created, Showing splashscreen");
|
||||||
splash.show();
|
splash.show();
|
||||||
//mainWindow.show();
|
//mainWindow.show();
|
||||||
@@ -302,7 +290,7 @@ function createWindow() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
PageView.webContents.setWindowOpenHandler(({ url }) => {
|
PageView.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
new BrowserWindow({ show: true, autoHideMenuBar: true }).loadURL(url);
|
new BrowserWindow({ show: true, autoHideMenuBar: true, icon: path.join(global.paths.app, 'ui', 'images', 'logo.png') }).loadURL(url);
|
||||||
return { action: 'deny' };
|
return { action: 'deny' };
|
||||||
});
|
});
|
||||||
// Log PageView navigations:
|
// Log PageView navigations:
|
||||||
@@ -322,6 +310,7 @@ function showAboutWindow() {
|
|||||||
//open_devtools: process.env.NODE_ENV !== 'production',
|
//open_devtools: process.env.NODE_ENV !== 'production',
|
||||||
use_version_info: [
|
use_version_info: [
|
||||||
['Application Version', `${global.appInfo.version}`],
|
['Application Version', `${global.appInfo.version}`],
|
||||||
|
['Contributors', contributors.map((contributor) => contributor.name).join(', ')],
|
||||||
],
|
],
|
||||||
license: `MIT, GPL-2.0, GPL-3.0, ${global.appInfo.license}`,
|
license: `MIT, GPL-2.0, GPL-3.0, ${global.appInfo.license}`,
|
||||||
});
|
});
|
||||||
@@ -329,7 +318,7 @@ function showAboutWindow() {
|
|||||||
|
|
||||||
function createTray() {
|
function createTray() {
|
||||||
logger.log("Creating Tray");
|
logger.log("Creating Tray");
|
||||||
const tray = new Tray(path.join(global.paths.app, 'ui', 'images', 'logo.png'));
|
const tray = new Tray(path.join(global.paths.app, 'ui', 'images', 'icons', '32x32.png'));
|
||||||
tray.setToolTip('Bsky Desktop');
|
tray.setToolTip('Bsky Desktop');
|
||||||
tray.setContextMenu(Menu.buildFromTemplate([
|
tray.setContextMenu(Menu.buildFromTemplate([
|
||||||
{ label: global.appInfo.name, enabled: false },
|
{ label: global.appInfo.name, enabled: false },
|
||||||
@@ -405,7 +394,7 @@ function handleDeeplink(commandLine) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "notiftest":
|
case "notiftest":
|
||||||
global.PageView.webContents.send('ui:notif', { title: 'Updater', message: 'Update downloaded', options: { position: 'topRight', timeout: 5000, layout: 2, color: 'blue' } });
|
global.PageView.webContents.send('ui:notif', { title: 'Updater', message: 'Update downloaded', options: { izitoast: { position: 'topRight', timeout: 5000, layout: 2, color: 'blue' } } });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -527,6 +516,70 @@ app.whenReady().then(() => {
|
|||||||
|
|
||||||
createWindow();
|
createWindow();
|
||||||
createTray();
|
createTray();
|
||||||
|
|
||||||
|
// Load extensions (.crx files):
|
||||||
|
logger.log("Checking for extensions");
|
||||||
|
const extensions = fs.readdirSync(global.paths.extensions).filter((file) => file.endsWith('.crx'));
|
||||||
|
if (extensions.length > 0) {
|
||||||
|
logger.log(`Unpacking ${extensions.length} extensions and loading them`);
|
||||||
|
extensions.forEach((extension) => {
|
||||||
|
loadCRX(path.join(global.paths.extensions, extension));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Check for unpacked extensions:
|
||||||
|
const unpackedExtensions = fs.readdirSync(global.paths.extensions).filter((file) => fs.lstatSync(path.join(global.paths.extensions, file)).isDirectory());
|
||||||
|
|
||||||
|
// Check if the directory contains a manifest.json file
|
||||||
|
unpackedExtensions.forEach((extension) => {
|
||||||
|
const manifestPath = path.join(global.paths.extensions, extension, 'manifest.json');
|
||||||
|
if (fs.existsSync(manifestPath)) {
|
||||||
|
logger.log(`Loading unpacked extension: ${extension}`);
|
||||||
|
session.defaultSession.loadExtension(path.join(global.paths.extensions, extension)).then(({ id }) => {
|
||||||
|
logger.log(`Extension loaded with ID: ${id}`);
|
||||||
|
}).catch((error) => {
|
||||||
|
logger.error(`Failed to load extension: ${error}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.warn(`Skipping directory ${extension} as it does not contain a manifest.json file`);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load userstyles
|
||||||
|
logger.log("Checking for userstyles");
|
||||||
|
const userStylesDir = path.join(global.paths.userstyles);
|
||||||
|
if (fs.existsSync(userStylesDir)) {
|
||||||
|
const files = fs.readdirSync(userStylesDir);
|
||||||
|
if (files.length > 0) {
|
||||||
|
const userStylePromises = files.map(async file => {
|
||||||
|
const cssFile = path.join(userStylesDir, file);
|
||||||
|
// Parse the CSS file
|
||||||
|
try {
|
||||||
|
const cssContent = fs.readFileSync(cssFile, 'utf-8');
|
||||||
|
const result = await userStyles.parseCSS(cssContent);
|
||||||
|
|
||||||
|
logger.info(`Loading userstyle: ${result.metadata.name}`);
|
||||||
|
|
||||||
|
// Compile the userstyle
|
||||||
|
const compiled = await userStyles.compileStyle(result.css, result.metadata);
|
||||||
|
|
||||||
|
// Check if the site 'bsky.app' is defined
|
||||||
|
if (compiled.sites?.['bsky.app']) {
|
||||||
|
// Apply the userstyle to the PageView
|
||||||
|
await PageView.webContents.insertCSS(compiled.sites['bsky.app']);
|
||||||
|
|
||||||
|
logger.info(`Applied userstyle: ${result.metadata.name}`);
|
||||||
|
} else {
|
||||||
|
logger.warn(`Userstyle ${result.metadata.name} does not target 'bsky.app'`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error loading userstyle: ${file}`, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(userStylePromises);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.log("Failed to get singleInstanceLock, Quitting");
|
logger.log("Failed to get singleInstanceLock, Quitting");
|
||||||
app.quit();
|
app.quit();
|
||||||
|
@@ -80,7 +80,7 @@ function asarUpdate() {
|
|||||||
|
|
||||||
function checkForUpdates() {
|
function checkForUpdates() {
|
||||||
// Current system information
|
// Current system information
|
||||||
logger.log('Current system information:', sys.platform, sys.getVersion());
|
logger.log('Current system information:', sys.platform, sys.getVersion(), sys.arch);
|
||||||
|
|
||||||
// Check if the current system is Windows
|
// Check if the current system is Windows
|
||||||
if (sys.isWin()) {
|
if (sys.isWin()) {
|
||||||
|
210
src/app/utils/userStyles.js
Normal file
210
src/app/utils/userStyles.js
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
const usercssMeta = require('usercss-meta');
|
||||||
|
const less = require('less');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts all global variable and mixin definitions from CSS.
|
||||||
|
* @param {string} css - The CSS string.
|
||||||
|
* @returns {string} The extracted global definitions.
|
||||||
|
*/
|
||||||
|
function extractGlobalDefinitions(css) {
|
||||||
|
const globalDefinitionRegex = /(@[\w-]+\s*(?:{[^}]*}|;))/g;
|
||||||
|
return (css.match(globalDefinitionRegex) || []).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts variable definitions from metadata.
|
||||||
|
* @param {object} metadata - Metadata containing variable definitions.
|
||||||
|
* @returns {object} A map of variable names to their default values.
|
||||||
|
*/
|
||||||
|
function extractMetadataVars(metadata) {
|
||||||
|
if (!metadata?.vars) return {};
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(metadata.vars).map(([key, value]) => [key, value.default || value.value || null])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts content enclosed within matching braces starting from a given position.
|
||||||
|
* @param {string} css - The CSS string.
|
||||||
|
* @param {number} startPos - The starting position to search for braces.
|
||||||
|
* @returns {object|null} The content and ending position of the matched braces.
|
||||||
|
*/
|
||||||
|
function extractBracedContent(css, startPos) {
|
||||||
|
const braceMatch = matchBraces(css, startPos);
|
||||||
|
if (!braceMatch) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: css.substring(braceMatch.start + 1, braceMatch.end - 1).trim(),
|
||||||
|
end: braceMatch.end,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches a pair of braces in a string starting from a given position.
|
||||||
|
* @param {string} content - The string content.
|
||||||
|
* @param {number} start - The starting position to search for braces.
|
||||||
|
* @returns {object|null} The start and end positions of the matched braces.
|
||||||
|
*/
|
||||||
|
function matchBraces(content, start) {
|
||||||
|
const openBrace = content.indexOf('{', start);
|
||||||
|
if (openBrace === -1) return null;
|
||||||
|
|
||||||
|
let braceCount = 1, pos = openBrace + 1;
|
||||||
|
while (braceCount > 0 && pos < content.length) {
|
||||||
|
if (content[pos] === '{') braceCount++;
|
||||||
|
if (content[pos] === '}') braceCount--;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return braceCount === 0 ? { start: openBrace, end: pos } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses domain rules from the provided CSS string.
|
||||||
|
* @param {string} css - The CSS string.
|
||||||
|
* @param {number} startPos - The starting position to search for domain rules.
|
||||||
|
* @returns {object|null} The domains and the rule start position.
|
||||||
|
*/
|
||||||
|
function parseDomainRule(css, startPos) {
|
||||||
|
const domainRuleRegex = /@-moz-document\s+domain\(\s*'([^']+)'(?:\s*,\s*'([^']+)')*\s*\)/g;
|
||||||
|
domainRuleRegex.lastIndex = startPos;
|
||||||
|
const match = domainRuleRegex.exec(css);
|
||||||
|
if (!match) return null;
|
||||||
|
|
||||||
|
const domains = match[0]
|
||||||
|
.match(/'[^']+'/g) // Extract all single-quoted domain values
|
||||||
|
.map(domain => domain.replace(/'/g, '').trim());
|
||||||
|
|
||||||
|
return {
|
||||||
|
domains,
|
||||||
|
ruleStart: match.index + match[0].length - 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses @-moz-document rules and extracts domain-specific CSS.
|
||||||
|
* @param {string} css - The CSS string.
|
||||||
|
* @returns {object} A map of domains to their associated CSS.
|
||||||
|
*/
|
||||||
|
function parseMozRules(css) {
|
||||||
|
const rules = {};
|
||||||
|
let currentPos = 0;
|
||||||
|
|
||||||
|
const globalCode = extractGlobalDefinitions(css);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const domainRule = parseDomainRule(css, currentPos);
|
||||||
|
if (!domainRule) break;
|
||||||
|
|
||||||
|
const bracedContent = extractBracedContent(css, domainRule.ruleStart);
|
||||||
|
if (!bracedContent) break;
|
||||||
|
|
||||||
|
// Add the CSS to all matched domains
|
||||||
|
for (const domain of domainRule.domains) {
|
||||||
|
rules[domain] = `${globalCode}\n${bracedContent.content}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPos = bracedContent.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses metadata from the provided CSS string.
|
||||||
|
* @param {string} css - The CSS string.
|
||||||
|
* @returns {object} Parsed metadata.
|
||||||
|
*/
|
||||||
|
function parseCSS(css) {
|
||||||
|
try {
|
||||||
|
const normalizedCss = css.replace(/\r\n?/g, '\n');
|
||||||
|
const nocommentsCss = normalizedCss.replace(/\/\*[\s\S]*?\*\//g, ''); // Remove comments
|
||||||
|
return {
|
||||||
|
...usercssMeta.parse(normalizedCss),
|
||||||
|
css: nocommentsCss,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to parse CSS metadata: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles CSS code with a preprocessor if specified in the metadata.
|
||||||
|
* @param {string} code - The CSS code.
|
||||||
|
* @param {object} metadata - Metadata containing preprocessor information.
|
||||||
|
* @param {object} [userVars={}] - User-defined variables to override defaults.
|
||||||
|
* @returns {Promise<{compiledCss: string, sites: object}>} The compiled CSS code and domain-specific mapping.
|
||||||
|
*/
|
||||||
|
async function compileStyle(code, metadata, userVars = {}) {
|
||||||
|
try {
|
||||||
|
// Extract and merge variables
|
||||||
|
const vars = {
|
||||||
|
...extractMetadataVars(metadata),
|
||||||
|
...userVars
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate full code with user variables
|
||||||
|
const fullCode = [
|
||||||
|
'// User variables',
|
||||||
|
Object.entries(vars).map(([key, value]) => `@${key}: ${value};`).join('\n'),
|
||||||
|
'// Main code',
|
||||||
|
code
|
||||||
|
].join('\n\n');
|
||||||
|
|
||||||
|
let compiledCode;
|
||||||
|
|
||||||
|
switch (metadata?.preprocessor?.toLowerCase()) {
|
||||||
|
case 'less':
|
||||||
|
compiledCode = await compileLess(fullCode);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
compiledCode = code; // Return unmodified for unknown preprocessors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse domain rules
|
||||||
|
const domainRules = parseMozRules(compiledCode);
|
||||||
|
|
||||||
|
// Compile each domain's CSS if needed
|
||||||
|
const compiledRules = {};
|
||||||
|
for (const [domain, css] of Object.entries(domainRules)) {
|
||||||
|
if (metadata.preprocessor === 'less') {
|
||||||
|
compiledRules[domain] = await compileLess(css, vars);
|
||||||
|
} else {
|
||||||
|
compiledRules[domain] = css;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine all CSS
|
||||||
|
const combinedCss = Object.entries(compiledRules)
|
||||||
|
.map(([domain, compiledCss]) => `/* ${domain} */\n${compiledCss}`)
|
||||||
|
.join('\n\n');
|
||||||
|
|
||||||
|
return {
|
||||||
|
compiledCss: combinedCss,
|
||||||
|
sites: compiledRules // Map of domains to their CSS
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Style compilation error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles LESS code to CSS.
|
||||||
|
* @param {string} code - The LESS code.
|
||||||
|
* @returns {Promise<string>} The compiled CSS.
|
||||||
|
*/
|
||||||
|
async function compileLess(code) {
|
||||||
|
const { css } = await less.render(code, {
|
||||||
|
math: 'parens-division',
|
||||||
|
javascriptEnabled: true,
|
||||||
|
compress: false
|
||||||
|
});
|
||||||
|
return css;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parseCSS,
|
||||||
|
compileStyle
|
||||||
|
};
|
@@ -1,7 +1,7 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin:auto;display:block;" width="150px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin:auto;display:block;" width="150px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||||
<circle cx="50" cy="50" r="30" stroke="#2B87D3" stroke-width="10" fill="none"></circle>
|
<circle cx="50" cy="50" r="30" stroke="#2B87D3" stroke-width="10" fill="none"></circle>
|
||||||
<circle cx="50" cy="50" r="30" stroke="#0C396A" stroke-width="8" stroke-linecap="round" fill="none">
|
<circle cx="50" cy="50" r="30" stroke="#0C396A" stroke-width="8" stroke-linecap="round" fill="none">
|
||||||
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform>
|
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform>
|
||||||
<animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="1s" values="18.84955592153876 169.64600329384882;94.2477796076938 94.24777960769377;18.84955592153876 169.64600329384882" keyTimes="0;0.5;1"></animate>
|
<animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="1s" values="18.84955592153876 169.64600329384882;94.2477796076938 94.24777960769377;18.84955592153876 169.64600329384882" keyTimes="0;0.5;1"></animate>
|
||||||
</circle>
|
</circle>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 848 B After Width: | Height: | Size: 812 B |
BIN
src/ui/images/mac.icns
Normal file
BIN
src/ui/images/mac.icns
Normal file
Binary file not shown.
7
src/ui/images/spinner.svg
Normal file
7
src/ui/images/spinner.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg class="spinner" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="60" height="60" style="box-sizing: border-box; padding: 3px; overflow: visible; color: #0071c2;">
|
||||||
|
<circle cx="60" cy="60" r="50" fill="none" stroke="currentColor" stroke-width="6" stroke-linecap="round" style="transform-origin: center;">
|
||||||
|
<animateTransform attributeName="transform" type="rotate" values="-90;810" keyTimes="0;1" dur="2s" repeatCount="indefinite"></animateTransform>
|
||||||
|
<animate attributeName="stroke-dashoffset" values="0%;0%;-157.080%" calcMode="spline" keySplines="0.61, 1, 0.88, 1; 0.12, 0, 0.39, 0" keyTimes="0;0.5;1" dur="2s" repeatCount="indefinite"></animate>
|
||||||
|
<animate attributeName="stroke-dasharray" values="0% 314.159%;157.080% 157.080%;0% 314.159%" calcMode="spline" keySplines="0.61, 1, 0.88, 1; 0.12, 0, 0.39, 0" keyTimes="0;0.5;1" dur="2s" repeatCount="indefinite"></animate>
|
||||||
|
</circle>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 920 B |
1
src/ui/lib/confetti-1.9.3-browser.min.js
vendored
Normal file
1
src/ui/lib/confetti-1.9.3-browser.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
src/ui/lib/css/stylelint-bundle.min.js
vendored
Normal file
11
src/ui/lib/css/stylelint-bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
63
src/ui/lib/css/stylus-renderer.min.js
vendored
Normal file
63
src/ui/lib/css/stylus-renderer.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -47,10 +47,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Load jQuery first
|
// Load jQuery first
|
||||||
loadScript('ui:///lib/jquery-3.3.1.min.js', () => {
|
loadScript('ui:///lib/jquery-3.3.1.min.js', () => {
|
||||||
// Load iziToast after jQuery
|
// Load confetti and iziToast after jQuery (ui libs)
|
||||||
|
loadScript('ui:///lib/confetti-1.9.3-browser.min.js');
|
||||||
loadScript('ui:///lib/izitoast.min.js', () => {
|
loadScript('ui:///lib/izitoast.min.js', () => {
|
||||||
|
// Load app specific scripts
|
||||||
loadScript('ui:///rend/register-handles.js');
|
loadScript('ui:///rend/register-handles.js');
|
||||||
|
// Load custom implementations
|
||||||
loadScript('ui:///rend/bsky-ext.js');
|
loadScript('ui:///rend/bsky-ext.js');
|
||||||
|
loadScript('ui:///rend/specialAnimations.js');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -20,7 +20,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
message: event.message,
|
message: event.message,
|
||||||
position: 'topRight',
|
position: 'topRight',
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
...event.options,
|
...event.options.izitoast,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to display notification:', error);
|
console.error('Failed to display notification:', error);
|
||||||
|
109
src/ui/rend/specialAnimations.js
Normal file
109
src/ui/rend/specialAnimations.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
const anims = {
|
||||||
|
imgs: {
|
||||||
|
// tree shape from https://thenounproject.com/icon/pine-tree-1471679/
|
||||||
|
tree: confetti.shapeFromPath({
|
||||||
|
path: 'M120 240c-41,14 -91,18 -120,1 29,-10 57,-22 81,-40 -18,2 -37,3 -55,-3 25,-14 48,-30 66,-51 -11,5 -26,8 -45,7 20,-14 40,-30 57,-49 -13,1 -26,2 -38,-1 18,-11 35,-25 51,-43 -13,3 -24,5 -35,6 21,-19 40,-41 53,-67 14,26 32,48 54,67 -11,-1 -23,-3 -35,-6 15,18 32,32 51,43 -13,3 -26,2 -38,1 17,19 36,35 56,49 -19,1 -33,-2 -45,-7 19,21 42,37 67,51 -19,6 -37,5 -56,3 25,18 53,30 82,40 -30,17 -79,13 -120,-1l0 41 -31 0 0 -41z',
|
||||||
|
matrix: [0.03597122302158273, 0, 0, 0.03597122302158273, -4.856115107913669, -5.071942446043165]
|
||||||
|
}),
|
||||||
|
// pumpkin shape from https://thenounproject.com/icon/pumpkin-5253388/
|
||||||
|
pumpkin: confetti.shapeFromPath({
|
||||||
|
path: 'M449.4 142c-5 0-10 .3-15 1a183 183 0 0 0-66.9-19.1V87.5a17.5 17.5 0 1 0-35 0v36.4a183 183 0 0 0-67 19c-4.9-.6-9.9-1-14.8-1C170.3 142 105 219.6 105 315s65.3 173 145.7 173c5 0 10-.3 14.8-1a184.7 184.7 0 0 0 169 0c4.9.7 9.9 1 14.9 1 80.3 0 145.6-77.6 145.6-173s-65.3-173-145.7-173zm-220 138 27.4-40.4a11.6 11.6 0 0 1 16.4-2.7l54.7 40.3a11.3 11.3 0 0 1-7 20.3H239a11.3 11.3 0 0 1-9.6-17.5zM444 383.8l-43.7 17.5a17.7 17.7 0 0 1-13 0l-37.3-15-37.2 15a17.8 17.8 0 0 1-13 0L256 383.8a17.5 17.5 0 0 1 13-32.6l37.3 15 37.2-15c4.2-1.6 8.8-1.6 13 0l37.3 15 37.2-15a17.5 17.5 0 0 1 13 32.6zm17-86.3h-82a11.3 11.3 0 0 1-6.9-20.4l54.7-40.3a11.6 11.6 0 0 1 16.4 2.8l27.4 40.4a11.3 11.3 0 0 1-9.6 17.5z',
|
||||||
|
matrix: [0.020491803278688523, 0, 0, 0.020491803278688523, -7.172131147540983, -5.9016393442622945]
|
||||||
|
}),
|
||||||
|
// heart shape from https://thenounproject.com/icon/heart-1545381/
|
||||||
|
heart: confetti.shapeFromPath({
|
||||||
|
path: 'M167 72c19,-38 37,-56 75,-56 42,0 76,33 76,75 0,76 -76,151 -151,227 -76,-76 -151,-151 -151,-227 0,-42 33,-75 75,-75 38,0 57,18 76,56z',
|
||||||
|
matrix: [0.03333333333333333, 0, 0, 0.03333333333333333, -5.566666666666666, -5.533333333333333]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// Function to animate the snow effect
|
||||||
|
snow: async function () {
|
||||||
|
var duration = 15 * 1000;
|
||||||
|
var animationEnd = Date.now() + duration;
|
||||||
|
var skew = 1;
|
||||||
|
|
||||||
|
function randomInRange(min, max) {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to animate the first snow particle
|
||||||
|
async function snowAnimation() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
var firstSnowDuration = 3000; // 3 seconds
|
||||||
|
var firstAnimationEnd = Date.now() + firstSnowDuration;
|
||||||
|
|
||||||
|
(function firstSnowFrame() {
|
||||||
|
var timeLeft = firstAnimationEnd - Date.now();
|
||||||
|
|
||||||
|
confetti({
|
||||||
|
particleCount: 1,
|
||||||
|
startVelocity: 0,
|
||||||
|
ticks: Math.max(200, 500 * (timeLeft / firstSnowDuration)),
|
||||||
|
origin: {
|
||||||
|
x: Math.random(),
|
||||||
|
y: randomInRange(0.2, 0.4) * skew - 0.4
|
||||||
|
},
|
||||||
|
colors: ['#ffffff'],
|
||||||
|
shapes: ['circle'],
|
||||||
|
gravity: randomInRange(0.4, 0.6),
|
||||||
|
scalar: randomInRange(0.4, 1),
|
||||||
|
drift: randomInRange(-0.4, 0.4)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (timeLeft > 0) {
|
||||||
|
requestAnimationFrame(firstSnowFrame);
|
||||||
|
} else {
|
||||||
|
resolve(); // Resolve the promise when the animation is complete
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to animate the continuous snow particles
|
||||||
|
function mainSnowAnimation() {
|
||||||
|
(function frame() {
|
||||||
|
var timeLeft = animationEnd - Date.now();
|
||||||
|
var ticks = Math.max(200, 500 * (timeLeft / duration));
|
||||||
|
skew = Math.max(0.8, skew - 0.001);
|
||||||
|
|
||||||
|
confetti({
|
||||||
|
particleCount: 1,
|
||||||
|
startVelocity: 0,
|
||||||
|
ticks: ticks,
|
||||||
|
origin: {
|
||||||
|
x: Math.random(),
|
||||||
|
y: randomInRange(0.2, 0.4) * skew - 0.4
|
||||||
|
},
|
||||||
|
colors: ['#ffffff'],
|
||||||
|
shapes: ['circle'],
|
||||||
|
gravity: randomInRange(0.4, 0.6),
|
||||||
|
scalar: randomInRange(0.4, 1),
|
||||||
|
drift: randomInRange(-0.4, 0.4)
|
||||||
|
});
|
||||||
|
|
||||||
|
confetti({
|
||||||
|
particleCount: 1,
|
||||||
|
startVelocity: 0,
|
||||||
|
ticks: ticks,
|
||||||
|
origin: {
|
||||||
|
x: Math.random(),
|
||||||
|
y: randomInRange(0.2, 0.4) * skew - 0.2
|
||||||
|
},
|
||||||
|
colors: ['#ffffff'],
|
||||||
|
shapes: ['circle'],
|
||||||
|
gravity: randomInRange(0.4, 0.6),
|
||||||
|
scalar: randomInRange(0.4, 1),
|
||||||
|
drift: randomInRange(-0.4, 0.4)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (timeLeft > 0) {
|
||||||
|
requestAnimationFrame(frame);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the animations
|
||||||
|
await snowAnimation(); // Wait for the first snow animation to complete
|
||||||
|
mainSnowAnimation(); // Start the continuous snow animation
|
||||||
|
}
|
||||||
|
};
|
Reference in New Issue
Block a user