43 Commits

Author SHA1 Message Date
oxmc
77dcea14cc Add userStyles support (wip) 2024-12-27 14:01:09 -08:00
oxmc
dcbbfde4a5 Reformat build dir 2024-12-20 09:28:49 -08:00
oxmc
685c2dc210 Fix sha256 method in aur build 2024-12-20 08:48:23 -08:00
oxmc
3d67f6d5ae change sha256 file paths 2024-12-20 08:36:27 -08:00
oxmc
e4ca9882b9 fix macOS sha256 2024-12-20 08:26:41 -08:00
oxmc
9c4a886799 Add extension support and work on auto update 2024-12-20 08:22:49 -08:00
oxmc
6839090631 Merge pull request #12 from GizzyUwU/main
remove a.yml
2024-12-20 05:50:07 -08:00
Gizzy
1232eea297 remove a.yml 2024-12-20 13:21:39 +00:00
oxmc
b88509d210 Merge pull request #10 from PlOszukiwaczDEV/main
fixed yq
2024-12-20 05:14:17 -08:00
oxmc
2f95583af1 Merge pull request #11 from GizzyUwU/main
Fixed version change on aur publish
2024-12-20 05:14:08 -08:00
Gizzy
6f6ab38c9f test 2024-12-20 13:12:39 +00:00
PlOszukiwacz
10887b8358 yq revert 2024-12-20 14:01:43 +01:00
PlOszukiwacz
4ce43bdac3 yq test 2 2024-12-20 13:57:38 +01:00
Gizzy
9947bfc3dc test 2024-12-20 12:52:53 +00:00
PlOszukiwacz
b68a85ac2f yq test 1 2024-12-20 13:50:31 +01:00
oxmc
dfbb0492ac Merge pull request #9 from PlOszukiwaczDEV/main
Added appimagelint to check the appimages
2024-12-20 04:46:50 -08:00
PlOszukiwacz
def3cd312d libfuse dosent exist ig 2024-12-20 13:45:49 +01:00
Gizzy
5539ae2c63 test 2024-12-20 12:43:44 +00:00
PlOszukiwacz
53bf782ddc appimagelint test 3 (libfuse) 2024-12-20 13:43:35 +01:00
Gizzy
01e9154a33 test 2024-12-20 12:37:30 +00:00
PlOszukiwacz
04ec30066d appimagelint test 2 2024-12-20 13:36:57 +01:00
PlOszukiwacz
8a1b2ac65f appimagelint test 1 2024-12-20 13:33:28 +01:00
oxmc
316f6685e0 Merge pull request #8 from PlOszukiwaczDEV/main
fixed an issue with the appimage
2024-12-20 04:11:58 -08:00
PlOszukiwacz
d26d82121c fixed an issue with the appimage 2024-12-20 13:09:05 +01:00
Gizzy
cb0cfbe640 test 2024-12-20 11:37:36 +00:00
oxmc
31731d5f24 Merge pull request #7 from GizzyUwU/main
syntax error :nerd:
2024-12-20 03:32:36 -08:00
Gizzy
6fece01fa9 syntax error :nerd: 2024-12-20 11:31:21 +00:00
oxmc
afedc14db5 Merge pull request #6 from GizzyUwU/main
Changes how version tag is set in action
2024-12-20 03:24:57 -08:00
Gizzy
9d337fa315 Changes how version tag is set in action 2024-12-20 11:22:36 +00:00
oxmc
f04efda911 Merge pull request #5 from GizzyUwU/main
AUR fix
2024-12-20 03:07:31 -08:00
Gizzy
3a0941c67c AUR fix 2024-12-20 10:59:57 +00:00
oxmc
08fbe470ad Change path in checksum download 2024-12-20 02:57:11 -08:00
oxmc
a82d5d67e2 Add jq install and debug 2024-12-20 02:52:25 -08:00
oxmc
52883d335d Add new builds, and update ext 2024-12-20 02:29:02 -08:00
oxmc
93ab0b9731 Merge pull request #4 from GizzyUwU/main
AUR Action
2024-12-20 02:15:51 -08:00
Gizzy
3e86319bb8 Merge branch 'main' of github.com:GizzyUwU/bsky-desktop 2024-12-20 10:10:40 +00:00
Gizzy
baa6e33fcd Checksum Implementation 2024-12-20 10:08:12 +00:00
GizzyUwU
cdb9130a3d Merge branch 'oxmc:main' into main 2024-12-20 09:39:16 +00:00
Gizzy
d7ad3a8812 Github Action 2024-12-20 09:38:11 +00:00
oxmc
c28640217b Merge pull request #3 from GizzyUwU/main
AUR Stuff
2024-12-20 01:01:08 -08:00
Gizzy
9e2bef0037 AUR 2024-12-20 08:52:26 +00:00
33eb3a931f Forgot to update version 2024-12-19 22:15:08 -08:00
6753ed7e79 Start adding auto updates (does not function currently) 2024-12-19 22:14:10 -08:00
24 changed files with 1425 additions and 105 deletions

View File

@@ -2,9 +2,9 @@ name: Build and Release bsky-desktop
on:
push:
branches: [ $default-branch ]
branches: [$default-branch]
pull_request:
branches: [ $default-branch ]
branches: [$default-branch]
workflow_dispatch:
concurrency:
@@ -15,8 +15,6 @@ jobs:
build-linux:
name: Build bsky-desktop (Linux)
runs-on: ubuntu-latest
outputs:
artifact: ${{ steps.upload-artifact.outputs.artifact }}
env:
ext: "AppImage"
GITHUB_TOKEN: ${{ secrets.GHT }}
@@ -39,6 +37,19 @@ jobs:
- name: Build (arm64)
run: npm run build -- --arch arm64
- name: Download appimagelint and its deps
run: |
sudo apt update && sudo apt install fuse -y
wget https://github.com/TheAssassin/appimagelint/releases/download/continuous/appimagelint-x86_64.AppImage
chmod +x appimagelint-x86_64.AppImage
- name: Check the appimage(s)
run: ./appimagelint-x86_64.AppImage dist/*.AppImage
- name: Generate checksum
run: |
sha256sum dist/*.AppImage > dist/sha256sum.txt
- name: Upload Linux Artifacts
uses: actions/upload-artifact@v4
id: upload-artifact
@@ -47,12 +58,11 @@ jobs:
path: |
dist/*.AppImage
dist/latest*.yml
dist/sha256sum.txt
build-windows:
name: Build bsky-desktop (Windows)
runs-on: windows-latest
outputs:
artifact: ${{ steps.upload-artifact.outputs.artifact }}
env:
ext: "exe"
GITHUB_TOKEN: ${{ secrets.GHT }}
@@ -74,6 +84,10 @@ jobs:
- name: Build (arm64)
run: npm run build -- --arch arm64
- name: Generate checksum
run: |
sha256sum dist/*.exe > dist/sha256sum.txt
- name: Upload Windows Artifacts
uses: actions/upload-artifact@v4
@@ -83,12 +97,11 @@ jobs:
path: |
dist/*.exe
dist/latest*.yml
dist/sha256sum.txt
build-macos:
name: Build bsky-desktop (macOS)
runs-on: macos-latest
outputs:
artifact: ${{ steps.upload-artifact.outputs.artifact }}
env:
ext: "dmg"
GITHUB_TOKEN: ${{ secrets.GHT }}
@@ -110,6 +123,10 @@ jobs:
- name: Build (arm64)
run: npm run build -- --arch arm64
- name: Generate checksum
run: |
shasum -a 256 dist/*.dmg > dist/sha256sum.txt
- name: Upload macOS Artifacts
uses: actions/upload-artifact@v4
@@ -119,22 +136,25 @@ jobs:
path: |
dist/*.dmg
dist/latest*.yml
dist/sha256sum.txt
release:
name: Create Release
runs-on: ubuntu-latest
needs: [build-linux, build-windows, build-macos]
outputs:
version_tag: ${{ steps.version.outputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GHT }}
steps:
- name: Checkout git repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Get app version
id: version
uses: pchynoweth/action-get-npm-version@1.1.1
- name: Download Linux Artifacts
uses: actions/download-artifact@v4
with:
@@ -152,9 +172,13 @@ jobs:
with:
name: macos-artifacts
path: dist/macos
- name: Display structure of downloaded files
run: ls -R dist
- name: Combine checksums
run: |
cat dist/linux/sha256sum.txt dist/windows/sha256sum.txt dist/macos/sha256sum.txt > sha256sums.txt
- name: Upload Release
id: create_release
@@ -166,4 +190,49 @@ jobs:
files: |
dist/linux/*.AppImage
dist/windows/*.exe
dist/macos/*.dmg
dist/macos/*.dmg
sha256sums.txt
aur:
name: Publish to AUR
runs-on: ubuntu-latest
needs: release
env:
AUR_TOKEN: ${{ secrets.AUR_TOKEN }}
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Download linux artifacts
uses: actions/download-artifact@v4
with:
name: linux-artifacts
path: dist/linux
- name: List downloaded files
run: ls -R dist
- name: Show content of sha256sum.txt
run: cat dist/linux/sha256sum.txt
- name: Get app version
id: version
uses: pchynoweth/action-get-npm-version@1.1.1
- name: Extract checksum from sha256sum.txt and change build version
run: |
new_checksum=$(awk 'NR==1 { print $1 }' ./dist/linux/sha256sum.txt)
sed -i "s|sha256sums=('SKIP' 'SKIP')|sha256sums=('$new_checksum' 'SKIP')|" ./build/arch-pkg/PKGBUILD
sed -i "s/^pkgver=.*$/pkgver=${{ steps.version.outputs.version }}/" ./build/arch-pkg/PKGBUILD
- name: Publish AUR package
uses: KSXGitHub/github-actions-deploy-aur@v3.0.1
with:
pkgname: bskydesktop
pkgbuild: ./build/arch-pkg/PKGBUILD
commit_username: ${{ secrets.AUR_USERNAME }}
commit_email: ${{ secrets.AUR_EMAIL }}
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: New Version
ssh_keyscan_types: rsa,ecdsa,ed25519

View File

@@ -1,3 +1,44 @@
# 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:
[![Build and Release bsky-desktop](https://github.com/oxmc/bsky-desktop/actions/workflows/build-and-release.yml/badge.svg)](https://github.com/oxmc/bsky-desktop/actions/workflows/build-and-release.yml)
[![Packaging status](https://repology.org/badge/vertical-allrepos/bskydesktop.svg?columns=4&exclude_unsupported=1)](https://repology.org/project/bskydesktop/versions)
### 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.

13
build/arch-pkg/.SRCINFO Normal file
View File

@@ -0,0 +1,13 @@
pkgbase = bskydesktop
pkgdesc = Bluesky Desktop - A decentralized social networking client distributed as an AppImage
pkgver = 1.0.8
pkgrel = 1
url = https://github.com/oxmc/bsky-desktop
arch = x86_64
arch = aarch64
license = MIT
makedepends = curl
depends = fuse2
options = !strip
pkgname = bskydesktop

82
build/arch-pkg/PKGBUILD Normal file
View File

@@ -0,0 +1,82 @@
# Maintainer: GizzyUwU me@gizzy.pro
# Maintainer: oxmc contact@oxmc.is-a.dev
pkgname=bskydesktop
pkgver=""
pkgrel=1
pkgdesc="Bluesky Desktop - A decentralized social networking client distributed as an AppImage"
arch=('x86_64' 'aarch64')
url="https://github.com/oxmc/bsky-desktop"
license=('AGPL-3.0-only')
depends=('fuse2')
makedepends=('curl')
options=('!strip')
icon_url="https://raw.githubusercontent.com/oxmc/bsky-desktop/refs/heads/main/src/ui/images/logo.png"
icon_name="bsky-desktop.png"
prepare() {
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"
case "$CARCH" in
x86_64)
appimage_name="bskyDesktop-${latest_tag:1}-linux-x64.AppImage"
sha256sum=$(echo "$latest_sha256" | grep "x64" | cut -d' ' -f1)
;;
aarch64)
appimage_name="bskyDesktop-${latest_tag:1}-linux-arm64.AppImage"
sha256sum=$(echo "$latest_sha256" | grep "arm64" | cut -d' ' -f1)
;;
*)
echo "Unsupported architecture: $CARCH"
exit 1
;;
esac
source=(
"https://github.com/oxmc/bsky-desktop/releases/download/$latest_tag/$appimage_name"
"$icon_url"
)
echo "AppImage source: ${source[0]}"
sha256sums=("$sha256sum" 'SKIP')
curl -L "${source[0]}" -o "$srcdir/bskyDesktop.appimage"
curl -L "${source[1]}" -o "$srcdir/$icon_name"
}
package() {
appimage_dest="$pkgdir/opt/appimages/bsky-desktop"
bin_dest="$pkgdir/usr/bin/bsky-desktop"
desktop_file="$pkgdir/usr/share/applications/bsky-desktop.desktop"
icon_dest="$pkgdir/usr/share/icons/hicolor/128x128/apps/$icon_name"
if [[ ! -f "$srcdir/bskyDesktop.appimage" ]]; then
echo "Error: AppImage file not found: $srcdir/bskyDesktop.appimage"
exit 1
fi
if [[ ! -f "$srcdir/$icon_name" ]]; then
echo "Error: Icon file not found: $srcdir/$icon_name"
exit 1
fi
install -d "$pkgdir/opt/appimages"
install -Dm755 "$srcdir/bskyDesktop.appimage" "$appimage_dest"
install -d "$(dirname "$bin_dest")"
ln -sf "/opt/appimages/bsky-desktop" "$bin_dest"
install -d "$(dirname "$icon_dest")"
install -Dm644 "$srcdir/$icon_name" "$icon_dest"
install -d "$(dirname "$desktop_file")"
install -Dm644 /dev/stdin "$desktop_file" <<EOF
[Desktop Entry]
Name=Bluesky Desktop
Comment=Bluesky Desktop Client
Exec=/usr/bin/bsky-desktop %u
Icon=bsky-desktop
Terminal=false
Type=Application
Categories=Network;Social;Application;
EOF
}

295
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "bsky-desktop",
"version": "1.0.6",
"version": "1.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bsky-desktop",
"version": "1.0.6",
"version": "1.1.0",
"license": "AGPL-3.0-only",
"dependencies": {
"@electron/asar": "^3.2.17",
@@ -14,9 +14,11 @@
"adm-zip": "^0.5.12",
"axios": "^1.6.8",
"electron-window-state": "^5.0.3",
"less": "^4.2.1",
"log4js": "^6.9.1",
"node-notifier": "^10.0.0",
"semver": "^7.6.3",
"usercss-meta": "^0.12.0",
"v8-compile-cache": "^2.3.0"
},
"devDependencies": {
@@ -1324,6 +1326,18 @@
"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": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -1711,6 +1725,19 @@
"dev": true,
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
@@ -2265,7 +2292,7 @@
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -2295,6 +2322,19 @@
],
"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": {
"version": "1.0.6",
"license": "ISC",
@@ -2343,6 +2383,12 @@
"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": {
"version": "2.2.0",
"license": "MIT",
@@ -2522,6 +2568,45 @@
"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": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -2604,6 +2689,30 @@
"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": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
@@ -2723,6 +2832,23 @@
"version": "2.1.2",
"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": {
"version": "1.7.2",
"dev": true,
@@ -2797,6 +2923,15 @@
"dev": true,
"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": {
"version": "1.0.1",
"license": "MIT",
@@ -2842,6 +2977,16 @@
"version": "1.2.0",
"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": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
@@ -2894,6 +3039,13 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"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": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
@@ -3063,7 +3215,7 @@
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/sanitize-filename": {
@@ -3080,7 +3232,7 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"dev": true,
"devOptional": true,
"license": "ISC"
},
"node_modules/semver": {
@@ -3189,7 +3341,7 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"devOptional": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -3466,6 +3618,12 @@
"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": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
@@ -3517,6 +3675,15 @@
"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": {
"version": "1.0.5",
"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": {
"version": "1.0.2",
"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==",
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
@@ -5289,7 +5473,7 @@
},
"iconv-lite": {
"version": "0.6.3",
"dev": true,
"devOptional": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
@@ -5300,6 +5484,12 @@
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"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": {
"version": "1.0.6",
"requires": {
@@ -5328,6 +5518,11 @@
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"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": {
"version": "2.2.0",
"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": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
@@ -5527,6 +5747,24 @@
"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": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
@@ -5605,6 +5843,16 @@
"ms": {
"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": {
"version": "1.7.2",
"dev": true,
@@ -5656,6 +5904,11 @@
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"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": {
"version": "1.0.1"
},
@@ -5686,6 +5939,12 @@
"pend": {
"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": {
"version": "3.1.0",
"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",
"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": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
@@ -5851,7 +6116,7 @@
},
"safer-buffer": {
"version": "2.1.2",
"dev": true
"devOptional": true
},
"sanitize-filename": {
"version": "1.6.3",
@@ -5866,7 +6131,7 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"dev": true
"devOptional": true
},
"semver": {
"version": "7.6.3",
@@ -5938,7 +6203,7 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
"devOptional": true
},
"source-map-support": {
"version": "0.5.21",
@@ -6135,6 +6400,11 @@
"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": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
@@ -6167,6 +6437,11 @@
"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": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "bsky-desktop",
"version": "1.0.7",
"version": "1.1.0",
"description": "A desktop app of bsky.app",
"main": "src/app/main.js",
"scripts": {
@@ -14,6 +14,20 @@
"name": "oxmc",
"email": "oxmc7769.mail@gmail.com"
},
"contributors": [
{
"name": "oxmc",
"email": "contact@oxmc.is-a.dev"
},
{
"name": "PlOszukiwaczDEV",
"email": "ploszukiwacz1@duck.com"
},
{
"name": "GizzyUwU",
"email": "me@gizzy.pro"
}
],
"license": "AGPL-3.0-only",
"devDependencies": {
"electron": "^29.4.6",
@@ -25,9 +39,11 @@
"adm-zip": "^0.5.12",
"axios": "^1.6.8",
"electron-window-state": "^5.0.3",
"less": "^4.2.1",
"log4js": "^6.9.1",
"node-notifier": "^10.0.0",
"semver": "^7.6.3",
"usercss-meta": "^0.12.0",
"v8-compile-cache": "^2.3.0"
},
"build": {
@@ -38,14 +54,28 @@
],
"artifactName": "bsky-desktop.${ext}",
"mac": {
"icon": "src/ui/images/logo.icns",
"target": [
"dmg",
"pkg"
],
"icon": "build/icons/mac-icon.icns",
"category": "Network"
},
"pkg": {
"scripts": "build/mac-pkg/scripts",
"installLocation": "/Applications",
"allowAnywhere": true,
"allowCurrentUserHome": true,
"allowRootDirectory": true,
"isVersionChecked": true,
"isRelocatable": false,
"overwriteAction": "upgrade"
},
"linux": {
"target": [
"appimage"
],
"icon": "src/ui/images/logo.png",
"icon": "src/ui/images/icons",
"category": "Network"
},
"win": {

View File

@@ -1,4 +1,4 @@
const { app, BrowserWindow, BrowserView, globalShortcut, ipcMain, ipcRenderer, Tray, Menu, protocol, session } = require("electron");
const { app, BrowserWindow, BrowserView, globalShortcut, ipcMain, Tray, Menu, protocol, session } = require("electron");
const electronremote = require("@electron/remote/main");
//const asar = require('@electron/asar');
const windowStateKeeper = require("electron-window-state");
@@ -6,8 +6,9 @@ const { setupTitlebar, attachTitlebarToWindow } = require("./titlebar/main");
const openAboutWindow = require("./about-window/src/index").default;
const badge = require('./badge');
const contextMenu = require('./context-menu');
const asarUpdater = require('./utils/asarUpdater');
//const loadCRX = require('./utils/loadCRX');
const autoUpdater = require('./utils/auto-update');
const loadCRX = require('./utils/loadCRX');
const userStyles = require('./utils/userStyles');
const log4js = require("log4js");
const path = require("path");
const fs = require("fs");
@@ -36,6 +37,8 @@ global.paths = {
temp: path.join(os.tmpdir(), global.appInfo.name),
};
global.paths.updateDir = path.join(global.paths.data, 'update');
global.paths.extensions = path.join(global.paths.data, 'extensions');
global.paths.userstyles = path.join(global.paths.data, 'userstyles');
// URLs:
global.urls = {
@@ -44,11 +47,11 @@ global.urls = {
// Settings urls:
global.settings = {
general: `${global.urls.main}/settings`,
account: `${global.urls.main}/settings/account`,
appearance: `${global.urls.main}/settings/appearance`,
privacy: `${global.urls.main}/settings/privacy-and-security`,
general: `${global.urls.main}/settings`
};
global.settings.account = `${global.settings.general}/account`;
global.settings.appearance = `${global.settings.general}/appearance`;
global.settings.privacy = `${global.settings.general}/privacy-and-security`;
// Badge options:
const badgeOptions = {
@@ -119,6 +122,18 @@ if (!fs.existsSync(global.paths.updateDir)) {
fs.mkdirSync(global.paths.updateDir, { recursive: true });
};
// Create extensions directory if it does not exist:
if (!fs.existsSync(global.paths.extensions)) {
logger.info("Creating Extensions Directory");
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?
if (process.platform !== "win32" && process.platform !== "darwin") {
logger.log("Disabling Hardware Acceleration and Transparent Visuals");
@@ -267,7 +282,7 @@ function createWindow() {
};
});
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' };
});
// Log PageView navigations:
@@ -287,6 +302,7 @@ function showAboutWindow() {
//open_devtools: process.env.NODE_ENV !== 'production',
use_version_info: [
['Application Version', `${global.appInfo.version}`],
['Contributors', packageJson.contributors.map((contributor) => contributor.name).join(', ')],
],
license: `MIT, GPL-2.0, GPL-3.0, ${global.appInfo.license}`,
});
@@ -370,7 +386,7 @@ function handleDeeplink(commandLine) {
break;
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;
default:
@@ -483,64 +499,7 @@ app.whenReady().then(() => {
// Initialize the updater:
logger.log("Initializing Updater");
asarUpdater.init();
// updater events:
asarUpdater.on('available', (task) => {
//console.log('Update availible for', task)
logger.log("Update availible for", task.name);
global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update', message: 'An update is available' }));
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Available', subtitle: 'An update is available! Downloading...' });
global.isUpdating = true;
});
asarUpdater.on('not-available', (task) => {
//console.log('not-available', task);
logger.log("No Updates Available for", task);
});
asarUpdater.on('progress', (task, p) => {
console.log(task.name, p);
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Downloading Update', subtitle: 'Downloading update...' });
if (global.splash) global.splash.webContents.send('ui:progbar', { reason: 'update', prog: p });
});
asarUpdater.on('downloaded', (task) => {
//console.log('downloaded', task);
logger.log("Downloaded Update for,", task.name);
global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update Downloaded', message: 'Restarting to apply update...' }));
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Downloaded', subtitle: 'Restarting to apply update...' });
});
asarUpdater.on('completed', (manifest, tasks) => {
console.log('completed', manifest, tasks);
if (tasks.length === 0) {
setTimeout(() => {
logger.log("Quitting and Installing Update");
asarUpdater.quitAndInstall();
}, 5000);
};
//app.quit()
});
asarUpdater.on('error', (err) => {
//console.error(err);
logger.error(err);
//app.quit()
});
// Set the feed URL (only works in packaged app):
if (app.isPackaged) {
logger.log("Setting Feed URL for app.asar");
asarUpdater.setFeedURL(path.join(global.paths.app_root), 'https://cdn.oxmc.me/internal/bsky-desktop/update/core');
};
//Check for updates:
logger.log("Checking for Updates");
if (app.isPackaged) {
const UPDATE_CHECK = 1000 * 60 * 60 * 4 // 4 hours
setInterval(() => {
//asarUpdater.checkForUpdates();
}, UPDATE_CHECK);
//asarUpdater.checkForUpdates();
} else {
logger.warn("Not checking for updates as app is not packaged");
};
autoUpdater();
// Handle ipc for render:
ipcMain.on('close-app', (event, arg) => {
@@ -549,6 +508,70 @@ app.whenReady().then(() => {
createWindow();
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(`Loaded 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 && 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 {
logger.log("Failed to get singleInstanceLock, Quitting");
app.quit();

View File

@@ -0,0 +1,157 @@
const path = require('path');
const os = require('os');
const fs = require('fs');
const childProcess = require('child_process');
const { app } = require('electron');
const log4js = require('log4js');
// import the asarUpdater module
const asarUpdater = require('./asarUpdater');
// Get system information
const SystemInfo = require('./sysInfo');
// Get the current system platform
const sys = new SystemInfo();
// Setup the logger
const logger = log4js.getLogger("bskydesktop");
function asarUpdate() {
asarUpdater.init();
// updater events:
asarUpdater.on('available', (task) => {
//console.log('Update availible for', task)
logger.log("Update availible for", task.name);
global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update', message: 'An update is available' }));
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Available', subtitle: 'An update is available! Downloading...' });
global.isUpdating = true;
});
asarUpdater.on('not-available', (task) => {
//console.log('not-available', task);
logger.log("No Updates Available for", task);
});
asarUpdater.on('progress', (task, p) => {
console.log(task.name, p);
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Downloading Update', subtitle: 'Downloading update...' });
if (global.splash) global.splash.webContents.send('ui:progbar', { reason: 'update', prog: p });
});
asarUpdater.on('downloaded', (task) => {
//console.log('downloaded', task);
logger.log("Downloaded Update for,", task.name);
global.PageView.webContents.send('ui:notif', JSON.stringify({ title: 'Update Downloaded', message: 'Restarting to apply update...' }));
if (global.splash) global.splash.webContents.send('ui:progtext', { title: 'Update Downloaded', subtitle: 'Restarting to apply update...' });
});
asarUpdater.on('completed', (manifest, tasks) => {
console.log('completed', manifest, tasks);
if (tasks.length === 0) {
setTimeout(() => {
logger.log("Quitting and Installing Update");
asarUpdater.quitAndInstall();
}, 5000);
};
//app.quit()
});
asarUpdater.on('error', (err) => {
//console.error(err);
logger.error(err);
//app.quit()
});
// Set the feed URL (only works in packaged app):
if (app.isPackaged) {
logger.log("Setting Feed URL for app.asar");
asarUpdater.setFeedURL(path.join(global.paths.app_root), 'https://cdn.oxmc.me/internal/bsky-desktop/update/core');
};
//Check for updates:
logger.log("Checking for Updates");
if (app.isPackaged) {
const UPDATE_CHECK = 1000 * 60 * 60 * 4 // 4 hours
setInterval(() => {
//asarUpdater.checkForUpdates();
}, UPDATE_CHECK);
//asarUpdater.checkForUpdates();
} else {
logger.warn("Not checking for updates as app is not packaged");
};
}
function checkForUpdates() {
// Current system information
logger.log('Current system information:', sys.platform, sys.getVersion());
// Check if the current system is Windows
if (sys.isWin()) {
// Check if the system is before Windows 10
if (sys.earlierThan('10.0.0')) {
// Windows 10 and above are supported, but windows 7 and 8 are not supported
logger.error('Windows 7 and 8 are not supported, please upgrade to Windows 10 or above, not updating...');
} else {
// Check for updates, and if there are updates, download and install them
logger.log('Checking for updates (win)...');
}
}
// Check if the current system is macOS
if (sys.isMac()) {
let macArch = '';
// Check the current version of macOS, and whether we can use the pkg installer
if (sys.laterThan('10.0.0')) {
// Check if system is after macOS 10 (11, 12, etc.)
if (sys.laterThan('11.0.0')) {
// macOS 11 and above support ARM64, check if system is ARM64
logger.log('Checkking system architecture...');
if (sys.isARM64()) {
// System is ARM64 (mac-arm64)
macArch = 'arm64';
} else {
// System is x64 (mac-x64)
macArch = 'x64';
}
} else {
// macOS 10 is mostly x64, but some versions are ARM64
macArch = 'x64';
}
logger.log('System architecture:', macArch);
// Check for updates, and if there are updates, download and install them
logger.log('Checking for updates (mac)...');
// Run the .pkg installer
const pkgPath = path.join(global.paths.updateDir, 'bsky-desktop.pkg');
const command = `sudo installer -pkg ${pkgPath} -target /`;
// Spawn a new shell
/*const shellProcess = spawn('sh', ['-c', command], {
stdio: 'inherit', // Pipe input/output to/from the shell
});
shellProcess.on('error', (err) => {
console.error('Failed to spawn shell:', err);
});
shellProcess.on('close', (code) => {
if (code === 0) {
console.log('Update installed successfully.');
} else {
console.error(`Shell process exited with code ${code}.`);
}
});*/
} else {
// macOS versions before 10 are not supported
logger.error('macOS versions before 10 are not supported, not updating...');
}
}
// Check if the current system is Linux
if (sys.isLinux()) {
// Check for updates, and if there are updates, download and install them (no system version check)
// Linux versions use AppImage, so we instead need to check for a new asar file so that the app can
// load the new asar instead of the packaged one (or even just delete the old appimage and download a new one)
logger.log('Checking for updates (linux)...');
asarUpdate();
}
}
module.exports = checkForUpdates;

View File

@@ -3,30 +3,91 @@ const path = require('path');
const { session } = require('electron');
const AdmZip = require('adm-zip');
/**
* Converts a CRX file buffer to a ZIP buffer.
* @param {Buffer} buf - The CRX file buffer.
* @returns {Buffer} - The ZIP buffer extracted from the CRX file.
*/
function crxToZip(buf) {
function calcLength(a, b, c, d) {
let length = 0;
length += a << 0;
length += b << 8;
length += c << 16;
length += (d << 24) >>> 0;
return length;
}
// Check if the file is already a ZIP file
if (buf[0] === 80 && buf[1] === 75 && buf[2] === 3 && buf[3] === 4) {
return buf;
}
// Validate CRX magic number
if (buf[0] !== 67 || buf[1] !== 114 || buf[2] !== 50 || buf[3] !== 52) {
throw new Error('Invalid CRX file: Missing Cr24 magic number');
}
const version = buf[4];
const isV2 = version === 2;
const isV3 = version === 3;
if ((!isV2 && !isV3) || buf[5] || buf[6] || buf[7]) {
throw new Error('Unsupported CRX format version.');
}
if (isV2) {
const publicKeyLength = calcLength(buf[8], buf[9], buf[10], buf[11]);
const signatureLength = calcLength(buf[12], buf[13], buf[14], buf[15]);
const zipStartOffset = 16 + publicKeyLength + signatureLength;
return buf.slice(zipStartOffset);
}
const headerSize = calcLength(buf[8], buf[9], buf[10], buf[11]);
const zipStartOffset = 12 + headerSize;
return buf.slice(zipStartOffset);
}
/**
* Unpacks a .crx file and loads it as an Electron extension.
* @param {string} crxPath - Path to the .crx file.
* @returns {Promise<string>} - Resolves with the extension ID after loading.
*/
async function loadCRX(crxPath) {
const outputDir = path.join(__dirname, 'extensions', path.basename(crxPath, '.crx'));
const outputDir = path.join(global.paths.extensions, path.basename(crxPath, '.crx'));
// Ensure the output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
// Extract the .crx file
// Read the CRX file
const crxData = fs.readFileSync(crxPath);
const crxHeaderSize = crxData.readUInt32LE(8); // Extract header size from CRX
const zipData = crxData.slice(crxHeaderSize);
// Save the ZIP content
// Convert CRX to ZIP
const zipData = crxToZip(crxData);
// Extract ZIP using AdmZip
const zip = new AdmZip(zipData);
zip.extractAllTo(outputDir, true);
zip.getEntries().forEach((entry) => {
const fullPath = path.join(outputDir, entry.entryName);
if (entry.isDirectory) {
fs.mkdirSync(fullPath, { recursive: true });
} else {
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, entry.getData());
}
});
}
// Load the unpacked extension into Electron
try {
// Check for manifest.json
const manifestPath = path.join(outputDir, 'manifest.json');
if (!fs.existsSync(manifestPath)) {
throw new Error('Extension is missing manifest.json');
};
// Load the extension
const { id } = await session.defaultSession.loadExtension(outputDir);
console.log(`Extension loaded with ID: ${id}`);
return id;

View File

@@ -5,6 +5,7 @@ class SystemInfo {
constructor() {
this.platform = os.platform(); // 'win32', 'darwin', 'linux'
this.release = os.release(); // OS version
this.arch = os.arch(); // 'arm', 'arm64', 'x64', 'x86'
this.versionInfo = this._getVersionInfo(); // Parsed version
}
@@ -23,6 +24,26 @@ class SystemInfo {
return this.platform === 'linux';
}
// Check if current system architecture is ARM
isARM() {
return this.arch === 'arm';
}
// Check if current system architecture is ARM64
isARM64() {
return this.arch === 'arm64';
}
// Check if current system architecture is x64
isX64() {
return this.arch === 'x64';
}
// Check if current system architecture is x86
isX86() {
return this.arch === 'x86';
}
// Compare if current version is later than the given version
laterThan(compareVersion) {
const current = this.versionInfo;
@@ -35,6 +56,11 @@ class SystemInfo {
return false;
}
// Compare if current version is earlier than the given version
earlierThan(compareVersion) {
return !this.laterThan(compareVersion);
}
// Private: Parse version strings (e.g., "10.0.19045" -> [10, 0, 19045])
_parseVersion(version) {
return version.split('.').map((num) => parseInt(num, 10) || 0);
@@ -57,13 +83,20 @@ class SystemInfo {
return [0, 0, 0]; // Unknown system
}
}
// Get current version as a string (e.g., "10.15.7")
getVersion() {
return this.versionInfo.join('.');
}
}
module.exports = SystemInfo;
// Usage Example
//const sys = new SystemInfo();
//console.log(`Is Windows: ${sys.isWin()}`);
//console.log(`Is macOS: ${sys.isMac()}`);
//console.log(`Is Linux: ${sys.isLinux()}`);
//console.log(`Current Version Info: ${sys.versionInfo.join('.')}`);
//console.log(`Current Version Info: ${sys.getVersion()}`);
//console.log(`Later than 10.0.19044: ${sys.laterThan('10.0.19044')}`);
//console.log(`Later than 5.15.0 (Linux Kernel): ${sys.laterThan('5.15.0')}`);

210
src/app/utils/userStyles.js Normal file
View 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
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View File

@@ -47,10 +47,14 @@ document.addEventListener('DOMContentLoaded', () => {
// Load jQuery first
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', () => {
// Load app specific scripts
loadScript('ui:///rend/register-handles.js');
// Load custom implementations
loadScript('ui:///rend/bsky-ext.js');
loadScript('ui:///rend/specialAnimations.js');
});
});
});

View File

@@ -374,6 +374,15 @@ const BskyExt = {
},
"regex": /discord\.com\/invite\/([a-zA-Z0-9_]+)/
},
"discord_profile": {
"name": "Discord",
"type": "social",
"icon": "fab fa-discord",
"brand": {
"color": "#7289DA",
},
"regex": /discord\.com\/users\/([a-zA-Z0-9_]+)/
},
"discord.gg": {
"name": "Discord",
"type": "messaging",
@@ -473,6 +482,33 @@ const BskyExt = {
},
"regex": /irc:\/\/([a-zA-Z0-9_]+)/
},
"itchio": {
"name": "Itch.io",
"type": "content",
"icon": "fab fa-itch-io",
"brand": {
"color": "#FA5C5C",
},
"regex": /([a-zA-Z0-9_]+)\.itch\.io/
},
"etsy_shop": {
"name": "Etsy",
"type": "content",
"icon": "fab fa-etsy",
"brand": {
"color": "#D5641C",
},
"regex": /etsy\.com\/shop\/([a-zA-Z0-9_]+)/
},
"etsy_user": {
"name": "Etsy",
"type": "content",
"icon": "fab fa-etsy",
"brand": {
"color": "#D5641C",
},
"regex": /([a-zA-Z0-9_]+)\.etsy\.com/
},
"email": {
"name": "Email",
"type": "messaging",
@@ -498,7 +534,43 @@ const BskyExt = {
"brand": {
"color": "#1DA1F2",
},
"regex": /twitter\.com\/([a-zA-Z0-9_]+)/
"regex": /(twitter\.com|x\.com)\/([a-zA-Z0-9_]+)\/?$/
},
"xbox": {
"name": "Xbox",
"type": "social",
"icon": "fab fa-xbox",
"brand": {
"color": "#107C10",
},
"regex": /xbox\.com\/([a-zA-Z0-9_]+)/
},
"playstation": {
"name": "PlayStation",
"type": "social",
"icon": "fab fa-playstation",
"brand": {
"color": "#003087",
},
"regex": /playstation\.com\/([a-zA-Z0-9_]+)/
},
"steam": {
"name": "Steam",
"type": "content",
"icon": "fab fa-steam",
"brand": {
"color": "#000000",
},
"regex": /steamcommunity\.com\/id\/([a-zA-Z0-9_]+)/
},
"tiktok": {
"name": "TikTok",
"type": "social",
"icon": "fab fa-tiktok",
"brand": {
"color": "#000000",
},
"regex": /tiktok\.com\/@([a-zA-Z0-9_]+)/
},
"instagram": {
"name": "Instagram",
@@ -729,6 +801,72 @@ const BskyExt = {
}
},
/**
* @constant {Object} linkStylesOverrides - Style overrides for specific link types
*/
linkStylesOverrides: {
"bluesky": {
"default": {
"color": "rgb(0, 133, 255)",
"background": "#0000",
"border": "#0000",
},
"hover": {
"color": "#fff",
"background": "#0000",
"border": "#0000"
}
},
"discord": {
"default": {
"color": "#FFFFFF",
"background": "#7289DA",
"border": "#7289DA",
},
"hover": {
"color": "#7289DA",
"background": "#FFFFFF",
"border": "#7289DA"
}
},
"linktree": {
"default": {
"color": "#FFFFFF",
"background": "#39e09b",
"border": "#39e09b",
},
"hover": {
"color": "#39e09b",
"background": "#FFFFFF",
"border": "#39e09b"
}
},
"carrd": {
"default": {
"color": "#FFFFFF",
"background": "#2C2F33",
"border": "#2C2F33",
},
"hover": {
"color": "#2C2F33",
"background": "#FFFFFF",
"border": "#2C2F33"
}
},
"dribbble": {
"default": {
"color": "#FFFFFF",
"background": "#EA4C89",
"border": "#EA4C89",
},
"hover": {
"color": "#EA4C89",
"background": "#FFFFFF",
"border": "#EA4C89"
}
},
},
/**
* @constant {Object} buttonStyles - The button styles for the supported messaging apps
*
@@ -1109,7 +1247,7 @@ const BskyExt = {
let header = bio.closest("[data-testid='profileView']");
// Get the profile header button group element
let buttonGroup = header.querySelector('[role="button"]').parentElement;
let buttonGroup = header.querySelector("div.css-175oi2r.r-2llsf > div:nth-child(1) > div > div:nth-child(2) > div.css-175oi2r.r-12vffkv");
// Check if the profile header button group element exists
if (!buttonGroup) {

View File

@@ -20,7 +20,7 @@ document.addEventListener('DOMContentLoaded', () => {
message: event.message,
position: 'topRight',
timeout: 5000,
...event.options,
...event.options.izitoast,
});
} catch (error) {
console.error('Failed to display notification:', error);

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