diff --git a/.docs/Troubleshooting.md b/.docs/Troubleshooting.md
index 76e206a3..024a8dbb 100644
--- a/.docs/Troubleshooting.md
+++ b/.docs/Troubleshooting.md
@@ -146,6 +146,12 @@ Red Hat: `cert-sync --user /etc/pki/tls/certs/ca-bundle.crt`
If it still doesn't work, try mozroots: `mozroots --import --ask-remove`
+## macOS-specific
+
+### DiscordChatExporter is damaged and can’t be opened. You should move it to the Trash.
+
+Check the [Using the GUI page](Using-the-GUI.md#step-1) for instructions on how to run the app.
+
---
> ❓ If you still have unanswered questions, feel free to [create a new discussion](https://github.com/Tyrrrz/DiscordChatExporter/discussions/new).
diff --git a/.docs/Using-the-GUI.md b/.docs/Using-the-GUI.md
index 27417f72..d175765b 100644
--- a/.docs/Using-the-GUI.md
+++ b/.docs/Using-the-GUI.md
@@ -10,7 +10,22 @@
### Step 1
-After extracting the `.zip`, run `DiscordChatExporter.exe` (Windows), or `DiscordChatExporter` (Mac OS and Linux).
+After extracting the `.zip`, run `DiscordChatExporter.exe` **(Windows)**, or `DiscordChatExporter` **(Linux)**.
+
+If you're using **macOS**, you'll need to manually grant permission for the app to run.
+If you skip these steps, the "DiscordChatExporter is damaged and can’t be opened" error will be shown.
+
+1. Open Terminal.app. You can search for it in Spotlight (press ⌘ + Space and type "Terminal").
+2. Paste the following into the terminal window:
+ ```bash
+ xattr -rd com.apple.quarantine
+ ```
+3. Hit Space once to add a space after the command
+4. Drag and drop DiscordChatExporter.app into the terminal window
+5. Press Return to run the command
+6. Open DiscordChatExporter.app normally
+
+> Apple requires apps to be notarized and signed in order to run on macOS without warnings, which in turn requires an Apple Developer membership ($99/year). This open-source project is distributed for free and without commercial intent.
### Step 2
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5bea0629..cb5e2054 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -138,6 +138,16 @@ jobs:
--runtime ${{ matrix.rid }}
--self-contained
+ - name: Generate macOS .app bundle resources
+ if: ${{ startsWith(matrix.rid, 'osx-') && matrix.app == 'DiscordChatExporter.Gui' }}
+ shell: pwsh
+ run: >
+ ./bundle-macos-app.ps1
+ -BundleName "${{ matrix.asset }}"
+ -PublishDir "${{ matrix.app }}/bin/publish/"
+ -Version "${{ github.ref_type == 'tag' && github.ref_name || '999.9.9'}}"
+ -GitHubSha "${{ github.sha }}"
+
- name: Upload artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
@@ -210,9 +220,13 @@ jobs:
path: ${{ matrix.app }}/
- name: Set permissions
- if: ${{ !startsWith(matrix.rid, 'win-') }}
+ if: ${{ !startsWith(matrix.rid, 'win-') && !(startsWith(matrix.rid, 'osx-') && matrix.app == 'DiscordChatExporter.Gui') }}
run: chmod +x ${{ matrix.app }}/${{ matrix.asset }}
+ - name: Set permissions for macOS .app bundle
+ if: ${{ startsWith(matrix.rid, 'osx-') && matrix.app == 'DiscordChatExporter.Gui' }}
+ run: chmod +x ${{ matrix.app }}/${{ matrix.asset }}.app/Contents/MacOS/${{ matrix.asset }}
+
- name: Create package
# Change into the artifacts directory to avoid including the directory itself in the zip archive
working-directory: ${{ matrix.app }}/
diff --git a/bundle-macos-app.ps1 b/bundle-macos-app.ps1
new file mode 100644
index 00000000..343cdfd4
--- /dev/null
+++ b/bundle-macos-app.ps1
@@ -0,0 +1,71 @@
+param(
+ [Parameter(Mandatory=$true)]
+ [string]$BundleName,
+
+ [Parameter(Mandatory=$true)]
+ [string]$PublishDir,
+
+ [Parameter(Mandatory=$true)]
+ [string]$Version,
+
+ [Parameter(Mandatory=$true)]
+ [string]$GitHubSha
+)
+
+# Setup paths
+$appName = "$BundleName.app"
+$appDir = Join-Path "bundle-macos-app-staging" $appName
+$contentsDir = Join-Path $appDir "Contents"
+$macosDir = Join-Path $contentsDir "MacOS"
+$resourcesDir = Join-Path $contentsDir "Resources"
+
+# Create the macOS .app bundle directory structure
+New-Item -ItemType Directory -Path $macosDir -Force
+New-Item -ItemType Directory -Path $resourcesDir -Force
+
+# Copy icon into the .app's Resources folder
+Copy-Item -Path "favicon.icns" -Destination (Join-Path $resourcesDir "AppIcon.icns") -Force
+
+# Generate Info.plist metadata file with app information
+$plistContent = @"
+
+
+
+
+ CFBundleDisplayName
+ $BundleName
+ CFBundleName
+ $BundleName
+ CFBundleExecutable
+ $BundleName
+ NSHumanReadableCopyright
+ © Oleksii Holub
+ CFBundleIdentifier
+ me.Tyrrrz.$BundleName
+ CFBundleSpokenName
+ Discord Chat Exporter
+ CFBundleIconFile
+ AppIcon
+ CFBundleIconName
+ AppIcon
+ CFBundleVersion
+ $GitHubSha
+ CFBundleShortVersionString
+ $Version
+ NSHighResolutionCapable
+
+ CFBundlePackageType
+ APPL
+
+
+"@
+
+Set-Content -Path (Join-Path $contentsDir "Info.plist") -Value $plistContent
+
+# Move all files from the publish directory into the MacOS directory
+Get-ChildItem -Path $PublishDir | ForEach-Object {
+ Move-Item -Path $_.FullName -Destination $macosDir -Force
+}
+
+# Move the final .app bundle into the publish directory for upload
+Move-Item -Path $appDir -Destination $PublishDir -Force
\ No newline at end of file
diff --git a/favicon.icns b/favicon.icns
new file mode 100644
index 00000000..85a0d257
Binary files /dev/null and b/favicon.icns differ