Compare commits

150 Commits

Author SHA1 Message Date
GitHub Actions
afc4ba1087 chore: bump version to 4.11.191 [skip ci] 2026-08-24 14:03:10 +00:00
f022155aa1 Update responsive image attachments
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 16s
Build / build-windows (push) Successful in 7m8s
Build / build-linux (push) Successful in 3m10s
Build / finalize-release (push) Successful in 6s
Advance Cinny to the responsive message image fix.
2026-08-25 00:02:49 +10:00
GitHub Actions
f4ec39f229 chore: bump version to 4.11.190 [skip ci] 2026-08-24 13:58:49 +00:00
848283da42 Add TikTok player embed
Some checks failed
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-linux (push) Has been cancelled
Build / finalize-release (push) Has been cancelled
Build / build-windows (push) Has been cancelled
Use TikTok's supported iframe player and safely invoke yt-dlp for YouTube streams.
2026-08-24 23:58:35 +10:00
GitHub Actions
2cd9409e3d chore: bump version to 4.11.189 [skip ci] 2026-08-24 10:07:31 +00:00
b85774e911 Bump cinny for Capacitor HTTP.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Successful in 6m37s
Build / build-linux (push) Successful in 3m10s
Build / finalize-release (push) Successful in 4s
Include the native HTTP configuration required for mobile collectible catalog downloads.
2026-08-24 20:07:17 +10:00
GitHub Actions
73d31a2cb4 chore: bump version to 4.11.188 [skip ci] 2026-08-24 09:53:09 +00:00
f9bc1c7c2f Bump cinny for mobile collectibles support.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Successful in 6m44s
Build / build-linux (push) Successful in 3m6s
Build / finalize-release (push) Successful in 4s
Use the mobile catalog and CDN fallback for profile cosmetics outside Electron.
2026-08-24 19:52:54 +10:00
GitHub Actions
85995d0744 chore: bump version to 4.11.187 [skip ci] 2026-08-24 09:30:06 +00:00
9d7ae1d63b Use published Discord collectibles catalog.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Successful in 6m54s
Build / build-linux (push) Successful in 3m5s
Build / finalize-release (push) Successful in 4s
Fetch and persist the public release catalog locally while retiring client-side Discord token handling.
2026-08-24 19:29:53 +10:00
GitHub Actions
ce7b2104ad chore: bump version to 4.11.186 [skip ci] 2026-08-24 08:49:07 +00:00
60d65a29ff Bump cinny for configurable sidebar nameplate background.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Successful in 6m49s
Build / build-linux (push) Failing after 3m18s
Build / finalize-release (push) Successful in 4s
Include immediate collectible rendering updates and sidebar opacity controls.
2026-08-24 18:48:53 +10:00
GitHub Actions
ce29b24c28 chore: bump version to 4.11.185 [skip ci] 2026-08-23 14:55:15 +00:00
608cbaf682 Add Discord collectibles IPC and bump cinny for profile overlays.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 15s
Build / build-windows (push) Successful in 6m55s
Build / build-linux (push) Successful in 3m10s
Build / finalize-release (push) Successful in 4s
Wire electron-side catalog fetch and asset download, exposing collectibles APIs to the renderer for live Discord shop browsing and Matrix profile uploads.
2026-08-24 00:55:01 +10:00
GitHub Actions
8ca7be8b42 chore: bump version to 4.11.184 [skip ci] 2026-08-23 06:55:17 +00:00
3c55fe65a3 Bump cinny: What's New dialog fixes and readable dev class names.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Successful in 7m2s
Build / build-linux (push) Successful in 3m4s
Build / finalize-release (push) Successful in 4s
2026-08-23 16:55:04 +10:00
GitHub Actions
f99b3c85d4 chore: bump version to 4.11.183 [skip ci] 2026-08-23 06:18:56 +00:00
fc4f087c68 Bump cinny for Android About/updates fixes and overlay user color crash guard.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Successful in 6m40s
Build / build-linux (push) Successful in 3m3s
Build / finalize-release (push) Successful in 4s
2026-08-23 16:18:43 +10:00
GitHub Actions
6d347a6f6b chore: bump version to 4.11.182 [skip ci] 2026-08-22 22:38:44 +00:00
316a5514e6 Bump cinny for Android View updates dialog fix.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 15s
Build / build-windows (push) Successful in 6m44s
Build / build-linux (push) Successful in 3m2s
Build / finalize-release (push) Successful in 4s
2026-08-23 08:38:29 +10:00
GitHub Actions
8392e3f7f5 chore: bump version to 4.11.181 [skip ci] 2026-08-22 22:26:52 +00:00
61b3cbc6d8 Point desktop build at cinny-local update manifest script for mobile CI.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 15s
Build / build-windows (push) Successful in 6m46s
Build / build-linux (push) Successful in 3m1s
Build / finalize-release (push) Successful in 7s
2026-08-23 08:26:33 +10:00
GitHub Actions
9befaa14a5 chore: bump version to 4.11.180 [skip ci] 2026-08-22 22:23:43 +00:00
ea45c4551b Bump cinny for What's New auto-show on first launch after update.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 14s
Build / finalize-release (push) Has been cancelled
Build / build-windows (push) Has been cancelled
Build / build-linux (push) Has been cancelled
2026-08-23 08:23:23 +10:00
GitHub Actions
146927fc10 chore: bump version to 4.11.179 [skip ci] 2026-08-22 22:20:20 +00:00
8edbc1b19a Bump cinny for Android updates dialog fix and add release notes manifest workflow.
Some checks failed
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Has started running
Build / finalize-release (push) Has been cancelled
Build / build-linux (push) Has been cancelled
2026-08-23 08:20:07 +10:00
GitHub Actions
9700c645a3 chore: bump version to 4.11.178 [skip ci] 2026-08-22 22:07:17 +00:00
2828ae425c Add native media save dialog and bump cinny submodule.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Successful in 6m51s
Build / build-linux (push) Successful in 3m11s
Build / finalize-release (push) Successful in 5s
Wire Electron/Tauri save-file IPC for desktop downloads and update the cinny submodule with username colors, release notes, and account profile layout fixes.
2026-08-23 08:07:02 +10:00
GitHub Actions
5e06256973 chore: bump version to 4.11.177 [skip ci] 2026-08-09 08:35:32 +00:00
28c7dedf65 Bump cinny for fullscreen sick emoji confetti spew.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Successful in 7m35s
Build / build-linux (push) Successful in 3m13s
Build / finalize-release (push) Successful in 4s
2026-08-09 18:35:17 +10:00
GitHub Actions
ca10121796 chore: bump version to 4.11.176 [skip ci] 2026-08-09 05:59:10 +00:00
1ad1fc7b70 Bump cinny for shadowbox image viewer.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 15s
Build / build-windows (push) Successful in 7m31s
Build / build-linux (push) Successful in 3m16s
Build / finalize-release (push) Successful in 6s
2026-08-09 15:58:52 +10:00
GitHub Actions
f16b84a9f1 chore: bump version to 4.11.175 [skip ci] 2026-08-09 05:50:38 +00:00
e917193b0d Harden Linux clipboard image paste and bump cinny.
Some checks failed
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 15s
Build / build-windows (push) Successful in 7m28s
Build / finalize-release (push) Has been cancelled
Build / build-linux (push) Has been cancelled
Require a real image/* clipboard format before treating paste as an image, and pick up firework confetti plus URL-paste fixes from cinny.
2026-08-09 15:50:23 +10:00
GitHub Actions
fc5d498673 chore: bump version to 4.11.174 [skip ci] 2026-08-09 03:17:02 +00:00
c01bd9a43e Bump cinny for bot-driven room app UI.
All checks were successful
Build / increment-version (push) Successful in 14s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Successful in 8m3s
Build / build-linux (push) Successful in 3m22s
Build / finalize-release (push) Successful in 4s
2026-08-09 13:16:48 +10:00
GitHub Actions
3f4b1b3fc4 chore: bump version to 4.11.173 [skip ci] 2026-08-03 05:22:25 +00:00
24b59a454a Ensure wine binary exists for Windows NSIS cross-builds.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-linux (push) Successful in 2m58s
Build / build-windows (push) Successful in 5m46s
Build / finalize-release (push) Successful in 4s
2026-08-03 15:22:12 +10:00
GitHub Actions
fac9a49d56 chore: bump version to 4.11.172 [skip ci] 2026-08-03 05:16:00 +00:00
83993efc83 Run Windows Electron builds on Linux with Wine.
Some checks failed
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-linux (push) Successful in 3m2s
Build / build-windows (push) Failing after 4m57s
Build / finalize-release (push) Successful in 4s
2026-08-03 15:15:46 +10:00
GitHub Actions
44a9230df3 chore: bump version to 4.11.171 [skip ci] 2026-08-03 05:08:38 +00:00
658ff1215c Bump cinny: local API message and unread endpoints by scope.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 14s
Build / build-linux (push) Successful in 2m55s
Build / build-windows (push) Successful in 3m54s
Build / finalize-release (push) Successful in 5s
2026-08-03 15:08:25 +10:00
GitHub Actions
829e9b06cb chore: bump version to 4.11.170 [skip ci] 2026-07-29 10:41:07 +00:00
e64279db74 Bump cinny: emoji confetti bursts and component playground.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 13s
Build / build-linux (push) Successful in 3m13s
Build / build-windows (push) Successful in 6m11s
Build / finalize-release (push) Successful in 4s
Wire the playground npm script through the desktop wrapper.
2026-07-29 20:40:53 +10:00
GitHub Actions
85d95faf11 chore: bump version to 4.11.169 [skip ci] 2026-07-25 10:09:49 +00:00
61de658029 Bump cinny: fix avatar upload for JPEG/WebP/GIF without profile color.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 16s
Build / build-linux (push) Successful in 3m21s
Build / build-windows (push) Successful in 5m45s
Build / finalize-release (push) Successful in 4s
2026-07-25 20:09:35 +10:00
GitHub Actions
c8b57ba217 chore: bump version to 4.11.168 [skip ci] 2026-07-24 06:52:49 +00:00
8d8809d0c7 Bump cinny: open DMs while space parent maps catch up.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 14s
Build / build-linux (push) Successful in 2m44s
Build / build-windows (push) Successful in 3m45s
Build / finalize-release (push) Successful in 4s
2026-07-24 16:52:35 +10:00
GitHub Actions
55a0f4f566 chore: bump version to 4.11.167 [skip ci] 2026-07-24 06:24:58 +00:00
f488f32d88 Fix PowerShell mangling GitHub upload URLs with ?name=.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 13s
Build / build-linux (push) Successful in 2m45s
Build / build-windows (push) Successful in 3m43s
Build / finalize-release (push) Successful in 4s
2026-07-24 16:24:44 +10:00
GitHub Actions
9154929de9 chore: bump version to 4.11.166 [skip ci] 2026-07-24 06:20:05 +00:00
c2cc0d5ed5 Fix PowerShell origin URL trimming for submodule checkout.
Some checks failed
Build / increment-version (push) Successful in 13s
Build / prepare-release (push) Successful in 13s
Build / build-linux (push) Successful in 2m44s
Build / build-windows (push) Failing after 3m22s
Build / finalize-release (push) Successful in 8s
2026-07-24 16:19:51 +10:00
GitHub Actions
aa38348fdd chore: bump version to 4.11.165 [skip ci] 2026-07-24 06:17:38 +00:00
02ab544a37 Fix CI Windows upload shell and submodule clone host.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 14s
Build / build-windows (push) Failing after 11s
Build / finalize-release (push) Has been cancelled
Build / build-linux (push) Has been cancelled
Use PowerShell + curl.exe on the Windows runner (no WSL), and clone cinny via a relative/origin-derived URL so act runners do not hairpin through public HTTPS.
2026-07-24 16:17:23 +10:00
GitHub Actions
3707a6d38a chore: bump version to 4.11.164 [skip ci] 2026-07-24 06:09:58 +00:00
fe1f4abf2d Bypass flaky Actions artifacts for AppImage/exe uploads.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / prepare-release (push) Successful in 13s
Build / build-windows (push) Failing after 3m27s
Build / finalize-release (push) Has been cancelled
Build / build-linux (push) Has been cancelled
Publish build outputs straight to Gitea/GitHub releases via the REST API with no HTTP timeout, since Gitea's artifact pipeline keeps timing out on ~130MB binaries.
2026-07-24 16:09:44 +10:00
147389724e Point cinny submodule at git.ruv.wtf. 2026-07-24 16:02:45 +10:00
GitHub Actions
d4d4c73c2b chore: bump version to 4.11.163 [skip ci] 2026-07-24 06:01:21 +00:00
73fbc1c77a Bump cinny: hide Jump to Latest when scrolled to live bottom.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / build-windows (push) Successful in 3m30s
Build / build-linux (push) Failing after 7m44s
Build / create-release (push) Has been cancelled
2026-07-24 16:01:06 +10:00
GitHub Actions
3dc732521f chore: bump version to 4.11.162 [skip ci] 2026-07-23 05:18:40 +00:00
b76aa3fe4a Add mock auto-updater in dev and title-bar download progress.
All checks were successful
Build / increment-version (push) Successful in 11s
Build / build-linux (push) Successful in 1m54s
Build / build-windows (push) Successful in 3m36s
Build / create-release (push) Successful in 1m13s
2026-07-23 15:18:25 +10:00
GitHub Actions
c0d1e87b8d chore: bump version to 4.11.161 [skip ci] 2026-07-23 05:14:07 +00:00
04d4026e78 Fix Linux SSO return via paarrot://desktop and protocol handler repair.
Some checks failed
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m55s
Build / build-windows (push) Successful in 3m35s
Build / create-release (push) Has been cancelled
2026-07-23 15:13:54 +10:00
GitHub Actions
cfbc59d6a9 chore: bump version to 4.11.160 [skip ci] 2026-07-23 04:58:11 +00:00
dfe42fa942 Sync cinny: remove editor autocorrect workaround.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m54s
Build / build-windows (push) Successful in 3m35s
Build / create-release (push) Successful in 1m12s
2026-07-23 14:57:58 +10:00
GitHub Actions
6a8397425e chore: bump version to 4.11.159 [skip ci] 2026-07-23 04:52:37 +00:00
641ec26142 Sync cinny: Shared Media drawer and same-room navigation fixes.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m59s
Build / build-windows (push) Successful in 3m47s
Build / create-release (push) Successful in 1m12s
2026-07-23 14:52:23 +10:00
GitHub Actions
1eaf246806 chore: bump version to 4.11.158 [skip ci] 2026-07-22 14:24:31 +00:00
0e97e4d3b6 Sync cinny: Stationery and Stationery Dark themes.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m58s
Build / build-windows (push) Successful in 3m37s
Build / create-release (push) Successful in 1m13s
2026-07-23 00:24:17 +10:00
GitHub Actions
1636a8d0c6 chore: bump version to 4.11.157 [skip ci] 2026-07-22 10:29:14 +00:00
ac236750aa Sync cinny: fix jumbo emoji regex for production builds.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m52s
Build / build-windows (push) Successful in 3m40s
Build / create-release (push) Successful in 1m12s
2026-07-22 20:29:00 +10:00
GitHub Actions
7e747e6c36 chore: bump version to 4.11.156 [skip ci] 2026-07-22 10:14:27 +00:00
1b2cae5b12 Sync cinny: fix jumbo emoji in production builds.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m56s
Build / build-windows (push) Successful in 3m42s
Build / create-release (push) Successful in 1m11s
2026-07-22 20:14:15 +10:00
GitHub Actions
76921c4e79 chore: bump version to 4.11.155 [skip ci] 2026-07-22 10:00:52 +00:00
688efff7c9 Sync cinny submodule and stop Vite from opening a browser on dev.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m55s
Build / build-windows (push) Successful in 3m36s
Build / create-release (push) Successful in 1m13s
Pull in timeline scroll memory, media height reservation, avatar caching, jumbo emoji, and Unicode 15 emoji font coverage.
2026-07-22 20:00:38 +10:00
GitHub Actions
9bc377b62a chore: bump version to 4.11.154 [skip ci] 2026-07-22 09:11:11 +00:00
836ccce7a3 Revert cinny to pre-jumbo emoji baseline.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m57s
Build / build-windows (push) Successful in 3m37s
Build / create-release (push) Successful in 1m13s
Roll back submodule to 154f4dfd before jumbo emoji and related styling regressions.
2026-07-22 19:10:58 +10:00
GitHub Actions
34c8e6e38c chore: bump version to 4.11.153 [skip ci] 2026-07-22 09:03:00 +00:00
e69bda3aa6 Bump cinny: fix production chat text color in Electron builds.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m51s
Build / build-windows (push) Successful in 3m43s
Build / create-release (push) Successful in 1m13s
2026-07-22 19:02:46 +10:00
GitHub Actions
5d55e5d826 chore: bump version to 4.11.152 [skip ci] 2026-07-22 08:37:39 +00:00
b5902b9fc3 Bump cinny: fix production CSS loading in Electron build.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m51s
Build / build-windows (push) Successful in 3m34s
Build / create-release (push) Successful in 1m12s
2026-07-22 18:37:26 +10:00
GitHub Actions
f60cf0bd3c chore: bump version to 4.11.151 [skip ci] 2026-07-22 08:21:32 +00:00
83dec0ef54 Bump cinny: fix black chat pane and jumbo emoji rendering.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m50s
Build / build-windows (push) Successful in 3m35s
Build / create-release (push) Successful in 1m12s
2026-07-22 18:21:18 +10:00
GitHub Actions
40a76f02cd chore: bump version to 4.11.150 [skip ci] 2026-07-22 08:02:08 +00:00
3def3cc456 Bump cinny: instant room switches, perf cleanup, members drawer prefs.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m53s
Build / build-windows (push) Successful in 3m42s
Build / create-release (push) Successful in 1m13s
2026-07-22 18:01:53 +10:00
GitHub Actions
30bd67d43a chore: bump version to 4.11.149 [skip ci] 2026-07-22 06:16:20 +00:00
ffeccee680 Bump cinny: media placeholders, avatar blob cache, jumbo emoji.
All checks were successful
Build / increment-version (push) Successful in 12s
Build / build-linux (push) Successful in 1m58s
Build / build-windows (push) Successful in 3m50s
Build / create-release (push) Successful in 1m13s
2026-07-22 14:42:19 +10:00
GitHub Actions
facbeee024 chore: bump version to 4.11.148 [skip ci] 2026-07-21 06:21:51 +00:00
09102c7903 Bump cinny: fix jumbo emoji actually rendering at 100px.
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m55s
Build / build-windows (push) Successful in 5m42s
Build / create-release (push) Successful in 1m13s
2026-07-21 16:21:37 +10:00
GitHub Actions
fa33ec7ac8 chore: bump version to 4.11.147 [skip ci] 2026-07-21 05:56:18 +00:00
c44f5cf14a Bump cinny: fix jumbo standalone emoji sizing.
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m57s
Build / build-windows (push) Successful in 5m21s
Build / create-release (push) Successful in 1m14s
2026-07-21 15:56:03 +10:00
GitHub Actions
27793d7c4f chore: bump version to 4.11.146 [skip ci] 2026-07-21 05:38:44 +00:00
32b2902ef7 Bump cinny: fix jumbo emoji vanilla-extract build.
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m2s
Build / build-windows (push) Successful in 3m33s
Build / create-release (push) Successful in 1m13s
2026-07-21 15:38:29 +10:00
GitHub Actions
5cdb318854 chore: bump version to 4.11.145 [skip ci] 2026-07-21 05:34:16 +00:00
a28f05bb88 Bump cinny: theme polish, jumbo emoji, and Windows autocorrect fix.
Some checks failed
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Failing after 44s
Build / build-windows (push) Failing after 1m4s
Build / create-release (push) Has been skipped
2026-07-21 15:34:00 +10:00
GitHub Actions
cecfe73b2e chore: bump version to 4.11.144 [skip ci] 2026-07-14 17:31:07 +00:00
b458c7918c chore: bump cinny submodule for emoji font spacing fix
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m3s
Build / build-windows (push) Successful in 5m10s
Build / create-release (push) Successful in 1m13s
2026-07-15 03:30:53 +10:00
GitHub Actions
d8660f4822 chore: bump version to 4.11.143 [skip ci] 2026-07-12 16:40:22 +00:00
dad83773a6 Bump cinny: Twemoji digit fix and Slate emoji focus crash
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 1m55s
Build / build-windows (push) Successful in 4m48s
Build / create-release (push) Successful in 1m12s
2026-07-13 02:40:08 +10:00
GitHub Actions
d2d35de46b chore: bump version to 4.11.142 [skip ci] 2026-07-12 16:25:25 +00:00
c752ec0b38 Bump cinny: fix noise/echo cancellation settings
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m57s
Build / build-windows (push) Successful in 5m5s
Build / create-release (push) Successful in 1m13s
2026-07-13 02:25:10 +10:00
GitHub Actions
28752eb56e chore: bump version to 4.11.141 [skip ci] 2026-07-12 08:15:59 +00:00
4f243a0fcf Bump cinny: fix vanilla-extract presence CSS build failure.
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m57s
Build / build-windows (push) Successful in 4m48s
Build / create-release (push) Successful in 1m13s
2026-07-12 18:15:44 +10:00
GitHub Actions
9bce616f26 chore: bump version to 4.11.140 [skip ci] 2026-07-11 22:19:18 +00:00
f16ab73c9a Allow git deps in npm and bump cinny submodule.
Some checks failed
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Failing after 42s
Build / build-windows (push) Failing after 1m47s
Build / create-release (push) Has been skipped
2026-07-12 08:19:03 +10:00
GitHub Actions
c29a15e073 chore: bump version to 4.11.139 [skip ci] 2026-07-11 21:52:03 +00:00
f5fe61e3eb hidden shit, and online status bars, search settings
Some checks failed
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Failing after 42s
Build / build-windows (push) Failing after 1m29s
Build / create-release (push) Has been skipped
2026-07-12 07:51:47 +10:00
GitHub Actions
a1a752b0fb chore: bump version to 4.11.138 [skip ci] 2026-07-11 21:15:56 +00:00
7769250875 Removed invalid channels
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m56s
Build / build-windows (push) Successful in 4m36s
Build / create-release (push) Successful in 1m13s
2026-07-12 07:15:28 +10:00
GitHub Actions
b3af9f3185 chore: bump version to 4.11.137 [skip ci] 2026-07-10 11:40:45 +00:00
7906326790 update base
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m56s
Build / build-windows (push) Successful in 4m56s
Build / create-release (push) Successful in 1m13s
2026-07-10 21:40:27 +10:00
GitHub Actions
3b4babb9d7 chore: bump version to 4.11.136 [skip ci] 2026-07-08 18:22:44 +00:00
4b284ec747 Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m0s
Build / build-windows (push) Successful in 3m28s
Build / create-release (push) Successful in 1m12s
2026-07-09 04:22:28 +10:00
e1b4cfb9c8 chore: bump cinny submodule for compact nav and back routing fixes 2026-07-09 04:22:14 +10:00
GitHub Actions
c0266903ec chore: bump version to 4.11.135 [skip ci] 2026-07-08 16:35:39 +00:00
81964d6f14 chore: bump cinny submodule for React Router 7 routing fixes
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 1m57s
Build / build-windows (push) Successful in 4m33s
Build / create-release (push) Successful in 1m15s
2026-07-09 02:35:18 +10:00
GitHub Actions
47e025cf55 chore: bump version to 4.11.134 [skip ci] 2026-07-08 15:56:06 +00:00
b400070ad0 chore: bump cinny submodule for sync recovery and toolchain upgrades
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m19s
Build / build-windows (push) Successful in 3m48s
Build / create-release (push) Successful in 1m14s
2026-07-09 01:55:21 +10:00
529694ce66 chore: update dependencies and improve external link handling in main.js 2026-07-09 01:52:52 +10:00
GitHub Actions
791c3ab4df chore: bump version to 4.11.133 [skip ci] 2026-07-06 06:07:10 +00:00
de8d9db795 chore: update subproject commit reference in cinny to indicate dirty state
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m52s
Build / build-windows (push) Successful in 4m32s
Build / create-release (push) Successful in 1m15s
2026-07-06 16:06:52 +10:00
GitHub Actions
ffaf9ff66a chore: bump version to 4.11.132 [skip ci] 2026-07-06 05:35:44 +00:00
f0891ba4cc Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 2m11s
Build / build-windows (push) Successful in 3m45s
Build / create-release (push) Successful in 1m11s
2026-07-06 15:35:28 +10:00
627b117119 chore: update subproject commit reference in cinny 2026-07-06 15:35:27 +10:00
GitHub Actions
b6c6659a5a chore: bump version to 4.11.131 [skip ci] 2026-07-05 17:29:38 +00:00
d7a73afebc Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 2m16s
Build / build-windows (push) Successful in 3m53s
Build / create-release (push) Successful in 1m7s
2026-07-06 03:29:23 +10:00
e469d51ba4 fix: improve navigation handler in main.js to better manage external links 2026-07-06 03:29:19 +10:00
GitHub Actions
6ba03277e5 chore: bump version to 4.11.130 [skip ci] 2026-07-05 15:30:44 +00:00
fdb2dca794 fix: update subproject status to dirty in cinny
All checks were successful
Build / increment-version (push) Successful in 13s
Build / build-linux (push) Successful in 2m12s
Build / build-windows (push) Successful in 4m53s
Build / create-release (push) Successful in 1m11s
2026-07-06 01:30:28 +10:00
GitHub Actions
8797bb5a22 chore: bump version to 4.11.129 [skip ci] 2026-07-05 15:07:08 +00:00
4d154fd4f3 Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
All checks were successful
Build / increment-version (push) Successful in 36s
Build / build-linux (push) Successful in 2m15s
Build / build-windows (push) Successful in 4m8s
Build / create-release (push) Successful in 1m12s
2026-07-06 01:06:28 +10:00
42e8fb71ef fix: mark subproject as dirty in cinny 2026-07-06 01:06:26 +10:00
GitHub Actions
ff2c284984 chore: bump version to 4.11.128 [skip ci] 2026-05-24 15:01:27 +00:00
75be84e472 Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m1s
Build / build-windows (push) Successful in 4m22s
Build / create-release (push) Successful in 1m10s
2026-05-25 01:01:10 +10:00
a3c91b306a fix: remove element protocol support and update related functionality 2026-05-25 01:01:06 +10:00
GitHub Actions
20edc293bb chore: bump version to 4.11.127 [skip ci] 2026-05-24 14:34:35 +00:00
6803eac8fb Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
All checks were successful
Build / increment-version (push) Successful in 14s
Build / build-linux (push) Successful in 2m4s
Build / build-windows (push) Successful in 5m5s
Build / create-release (push) Successful in 1m10s
2026-05-25 00:34:19 +10:00
fa5592f1e4 feat: enhance protocol handling and diagnostics for Paarrot and Element URLs 2026-05-25 00:34:15 +10:00
GitHub Actions
78601be6f4 chore: bump version to 4.11.126 [skip ci] 2026-05-24 14:14:31 +00:00
c940bdc78c feat: add support for element:// deep links and register protocol handler
All checks were successful
Build / increment-version (push) Successful in 15s
Build / build-linux (push) Successful in 2m11s
Build / build-windows (push) Successful in 5m35s
Build / create-release (push) Successful in 1m11s
2026-05-25 00:14:09 +10:00
GitHub Actions
9471fe2f5d chore: bump version to 4.11.125 [skip ci] 2026-05-17 07:08:14 +00:00
57f7fe7086 Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
All checks were successful
Build / increment-version (push) Successful in 11s
Build / build-linux (push) Successful in 1m59s
Build / build-windows (push) Successful in 4m46s
Build / create-release (push) Successful in 1m8s
2026-05-17 17:08:00 +10:00
a2364f2622 fix: mark subproject as dirty in cinny 2026-05-17 17:07:58 +10:00
GitHub Actions
df2220581d chore: bump version to 4.11.124 [skip ci] 2026-05-17 06:53:59 +00:00
198eb0fe04 Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
All checks were successful
Build / increment-version (push) Successful in 11s
Build / build-linux (push) Successful in 1m59s
Build / build-windows (push) Successful in 5m24s
Build / create-release (push) Successful in 1m18s
2026-05-17 16:53:45 +10:00
fff114abef fix: mark subproject as dirty in cinny 2026-05-17 16:53:41 +10:00
GitHub Actions
7013f1c22d chore: bump version to 4.11.123 [skip ci] 2026-05-17 06:51:55 +00:00
1dbc7d6cc4 Merge branch 'main' of http://synbox.ruv.wtf:8418/litruv/cinny-desktop
Some checks failed
Build / increment-version (push) Successful in 11s
Build / build-linux (push) Failing after 47s
Build / build-windows (push) Failing after 1m37s
Build / create-release (push) Has been skipped
2026-05-17 16:51:43 +10:00
f93f109d4a fix: mark subproject as dirty in cinny 2026-05-17 16:51:40 +10:00
GitHub Actions
f707aa98f5 chore: bump version to 4.11.122 [skip ci] 2026-05-17 06:48:56 +00:00
19 changed files with 3336 additions and 2634 deletions

View File

@@ -8,6 +8,9 @@ on:
tags: tags:
- 'v*' - 'v*'
# Large AppImage/exe uploads time out on Gitea's actions artifact pipeline.
# Build jobs publish assets straight to Gitea/GitHub releases via the REST API.
jobs: jobs:
increment-version: increment-version:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -105,84 +108,101 @@ jobs:
echo "VERSION=${{ steps.decide.outputs.CURRENT_VERSION }}" >> $GITHUB_OUTPUT echo "VERSION=${{ steps.decide.outputs.CURRENT_VERSION }}" >> $GITHUB_OUTPUT
fi fi
build-windows: prepare-release:
runs-on: windows runs-on: ubuntu-latest
needs: increment-version needs: increment-version
if: always() && (needs.increment-version.outputs.should_build == 'true' || needs.increment-version.result == 'skipped') if: always() && needs.increment-version.outputs.should_build == 'true'
outputs:
version: ${{ steps.version.outputs.VERSION }}
tag: ${{ steps.version.outputs.TAG_NAME }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
submodules: false fetch-depth: 0
ref: ${{ github.ref_name }} ref: ${{ github.ref_name }}
- name: Checkout submodules manually
shell: powershell
run: |
git submodule sync --recursive
if (git submodule update --init --recursive) {
Write-Host "Submodules checked out at pinned commits."
exit 0
}
Write-Warning "Pinned submodule commit is unavailable on remote. Falling back to remote submodule HEAD."
git submodule deinit -f --all
git submodule update --init --recursive --remote
Write-Host "Resolved submodule commit:"
git -C cinny rev-parse HEAD
- name: Pull latest (after version bump) - name: Pull latest (after version bump)
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
shell: powershell
run: git pull origin main run: git pull origin main
- name: Setup Node.js - name: Resolve version
uses: actions/setup-node@v4 id: version
with:
node-version: '20'
- name: Install dependencies (root)
run: npm ci --prefer-offline
- name: Install dependencies (cinny)
working-directory: ./cinny
run: npm ci --prefer-offline
- name: Clean previous builds
shell: powershell
run: | run: |
if (Test-Path cinny/dist) { Remove-Item -Recurse -Force cinny/dist } if [ -n "${{ needs.increment-version.outputs.version }}" ]; then
if (Test-Path dist-electron) { Remove-Item -Recurse -Force dist-electron } VERSION="${{ needs.increment-version.outputs.version }}"
else
VERSION=$(grep -Po '"version":\s*"\K[^"]+' package.json | head -1)
fi
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "TAG_NAME=v$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
- name: Build Electron app - name: Create and push tag
run: npm run build:local -- --win run: |
VERSION="${{ steps.version.outputs.VERSION }}"
TAG_NAME="v${VERSION}"
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
echo "Tag $TAG_NAME already exists"
else
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git tag -a "$TAG_NAME" -m "Release $TAG_NAME"
git push origin "$TAG_NAME"
fi
- name: Ensure Gitea release
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.VERSION }}"
TAG_NAME="v${VERSION}"
API_BASE="${{ github.server_url }}/api/v1"
REPO="${{ github.repository }}"
RELEASE_ID=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty' || true)
if [ -z "$RELEASE_ID" ]; then
echo "Creating Gitea release ${TAG_NAME}..."
RELEASE_ID=$(curl -fsS -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${TAG_NAME}\", \"body\": \"Release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
"${API_BASE}/repos/${REPO}/releases" | jq -r '.id')
fi
echo "Gitea release id: ${RELEASE_ID}"
- name: Ensure GitHub release
env: env:
GH_TOKEN: ${{ secrets.GHTOKEN }} GH_TOKEN: ${{ secrets.GHTOKEN }}
- name: Upload NSIS Installer (x64)
uses: actions/upload-artifact@v3
with:
name: Paarrot-Setup-x64.exe
path: dist-electron/Paarrot-*-win-x64.exe
- name: Upload latest.yml
uses: actions/upload-artifact@v3
with:
name: latest.yml
path: dist-electron/latest.yml
- name: List build output
shell: powershell
run: | run: |
Get-ChildItem -Path dist-electron -Recurse | Select-Object FullName set -euo pipefail
VERSION="${{ steps.version.outputs.VERSION }}"
TAG_NAME="v${VERSION}"
GITHUB_REPO="Paarrot/Paarrot-Desktop"
build-linux: RELEASE_ID=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty' || true)
if [ -z "$RELEASE_ID" ]; then
echo "Creating GitHub release ${TAG_NAME}..."
RELEASE_ID=$(curl -fsS -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${TAG_NAME}\", \"body\": \"Release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases" | jq -r '.id')
fi
echo "GitHub release id: ${RELEASE_ID}"
build-windows:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: increment-version needs: [increment-version, prepare-release]
if: always() && (needs.increment-version.outputs.should_build == 'true' || needs.increment-version.result == 'skipped') if: always() && needs.prepare-release.result == 'success'
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -194,6 +214,14 @@ jobs:
- name: Checkout submodules manually - name: Checkout submodules manually
run: | run: |
set -euo pipefail set -euo pipefail
# Use the same host/scheme the runner already used for origin (not public HTTPS).
ORIGIN_URL=$(git remote get-url origin)
ORIGIN_URL=${ORIGIN_URL%/}
ORIGIN_URL=${ORIGIN_URL%.git}
BASE_URL=${ORIGIN_URL%/*}
CINNY_URL="${BASE_URL}/cinny.git"
echo "Rewriting submodule URL to ${CINNY_URL}"
git config submodule.cinny.url "${CINNY_URL}"
git submodule sync --recursive git submodule sync --recursive
if git submodule update --init --recursive; then if git submodule update --init --recursive; then
@@ -202,7 +230,165 @@ jobs:
fi fi
echo "Pinned submodule commit is unavailable on remote. Falling back to remote submodule HEAD." echo "Pinned submodule commit is unavailable on remote. Falling back to remote submodule HEAD."
git submodule deinit -f --all git submodule deinit -f --all || true
rm -rf cinny .git/modules/cinny
git config submodule.cinny.url "${CINNY_URL}"
git submodule sync --recursive
git submodule update --init --recursive --remote
echo "Resolved submodule commit:"
git -C cinny rev-parse HEAD
- name: Pull latest (after version bump)
if: github.ref == 'refs/heads/main'
run: git pull origin main
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Wine (NSIS cross-build)
run: |
set -euo pipefail
# electron-builder spawns `wine` by name; wine64 alone is not enough.
sudo dpkg --add-architecture i386
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
wine \
wine64 \
wine32 \
|| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends wine64
if ! command -v wine >/dev/null 2>&1; then
WINE64_BIN=$(command -v wine64)
echo "No wine binary; linking wine64 -> /usr/local/bin/wine (${WINE64_BIN})"
sudo ln -sf "${WINE64_BIN}" /usr/local/bin/wine
fi
command -v wine
wine --version
- name: Install dependencies (root)
run: npm ci --prefer-offline
- name: Install dependencies (cinny)
working-directory: ./cinny
run: npm ci --prefer-offline
- name: Clean previous builds
run: |
rm -rf cinny/dist dist-electron
- name: Build Electron app
run: npm run build:local -- --win
env:
GH_TOKEN: ${{ secrets.GHTOKEN }}
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
- name: Upload Windows assets to releases
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GHTOKEN }}
run: |
set -euo pipefail
TAG_NAME="${{ needs.prepare-release.outputs.tag }}"
API_BASE="${{ github.server_url }}/api/v1"
REPO="${{ github.repository }}"
GITHUB_REPO="Paarrot/Paarrot-Desktop"
GITEA_RELEASE_ID=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/tags/${TAG_NAME}" | jq -r '.id')
GH_RELEASE_ID=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG_NAME}" | jq -r '.id')
GH_UPLOAD_URL=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${GH_RELEASE_ID}" | jq -r '.upload_url' | sed 's/{?name,label}//')
upload_one() {
local FILE="$1"
local FILENAME
FILENAME=$(basename "$FILE")
echo "Uploading ${FILENAME} ($(du -h "$FILE" | cut -f1))..."
OLD_ID=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${GITEA_RELEASE_ID}/assets" \
| jq -r --arg n "$FILENAME" '.[] | select(.name == $n) | .id' || true)
if [ -n "${OLD_ID:-}" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${GITEA_RELEASE_ID}/assets/${OLD_ID}" >/dev/null
fi
curl -fsS --max-time 0 --retry 5 --retry-delay 10 \
-X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"${FILE}" \
"${API_BASE}/repos/${REPO}/releases/${GITEA_RELEASE_ID}/assets?name=${FILENAME}" \
>/dev/null
echo " Gitea: ok"
OLD_GH=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${GH_RELEASE_ID}/assets" \
| jq -r --arg n "$FILENAME" '.[] | select(.name == $n) | .id' || true)
if [ -n "${OLD_GH:-}" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/assets/${OLD_GH}" >/dev/null
fi
curl -fsS --max-time 0 --retry 5 --retry-delay 10 \
-X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"${FILE}" \
"${GH_UPLOAD_URL}?name=${FILENAME}" \
>/dev/null
echo " GitHub: ok"
}
shopt -s nullglob
for FILE in dist-electron/Paarrot-*-win-x64.exe dist-electron/latest.yml; do
upload_one "$FILE"
done
- name: List build output
run: |
find dist-electron -type f -printf '%p\n' | sort
build-linux:
runs-on: ubuntu-latest
needs: [increment-version, prepare-release]
if: always() && needs.prepare-release.result == 'success'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: false
ref: ${{ github.ref_name }}
- name: Checkout submodules manually
run: |
set -euo pipefail
# Use the same host/scheme the runner already used for origin (not public HTTPS).
ORIGIN_URL=$(git remote get-url origin)
ORIGIN_URL=${ORIGIN_URL%/}
ORIGIN_URL=${ORIGIN_URL%.git}
BASE_URL=${ORIGIN_URL%/*}
CINNY_URL="${BASE_URL}/cinny.git"
echo "Rewriting submodule URL to ${CINNY_URL}"
git config submodule.cinny.url "${CINNY_URL}"
git submodule sync --recursive
if git submodule update --init --recursive; then
echo "Submodules checked out at pinned commits."
exit 0
fi
echo "Pinned submodule commit is unavailable on remote. Falling back to remote submodule HEAD."
git submodule deinit -f --all || true
rm -rf cinny .git/modules/cinny
git config submodule.cinny.url "${CINNY_URL}"
git submodule sync --recursive
git submodule update --init --recursive --remote git submodule update --init --recursive --remote
echo "Resolved submodule commit:" echo "Resolved submodule commit:"
@@ -233,208 +419,126 @@ jobs:
env: env:
GH_TOKEN: ${{ secrets.GHTOKEN }} GH_TOKEN: ${{ secrets.GHTOKEN }}
- name: Upload AppImage - name: Upload Linux assets to releases
uses: actions/upload-artifact@v3 env:
with: GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: Paarrot-Linux-x64.AppImage GH_TOKEN: ${{ secrets.GHTOKEN }}
path: dist-electron/Paarrot-*.AppImage run: |
set -euo pipefail
- name: Upload latest-linux.yml VERSION="${{ needs.prepare-release.outputs.version }}"
uses: actions/upload-artifact@v3 TAG_NAME="${{ needs.prepare-release.outputs.tag }}"
with: API_BASE="${{ github.server_url }}/api/v1"
name: latest-linux.yml REPO="${{ github.repository }}"
path: dist-electron/latest-linux.yml GITHUB_REPO="Paarrot/Paarrot-Desktop"
create-release: GITEA_RELEASE_ID=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/tags/${TAG_NAME}" | jq -r '.id')
GH_RELEASE_ID=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG_NAME}" | jq -r '.id')
GH_UPLOAD_URL=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${GH_RELEASE_ID}" | jq -r '.upload_url' | sed 's/{?name,label}//')
upload_one() {
local FILE="$1"
local FILENAME
FILENAME=$(basename "$FILE")
echo "Uploading ${FILENAME} ($(du -h "$FILE" | cut -f1))..."
OLD_ID=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${GITEA_RELEASE_ID}/assets" \
| jq -r --arg n "$FILENAME" '.[] | select(.name == $n) | .id' || true)
if [ -n "${OLD_ID:-}" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${GITEA_RELEASE_ID}/assets/${OLD_ID}" >/dev/null
fi
curl -fsS --max-time 0 --retry 5 --retry-delay 10 \
-X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"${FILE}" \
"${API_BASE}/repos/${REPO}/releases/${GITEA_RELEASE_ID}/assets?name=${FILENAME}" \
>/dev/null
echo " Gitea: ok"
OLD_GH=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${GH_RELEASE_ID}/assets" \
| jq -r --arg n "$FILENAME" '.[] | select(.name == $n) | .id' || true)
if [ -n "${OLD_GH:-}" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/assets/${OLD_GH}" >/dev/null
fi
curl -fsS --max-time 0 --retry 5 --retry-delay 10 \
-X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"${FILE}" \
"${GH_UPLOAD_URL}?name=${FILENAME}" \
>/dev/null
echo " GitHub: ok"
}
shopt -s nullglob
for FILE in dist-electron/Paarrot-*.AppImage dist-electron/latest-linux.yml; do
upload_one "$FILE"
done
finalize-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [increment-version, build-windows, build-linux] needs: [increment-version, prepare-release, build-windows, build-linux]
if: always() && (needs.build-windows.result == 'success' || needs.build-linux.result == 'success') if: always() && needs.prepare-release.result == 'success' && (needs.build-windows.result == 'success' || needs.build-linux.result == 'success')
steps: steps:
- name: Checkout repository - name: Cleanup old Gitea releases
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Pull latest (after version bump)
if: github.ref == 'refs/heads/main'
run: git pull origin main
- name: Get version
id: version
run: |
# Use version from increment-version job if available, otherwise read from file
if [ -n "${{ needs.increment-version.outputs.version }}" ]; then
VERSION="${{ needs.increment-version.outputs.version }}"
else
VERSION=$(grep -Po '"version":\s*"\K[^"]+' package.json | head -1)
fi
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
- name: Check if tag exists
id: tag_check
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
echo "Tag v$VERSION already exists"
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "Tag v$VERSION does not exist"
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Create and push tag
if: steps.tag_check.outputs.exists == 'false'
run: |
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git tag -a "v${{ steps.version.outputs.VERSION }}" -m "Release v${{ steps.version.outputs.VERSION }}"
git push origin "v${{ steps.version.outputs.VERSION }}"
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
path: artifacts
- name: Prepare release files
run: |
mkdir -p release-files
find artifacts -type f \( -name "*.exe" -o -name "*.AppImage" -o -name "*.yml" \) -exec cp {} release-files/ \;
ls -la release-files/
- name: Create or Update Release
env: env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
VERSION="${{ steps.version.outputs.VERSION }}" set -euo pipefail
TAG_NAME="v${VERSION}" TAG_NAME="${{ needs.prepare-release.outputs.tag }}"
API_BASE="http://synbox.ruv.wtf:8418/api/v1" API_BASE="${{ github.server_url }}/api/v1"
REPO="litruv/cinny-desktop" REPO="${{ github.repository }}"
# Delete old releases (keep only current) echo "Cleaning up old Gitea releases (keeping ${TAG_NAME})..."
echo "Cleaning up old releases..." OLD_RELEASES=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
OLD_RELEASES=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \ "${API_BASE}/repos/${REPO}/releases" | jq -r --arg t "$TAG_NAME" '.[] | select(.tag_name != $t) | .id')
"${API_BASE}/repos/${REPO}/releases" | jq -r '.[] | select(.tag_name != "'${TAG_NAME}'") | .id')
for OLD_ID in $OLD_RELEASES; do for OLD_ID in $OLD_RELEASES; do
echo "Deleting old release ${OLD_ID}..." [ -n "$OLD_ID" ] || continue
OLD_TAG=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \ OLD_TAG=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${OLD_ID}" | jq -r '.tag_name') "${API_BASE}/repos/${REPO}/releases/${OLD_ID}" | jq -r '.tag_name')
echo "Deleting Gitea release ${OLD_ID} (${OLD_TAG})..."
# Delete release curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ "${API_BASE}/repos/${REPO}/releases/${OLD_ID}" >/dev/null || true
"${API_BASE}/repos/${REPO}/releases/${OLD_ID}" if [ -n "$OLD_TAG" ] && [ "$OLD_TAG" != "null" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
# Delete tag "${API_BASE}/repos/${REPO}/tags/${OLD_TAG}" >/dev/null || true
if [ -n "$OLD_TAG" ]; then
echo "Deleting old tag ${OLD_TAG}..."
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/tags/${OLD_TAG}"
fi fi
done done
# Check if release exists
RELEASE_ID=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty')
if [ -z "$RELEASE_ID" ]; then
echo "Creating new release for ${TAG_NAME}..."
RELEASE_ID=$(curl -s -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${TAG_NAME}\", \"body\": \"Release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
"${API_BASE}/repos/${REPO}/releases" | jq -r '.id')
echo "Created release with ID: ${RELEASE_ID}"
else
echo "Release exists with ID: ${RELEASE_ID}"
# Delete existing assets
echo "Deleting existing assets..."
ASSETS=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets" | jq -r '.[].id')
for ASSET_ID in $ASSETS; do
echo "Deleting asset ${ASSET_ID}..."
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
done
fi
# Upload new assets
echo "Uploading assets..."
for FILE in release-files/*; do
FILENAME=$(basename "$FILE")
echo "Uploading ${FILENAME}..."
curl -s -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${FILE}" \
"${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${FILENAME}"
echo ""
done
echo "Release complete!"
- name: Create GitHub Release - name: Cleanup old GitHub releases
env: env:
GH_TOKEN: ${{ secrets.GHTOKEN }} GH_TOKEN: ${{ secrets.GHTOKEN }}
run: | run: |
VERSION="${{ steps.version.outputs.VERSION }}" set -euo pipefail
TAG_NAME="v${VERSION}" TAG_NAME="${{ needs.prepare-release.outputs.tag }}"
GITHUB_REPO="Paarrot/Paarrot-Desktop" GITHUB_REPO="Paarrot/Paarrot-Desktop"
# Delete old GitHub releases (keep only current) echo "Cleaning up old GitHub releases (keeping ${TAG_NAME})..."
echo "Cleaning up old GitHub releases..." OLD_RELEASES=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
OLD_RELEASES=$(curl -s -H "Authorization: token ${GH_TOKEN}" \ "https://api.github.com/repos/${GITHUB_REPO}/releases" | jq -r --arg t "$TAG_NAME" '.[] | select(.tag_name != $t) | .id')
"https://api.github.com/repos/${GITHUB_REPO}/releases" | jq -r '.[] | select(.tag_name != "'${TAG_NAME}'") | .id')
for OLD_ID in $OLD_RELEASES; do for OLD_ID in $OLD_RELEASES; do
echo "Deleting old GitHub release ${OLD_ID}..." [ -n "$OLD_ID" ] || continue
OLD_TAG=$(curl -s -H "Authorization: token ${GH_TOKEN}" \ OLD_TAG=$(curl -fsS -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${OLD_ID}" | jq -r '.tag_name') "https://api.github.com/repos/${GITHUB_REPO}/releases/${OLD_ID}" | jq -r '.tag_name')
echo "Deleting GitHub release ${OLD_ID} (${OLD_TAG})..."
# Delete release curl -fsS -X DELETE -H "Authorization: token ${GH_TOKEN}" \
curl -s -X DELETE -H "Authorization: token ${GH_TOKEN}" \ "https://api.github.com/repos/${GITHUB_REPO}/releases/${OLD_ID}" >/dev/null || true
"https://api.github.com/repos/${GITHUB_REPO}/releases/${OLD_ID}" if [ -n "$OLD_TAG" ] && [ "$OLD_TAG" != "null" ]; then
curl -fsS -X DELETE -H "Authorization: token ${GH_TOKEN}" \
# Delete tag "https://api.github.com/repos/${GITHUB_REPO}/git/refs/tags/${OLD_TAG}" >/dev/null || true
if [ -n "$OLD_TAG" ]; then
echo "Deleting old GitHub tag ${OLD_TAG}..."
curl -s -X DELETE -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/git/refs/tags/${OLD_TAG}"
fi fi
done done
# Check if release exists on GitHub echo "Release finalize complete for ${TAG_NAME}"
RELEASE_ID=$(curl -s -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty')
if [ -z "$RELEASE_ID" ]; then
echo "Creating new GitHub release for ${TAG_NAME}..."
RELEASE_ID=$(curl -s -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"Release ${TAG_NAME}\", \"body\": \"Release ${VERSION}\", \"draft\": false, \"prerelease\": false}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases" | jq -r '.id')
echo "Created GitHub release with ID: ${RELEASE_ID}"
else
echo "GitHub release exists with ID: ${RELEASE_ID}"
fi
# Upload assets to GitHub
echo "Uploading assets to GitHub..."
UPLOAD_URL=$(curl -s -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${RELEASE_ID}" | jq -r '.upload_url' | sed 's/{?name,label}//')
for FILE in release-files/*; do
FILENAME=$(basename "$FILE")
echo "Uploading ${FILENAME} to GitHub..."
curl -s -X POST \
-H "Authorization: token ${GH_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${FILE}" \
"${UPLOAD_URL}?name=${FILENAME}"
echo ""
done
echo "GitHub release complete!"

35
.github/workflows/update-manifest.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Update release notes manifest
on:
push:
paths:
- 'cinny/public/update/*.md'
- 'cinny/scripts/generate-update-manifest.mjs'
permissions:
contents: write
jobs:
manifest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Generate manifest.json
run: node cinny/scripts/generate-update-manifest.mjs
- name: Commit manifest if changed
run: |
if git diff --quiet -- cinny/public/update/manifest.json; then
echo "manifest.json unchanged"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add cinny/public/update/manifest.json
git commit -m "chore: regenerate update manifest.json"
git push

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "cinny"] [submodule "cinny"]
path = cinny path = cinny
url = http://synbox.ruv.wtf:8418/litruv/cinny.git url = ../cinny.git

3
.npmrc
View File

@@ -1 +1,2 @@
save-exact=true save-exact=true
allow-git=all

2
cinny

Submodule cinny updated: 40dc552c8d...47979718eb

View File

@@ -36,6 +36,12 @@
"filter": ["**/*"] "filter": ["**/*"]
} }
], ],
"protocols": [
{
"name": "Paarrot URL",
"schemes": ["paarrot"]
}
],
"linux": { "linux": {
"target": [ "target": [
{ {
@@ -51,6 +57,7 @@
"Name": "Paarrot", "Name": "Paarrot",
"GenericName": "Matrix Client", "GenericName": "Matrix Client",
"Comment": "A Matrix client built with Cinny", "Comment": "A Matrix client built with Cinny",
"MimeType": "x-scheme-handler/paarrot;",
"Categories": "Network;InstantMessaging;", "Categories": "Network;InstantMessaging;",
"Keywords": "matrix;chat;messaging;", "Keywords": "matrix;chat;messaging;",
"StartupWMClass": "paarrot" "StartupWMClass": "paarrot"

View File

@@ -153,6 +153,72 @@ class PaarrotAPIServer {
} }
}); });
// Recent messages across group channels (non-DMs)
this.app.get('/messages/groups', async (req, res) => {
try {
const result = await this.executeAction('get-messages-groups', {
limit: req.query.limit,
});
res.json({ success: true, data: result });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// Recent messages across DMs
this.app.get('/messages/dms', async (req, res) => {
try {
const result = await this.executeAction('get-messages-dms', {
limit: req.query.limit,
});
res.json({ success: true, data: result });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// Recent messages across groups + DMs
this.app.get('/messages/combined', async (req, res) => {
try {
const result = await this.executeAction('get-messages-combined', {
limit: req.query.limit,
});
res.json({ success: true, data: result });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// Unread conversations across group channels (non-DMs)
this.app.get('/unreads/groups', async (req, res) => {
try {
const result = await this.executeAction('get-unreads-groups');
res.json({ success: true, data: result });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// Unread conversations across DMs
this.app.get('/unreads/dms', async (req, res) => {
try {
const result = await this.executeAction('get-unreads-dms');
res.json({ success: true, data: result });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// Unread conversations across groups + DMs
this.app.get('/unreads/combined', async (req, res) => {
try {
const result = await this.executeAction('get-unreads-combined');
res.json({ success: true, data: result });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
// 404 handler // 404 handler
this.app.use((req, res) => { this.app.use((req, res) => {
res.status(404).json({ res.status(404).json({
@@ -169,7 +235,13 @@ class PaarrotAPIServer {
'GET /channels', 'GET /channels',
'POST /message', 'POST /message',
'POST /message/current', 'POST /message/current',
'GET /room/current' 'GET /room/current',
'GET /messages/groups',
'GET /messages/dms',
'GET /messages/combined',
'GET /unreads/groups',
'GET /unreads/dms',
'GET /unreads/combined'
] ]
}); });
}); });
@@ -237,6 +309,12 @@ class PaarrotAPIServer {
console.log(' POST /message'); console.log(' POST /message');
console.log(' POST /message/current'); console.log(' POST /message/current');
console.log(' GET /room/current'); console.log(' GET /room/current');
console.log(' GET /messages/groups');
console.log(' GET /messages/dms');
console.log(' GET /messages/combined');
console.log(' GET /unreads/groups');
console.log(' GET /unreads/dms');
console.log(' GET /unreads/combined');
resolve(port); resolve(port);
}).on('error', (err) => { }).on('error', (err) => {
if (err.code === 'EADDRINUSE') { if (err.code === 'EADDRINUSE') {

View File

@@ -0,0 +1,381 @@
/**
* Discord shop collectibles: fetch a published catalog and download selected assets from Discord CDN.
*/
const ITEM_TYPE_FOLDER = {
0: 'avatar-decorations',
1: 'profile-effects',
2: 'nameplates',
};
const ITEM_TYPE_KEY = {
0: 'avatar_decoration',
1: 'profile_effect',
2: 'nameplate',
};
const CDN_BASE = 'https://cdn.discordapp.com';
const CDN_HOST_RE = /(^|\.)(discordapp\.com|discordapp\.net|discord\.com)$/i;
const DEFAULT_USER_AGENT =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) discord/1.0 Chrome/120 Electron/28 Safari/537.36';
const PUBLISHED_CATALOG_URLS = [
'https://github.com/litruv/discord-collectibles/releases/download/latest/profileeffects.json',
'https://github.com/litruv/discord-collectibles/releases/download/latest/nameplate.json',
'https://github.com/litruv/discord-collectibles/releases/download/latest/avatardecorations.json',
];
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function isCdnUrl(value) {
if (typeof value !== 'string' || !value.startsWith('http')) return false;
try {
return CDN_HOST_RE.test(new URL(value).hostname);
} catch {
return false;
}
}
function basenameFromUrl(url) {
try {
const { pathname } = new URL(url);
const last = decodeURIComponent(pathname.split('/').filter(Boolean).pop() || '');
return last || 'asset';
} catch {
return 'asset';
}
}
function guessMimeType(filename) {
const lower = String(filename).toLowerCase();
if (lower.endsWith('.png')) return 'image/png';
if (lower.endsWith('.webp')) return 'image/webp';
if (lower.endsWith('.webm')) return 'video/webm';
if (lower.endsWith('.gif')) return 'image/gif';
if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) return 'image/jpeg';
return 'application/octet-stream';
}
function deepCollectUrls(node, acc = new Set()) {
if (node == null) return acc;
if (typeof node === 'string') {
if (isCdnUrl(node)) acc.add(node);
return acc;
}
if (Array.isArray(node)) {
for (const v of node) deepCollectUrls(v, acc);
return acc;
}
if (typeof node === 'object') {
for (const v of Object.values(node)) deepCollectUrls(v, acc);
}
return acc;
}
function urlsForItem(item) {
const out = [];
const seen = new Set();
function push(url, filename, role) {
if (!url || seen.has(url)) return;
seen.add(url);
out.push({ url, filename, role: role || filename });
}
for (const url of deepCollectUrls(item)) {
let role = basenameFromUrl(url);
if (item.thumbnailPreviewSrc === url) role = 'thumbnail';
else if (item.reducedMotionSrc === url) role = 'reduced_motion';
else if (Array.isArray(item.effects)) {
const effectIndex = item.effects.findIndex((effect) => effect?.src === url);
if (effectIndex >= 0) role = `effect_${effectIndex}`;
}
push(url, basenameFromUrl(url), role);
}
if (item.type === 0 && item.asset) {
const base = `${CDN_BASE}/avatar-decoration-presets/${item.asset}.png`;
push(`${base}?passthrough=true`, 'animated.png', 'animated');
push(`${base}?passthrough=false`, 'static.png', 'static');
}
if (item.type === 2 && item.asset) {
const assetPath = String(item.asset).replace(/^\/+/, '');
push(`${CDN_BASE}/assets/collectibles/${assetPath}static.png`, 'static.png', 'static');
push(`${CDN_BASE}/assets/collectibles/${assetPath}asset.webm`, 'asset.webm', 'animated');
}
return out;
}
function decimalRgbToCss(color) {
const value = Number(color) >>> 0;
const r = (value >> 16) & 0xff;
const g = (value >> 8) & 0xff;
const b = value & 0xff;
return `rgb(${r}, ${g}, ${b})`;
}
function gradientFromColors(colors) {
if (!Array.isArray(colors) || colors.length === 0) return undefined;
if (colors.length === 1) return decimalRgbToCss(colors[0]);
return `linear-gradient(135deg, ${decimalRgbToCss(colors[0])}, ${decimalRgbToCss(colors[1])})`;
}
function previewAspectRatioForItem(item) {
if (item.type === 0) return 1;
if (item.type === 2) return 448 / 84;
const firstEffect = item.effects?.[0];
if (firstEffect?.width && firstEffect?.height) {
return firstEffect.width / firstEffect.height;
}
return 450 / 880;
}
function formatPaletteName(palette) {
if (!palette) return undefined;
return String(palette)
.split('_')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}
const NAMEPLATE_PALETTE_GRADIENTS = {
crimson: 'linear-gradient(135deg, #5c0a1c, #dc143c)',
berry: 'linear-gradient(135deg, #4a1030, #c42d78)',
sky: 'linear-gradient(135deg, #0a2a5c, #3b8eed)',
teal: 'linear-gradient(135deg, #0a3d3d, #2dd4bf)',
forest: 'linear-gradient(135deg, #0a2e1a, #22c55e)',
bubble_gum: 'linear-gradient(135deg, #4a1038, #f472b6)',
violet: 'linear-gradient(135deg, #2d1050, #8b5cf6)',
cobalt: 'linear-gradient(135deg, #0a1448, #3b5bdb)',
clover: 'linear-gradient(135deg, #0a3d20, #4ade80)',
lemon: 'linear-gradient(135deg, #4a3d0a, #fbbf24)',
white: 'linear-gradient(135deg, #888888, #f0f0f0)',
black: 'linear-gradient(135deg, #1a1a1a, #404040)',
};
function nameplatePaletteGradient(palette) {
if (!palette) return undefined;
return NAMEPLATE_PALETTE_GRADIENTS[palette];
}
function thumbnailForItem(item) {
if (item.thumbnailPreviewSrc && isCdnUrl(item.thumbnailPreviewSrc)) {
return item.thumbnailPreviewSrc;
}
if (item.type === 0 && item.asset) {
return `${CDN_BASE}/avatar-decoration-presets/${item.asset}.png?passthrough=false`;
}
if (item.type === 2 && item.asset) {
const assetPath = String(item.asset).replace(/^\/+/, '');
return `${CDN_BASE}/assets/collectibles/${assetPath}static.png`;
}
const assets = urlsForItem(item);
return assets[0]?.url;
}
function* walkProduct(product, category) {
yield { product, category };
for (const bundled of product.bundled_products ?? []) {
yield* walkProduct(bundled, category);
}
for (const variant of product.variants ?? []) {
yield* walkProduct(variant, category);
}
}
function* walkProducts(categories) {
for (const category of categories ?? []) {
for (const product of category.products ?? []) {
yield* walkProduct(product, category);
}
}
}
function extractCollectibleItems(categories) {
const items = [];
const seenItems = new Set();
for (const { product, category } of walkProducts(categories)) {
const categoryName = category?.name || 'Unknown';
for (const item of product.items ?? []) {
if (![0, 1, 2].includes(item.type)) continue;
const skuId = String(item.sku_id ?? item.id ?? product.sku_id ?? 'unknown');
const itemKey = `${item.type}:${skuId}:${item.id ?? ''}`;
if (seenItems.has(itemKey)) continue;
seenItems.add(itemKey);
const assets = urlsForItem(item);
if (assets.length === 0) continue;
const productName = product.name || item.title || item.label || skuId;
const typeKey = ITEM_TYPE_KEY[item.type];
const entry = {
id: `${typeKey}:${skuId}`,
skuId,
name: productName,
type: typeKey,
category: categoryName,
label: item.label ?? item.title ?? productName,
thumbnailUrl: thumbnailForItem(item),
previewAspectRatio: previewAspectRatioForItem(item),
previewColors: product.styles?.background_colors,
palette: item.type === 2 ? item.palette : undefined,
paletteLabel: item.type === 2 ? formatPaletteName(item.palette) : undefined,
previewGradient: item.type === 2 ? nameplatePaletteGradient(item.palette) : undefined,
assets: assets.map((a) => ({
role: a.role,
url: a.url,
filename: a.filename,
mimeType: guessMimeType(a.filename),
})),
};
if (item.type === 1) {
entry.effect = {
animationType: item.animationType,
thumbnailPreviewSrc: item.thumbnailPreviewSrc,
reducedMotionSrc: item.reducedMotionSrc,
effects: item.effects,
};
}
items.push(entry);
}
}
return items;
}
function isPublishedCatalog(document) {
return (
document &&
document.schema_version === 1 &&
typeof document.type === 'string' &&
Array.isArray(document.items) &&
document.items.every(
(item) =>
item &&
typeof item.id === 'string' &&
typeof item.skuId === 'string' &&
typeof item.type === 'string' &&
Array.isArray(item.assets)
)
);
}
async function fetchPublishedCatalog() {
const documents = await Promise.all(
PUBLISHED_CATALOG_URLS.map(async (url) => {
const response = await fetch(url, { headers: { Accept: 'application/json' } });
if (!response.ok) {
throw new Error(`Published collectibles catalog request failed with HTTP ${response.status}.`);
}
const document = await response.json();
if (!isPublishedCatalog(document)) {
throw new Error('Published collectibles catalog has an unsupported schema.');
}
return document;
})
);
const items = documents.flatMap((document) => document.items);
if (items.length === 0) {
throw new Error('Published collectibles catalog is empty.');
}
return items;
}
async function downloadAsset(url, { retries = 4 } = {}) {
for (let attempt = 0; ; attempt++) {
let res;
try {
res = await fetch(url, {
headers: { 'User-Agent': DEFAULT_USER_AGENT, Accept: '*/*' },
});
} catch (err) {
if (attempt >= retries) throw err;
await sleep(500 * 2 ** attempt);
continue;
}
if (res.status === 429) {
const retryAfter = Number(res.headers.get('retry-after')) || 1;
await sleep((retryAfter + 0.5) * 1000);
continue;
}
if (!res.ok) {
if (attempt >= retries) {
throw new Error(`Failed to download asset: ${res.status} ${res.statusText}`);
}
await sleep(500 * 2 ** attempt);
continue;
}
const buf = Buffer.from(await res.arrayBuffer());
return buf;
}
}
function createDiscordCollectiblesService(store) {
let catalogCache = store.get('discordCollectiblesCatalog') || null;
if (
!catalogCache ||
!Array.isArray(catalogCache.items) ||
typeof catalogCache.fetchedAt !== 'string'
) {
catalogCache = null;
}
let catalogCacheAt = catalogCache?.fetchedAt ? Date.parse(catalogCache.fetchedAt) : 0;
const CATALOG_TTL_MS = 6 * 60 * 60 * 1000;
store.delete('discordCollectiblesToken');
async function getCatalog({ force = false } = {}) {
const now = Date.now();
if (!force && catalogCache && now - catalogCacheAt < CATALOG_TTL_MS) {
return { success: true, data: catalogCache };
}
try {
const items = await fetchPublishedCatalog();
catalogCache = { items, fetchedAt: new Date().toISOString() };
catalogCacheAt = now;
store.set('discordCollectiblesCatalog', catalogCache);
return { success: true, data: catalogCache };
} catch (err) {
if (catalogCache) {
return {
success: true,
data: { ...catalogCache, stale: true },
};
}
return { success: false, error: err instanceof Error ? err.message : String(err) };
}
}
async function downloadAssets(assetUrls) {
const results = [];
for (const asset of assetUrls) {
const buffer = await downloadAsset(asset.url);
results.push({
role: asset.role,
url: asset.url,
filename: asset.filename,
mimeType: asset.mimeType || guessMimeType(asset.filename),
data: buffer,
});
}
return results;
}
return {
getCatalog,
downloadAssets,
ITEM_TYPE_FOLDER,
};
}
module.exports = { createDiscordCollectiblesService, extractCollectibleItems, urlsForItem };

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,8 @@ const commandMap = {
'check_for_updates': 'check-for-updates', 'check_for_updates': 'check-for-updates',
'download_update': 'download-update', 'download_update': 'download-update',
'install_update': 'install-update', 'install_update': 'install-update',
'get_desktop_sources': 'get-desktop-sources' 'get_desktop_sources': 'get-desktop-sources',
'get_protocol_status': 'protocol:get-status'
}; };
// Shared invoke function for legacy API compatibility // Shared invoke function for legacy API compatibility
@@ -103,6 +104,12 @@ contextBridge.exposeInMainWorld('electron', {
readImage: () => ipcRenderer.invoke('read-clipboard-image'), readImage: () => ipcRenderer.invoke('read-clipboard-image'),
writeText: (text) => ipcRenderer.invoke('write-clipboard-text', text) writeText: (text) => ipcRenderer.invoke('write-clipboard-text', text)
}, },
// Media save (native Save dialog for images / videos / files)
media: {
saveFile: ({ filename, mimeType, data }) =>
ipcRenderer.invoke('media:save-file', { filename, mimeType, data }),
},
// Audio // Audio
audio: { audio: {
playNotificationSound: (soundType = 'message') => ipcRenderer.invoke('play-notification-sound', soundType), playNotificationSound: (soundType = 'message') => ipcRenderer.invoke('play-notification-sound', soundType),
@@ -129,6 +136,7 @@ contextBridge.exposeInMainWorld('electron', {
updater: { updater: {
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'), checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
downloadAndInstallUpdate: () => ipcRenderer.invoke('download-and-install-update'), downloadAndInstallUpdate: () => ipcRenderer.invoke('download-and-install-update'),
isMock: () => ipcRenderer.invoke('updater-is-mock'),
onUpdateAvailable: (callback) => { onUpdateAvailable: (callback) => {
ipcRenderer.on('update-available', (_, info) => callback(info)); ipcRenderer.on('update-available', (_, info) => callback(info));
}, },
@@ -138,6 +146,9 @@ contextBridge.exposeInMainWorld('electron', {
onUpdateDownloaded: (callback) => { onUpdateDownloaded: (callback) => {
ipcRenderer.on('update-downloaded', (_, info) => callback(info)); ipcRenderer.on('update-downloaded', (_, info) => callback(info));
}, },
onUpdateNotAvailable: (callback) => {
ipcRenderer.on('update-not-available', (_, info) => callback(info));
},
downloadUpdate: () => ipcRenderer.invoke('download-update'), downloadUpdate: () => ipcRenderer.invoke('download-update'),
installUpdate: () => ipcRenderer.invoke('install-update'), installUpdate: () => ipcRenderer.invoke('install-update'),
}, },
@@ -176,7 +187,19 @@ contextBridge.exposeInMainWorld('electron', {
list: () => ipcRenderer.invoke('plugin:list'), list: () => ipcRenderer.invoke('plugin:list'),
uninstall: (pluginId) => ipcRenderer.invoke('plugin:uninstall', pluginId), uninstall: (pluginId) => ipcRenderer.invoke('plugin:uninstall', pluginId),
readPluginCode: (pluginId) => ipcRenderer.invoke('plugin:read-code', pluginId) readPluginCode: (pluginId) => ipcRenderer.invoke('plugin:read-code', pluginId)
} },
// Desktop protocol diagnostics
protocol: {
getStatus: () => ipcRenderer.invoke('protocol:get-status'),
repair: () => ipcRenderer.invoke('protocol:repair')
},
// Discord shop collectibles (published catalog + on-demand CDN download)
discordCollectibles: {
fetchCatalog: (force = false) => ipcRenderer.invoke('discord-collectibles:fetch-catalog', { force }),
downloadAssets: (assets) => ipcRenderer.invoke('discord-collectibles:download-assets', { assets }),
},
}); });
// Preload script ready // Preload script ready

View File

@@ -208,6 +208,81 @@
{ {
"name": "Messaging", "name": "Messaging",
"item": [ "item": [
{
"name": "Get Group Messages",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/messages/groups?limit=10",
"host": [
"{{baseUrl}}"
],
"path": [
"messages",
"groups"
],
"query": [
{
"key": "limit",
"value": "10",
"description": "Number of recent messages (1-100, default 10)"
}
]
},
"description": "Get the most recent messages across joined group channels (non-DMs)."
}
},
{
"name": "Get DM Messages",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/messages/dms?limit=10",
"host": [
"{{baseUrl}}"
],
"path": [
"messages",
"dms"
],
"query": [
{
"key": "limit",
"value": "10",
"description": "Number of recent messages (1-100, default 10)"
}
]
},
"description": "Get the most recent messages across joined direct messages."
}
},
{
"name": "Get Combined Messages",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{baseUrl}}/messages/combined?limit=10",
"host": [
"{{baseUrl}}"
],
"path": [
"messages",
"combined"
],
"query": [
{
"key": "limit",
"value": "10",
"description": "Number of recent messages (1-100, default 10)"
}
]
},
"description": "Get the most recent messages across groups and DMs together."
}
},
{ {
"name": "Send Message to Room", "name": "Send Message to Room",
"request": { "request": {

3521
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "paarrot", "name": "paarrot",
"version": "4.11.121", "version": "4.11.191",
"description": "Paarrot - A Matrix client based on Cinny", "description": "Paarrot - A Matrix client based on Cinny",
"homepage": "https://github.com/Paarrot/Paarrot-Desktop", "homepage": "https://github.com/Paarrot/Paarrot-Desktop",
"repository": { "repository": {
@@ -13,13 +13,15 @@
}, },
"scripts": { "scripts": {
"dev": "concurrently \"npm run dev:vite\" \"npm run dev:electron\"", "dev": "concurrently \"npm run dev:vite\" \"npm run dev:electron\"",
"dev:vite": "cd cinny && npm start", "dev:vite": "cross-env BROWSER=none sh -c 'cd cinny && npm start'",
"dev:electron": "wait-on http://localhost:38347 && cross-env NODE_ENV=development electron .", "dev:electron": "wait-on http://localhost:38347 && cross-env NODE_ENV=development electron .",
"build": "node -e \"require('fs').copyFileSync('config.json', 'cinny/config.json')\" && cd cinny && npm run build && cd .. && electron-builder --publish always", "build": "node scripts/generate-update-manifest.mjs && node -e \"require('fs').copyFileSync('config.json', 'cinny/config.json')\" && cd cinny && npm run build && cd .. && electron-builder --publish always",
"build:local": "node -e \"require('fs').copyFileSync('config.json', 'cinny/config.json')\" && cd cinny && npm run build && cd .. && electron-builder", "build:local": "node scripts/generate-update-manifest.mjs && node -e \"require('fs').copyFileSync('config.json', 'cinny/config.json')\" && cd cinny && npm run build && cd .. && electron-builder",
"build:linux": "npm run build -- --linux", "build:linux": "npm run build -- --linux",
"build:win": "npm run build -- --win", "build:win": "npm run build -- --win",
"postman:generate": "node scripts/generate-postman-collection.js" "postman:generate": "node scripts/generate-postman-collection.js",
"generate:update-manifest": "node scripts/generate-update-manifest.mjs",
"playground": "npm --prefix cinny run playground"
}, },
"keywords": [], "keywords": [],
"author": { "author": {
@@ -28,24 +30,28 @@
}, },
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"adm-zip": "^0.5.16", "adm-zip": "^0.5.18",
"body-parser": "2.2.2", "body-parser": "2.3.0",
"cors": "2.8.6", "cors": "2.8.6",
"electron-store": "^8.2.0", "electron-store": "^11.0.2",
"electron-updater": "^6.3.9", "electron-updater": "^6.8.9",
"express": "5.2.1", "express": "5.2.1",
"open": "11.0.0" "open": "11.0.0"
}, },
"devDependencies": { "devDependencies": {
"@actions/github": "6.0.0", "@actions/github": "9.1.1",
"@electron/rebuild": "4.0.3", "@electron/rebuild": "4.2.0",
"concurrently": "9.2.1", "concurrently": "10.0.3",
"cross-env": "10.1.0", "cross-env": "10.1.0",
"electron": "40.6.0", "electron": "43.1.0",
"electron-builder": "26.8.1", "electron-builder": "26.15.3",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"png2icons": "2.0.1", "png2icons": "2.0.1",
"sharp": "0.34.5", "sharp": "0.35.3",
"wait-on": "9.0.4" "wait-on": "9.0.10"
},
"allowScripts": {
"electron-winstaller@5.4.0": true,
"electron@43.1.0": true
} }
} }

View File

@@ -164,6 +164,87 @@ const collection = {
{ {
name: 'Messaging', name: 'Messaging',
item: [ item: [
{
name: 'Get Group Messages',
request: {
method: 'GET',
header: [],
url: {
raw: '{{baseUrl}}/messages/groups?limit=10',
host: ['{{baseUrl}}'],
path: ['messages', 'groups'],
query: [{ key: 'limit', value: '10', description: 'Number of recent messages (1-100, default 10)' }],
},
description: 'Get the most recent messages across joined group channels (non-DMs).',
},
},
{
name: 'Get DM Messages',
request: {
method: 'GET',
header: [],
url: {
raw: '{{baseUrl}}/messages/dms?limit=10',
host: ['{{baseUrl}}'],
path: ['messages', 'dms'],
query: [{ key: 'limit', value: '10', description: 'Number of recent messages (1-100, default 10)' }],
},
description: 'Get the most recent messages across joined direct messages.',
},
},
{
name: 'Get Combined Messages',
request: {
method: 'GET',
header: [],
url: {
raw: '{{baseUrl}}/messages/combined?limit=10',
host: ['{{baseUrl}}'],
path: ['messages', 'combined'],
query: [{ key: 'limit', value: '10', description: 'Number of recent messages (1-100, default 10)' }],
},
description: 'Get the most recent messages across groups and DMs together.',
},
},
{
name: 'Get Group Unreads',
request: {
method: 'GET',
header: [],
url: {
raw: '{{baseUrl}}/unreads/groups',
host: ['{{baseUrl}}'],
path: ['unreads', 'groups'],
},
description: 'List unread group conversations (non-DMs), including counts and latest message preview.',
},
},
{
name: 'Get DM Unreads',
request: {
method: 'GET',
header: [],
url: {
raw: '{{baseUrl}}/unreads/dms',
host: ['{{baseUrl}}'],
path: ['unreads', 'dms'],
},
description: 'List unread DM conversations, including counts and latest message preview.',
},
},
{
name: 'Get Combined Unreads',
request: {
method: 'GET',
header: [],
url: {
raw: '{{baseUrl}}/unreads/combined',
host: ['{{baseUrl}}'],
path: ['unreads', 'combined'],
},
description: 'List unread conversations across groups and DMs.',
},
},
{ {
name: 'Send Message to Room', name: 'Send Message to Room',
request: { request: {

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env node
/**
* Desktop repo wrapper — manifest generator lives in cinny/scripts/.
*/
import { spawnSync } from 'child_process';
import path from 'path';
import { fileURLToPath } from 'url';
const script = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'cinny',
'scripts',
'generate-update-manifest.mjs'
);
const result = spawnSync(process.execPath, [script], { stdio: 'inherit' });
process.exit(result.status ?? 1);

104
scripts/migrate-icons.mjs Normal file
View File

@@ -0,0 +1,104 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const srcRoot = path.resolve(__dirname, '../cinny/src');
const iconsModule = path.resolve(srcRoot, 'app/components/icons');
const ICON_SYMBOLS = new Set(['Icon', 'Icons', 'IconName', 'IconSrc']);
function walk(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(fullPath, files);
} else if (/\.(tsx?)$/.test(entry.name)) {
files.push(fullPath);
}
}
return files;
}
function getIconsImportPath(filePath) {
const rel = path.relative(path.dirname(filePath), iconsModule).replace(/\\/g, '/');
return rel.startsWith('.') ? rel : `./${rel}`;
}
function splitFoldsImport(importClause) {
const match = importClause.match(/^\{([^}]+)\}(?:\s+as\s+\w+)?$/);
if (!match) return null;
const specifiers = match[1]
.split(',')
.map((part) => part.trim())
.filter(Boolean)
.map((part) => {
const [local, imported] = part.split(/\s+as\s+/).map((s) => s.trim());
return { local: local ?? imported, imported: imported ?? local };
});
const iconSpecs = specifiers.filter((s) => ICON_SYMBOLS.has(s.imported));
const foldsSpecs = specifiers.filter((s) => !ICON_SYMBOLS.has(s.imported));
if (iconSpecs.length === 0) return null;
return { iconSpecs, foldsSpecs };
}
function migrateFile(filePath) {
let content = fs.readFileSync(filePath, 'utf8');
const importRegex = /import\s+(\{[^}]+\})\s+from\s+['"]folds['"];?/g;
let changed = false;
const iconImports = new Map();
content = content.replace(importRegex, (fullMatch, importClause) => {
const split = splitFoldsImport(importClause);
if (!split) return fullMatch;
changed = true;
for (const spec of split.iconSpecs) {
iconImports.set(spec.local, spec.imported);
}
if (split.foldsSpecs.length === 0) {
return '';
}
const foldsImport = split.foldsSpecs.map((s) => (s.local === s.imported ? s.local : `${s.imported} as ${s.local}`)).join(', ');
return `import { ${foldsImport} } from 'folds';`;
});
if (!changed) return false;
const iconsPath = getIconsImportPath(filePath);
const iconExportList = [...iconImports.entries()]
.map(([local, imported]) => (local === imported ? imported : `${imported} as ${local}`))
.join(', ');
const iconsImportLine = `import { ${iconExportList} } from '${iconsPath}';`;
const foldsImportMatch = content.match(/^import\s+\{[^}]+\}\s+from\s+['"]folds['"];?\s*$/m);
if (foldsImportMatch) {
const insertAt = content.indexOf(foldsImportMatch[0]) + foldsImportMatch[0].length;
content = `${content.slice(0, insertAt)}\n${iconsImportLine}${content.slice(insertAt)}`;
} else {
content = `${iconsImportLine}\n${content}`;
}
content = content.replace(/\n{3,}/g, '\n\n');
fs.writeFileSync(filePath, content);
return true;
}
const files = walk(srcRoot);
let migrated = 0;
for (const file of files) {
if (file.includes(`${path.sep}components${path.sep}icons${path.sep}`)) continue;
if (migrateFile(file)) {
migrated += 1;
}
}
console.log(`Migrated ${migrated} files.`);

View File

@@ -18,6 +18,17 @@
"updater:allow-download", "updater:allow-download",
"updater:allow-download-and-install", "updater:allow-download-and-install",
"dialog:default", "dialog:default",
"fs:default",
"fs:allow-write-file",
{
"identifier": "fs:scope",
"allow": [
{ "path": "$HOME/**" },
{ "path": "$DOWNLOAD/**" },
{ "path": "$DESKTOP/**" },
{ "path": "$DOCUMENT/**" }
]
},
"process:allow-restart", "process:allow-restart",
{ {
"identifier": "http:default", "identifier": "http:default",

View File

@@ -60,6 +60,16 @@ async function runTests() {
// Get current room // Get current room
await testEndpoint('Get Current Room', 'GET', '/room/current'); await testEndpoint('Get Current Room', 'GET', '/room/current');
// Recent messages by scope
await testEndpoint('Get Group Messages', 'GET', '/messages/groups');
await testEndpoint('Get DM Messages', 'GET', '/messages/dms');
await testEndpoint('Get Combined Messages', 'GET', '/messages/combined?limit=10');
// Unread conversations by scope
await testEndpoint('Get Group Unreads', 'GET', '/unreads/groups');
await testEndpoint('Get DM Unreads', 'GET', '/unreads/dms');
await testEndpoint('Get Combined Unreads', 'GET', '/unreads/combined');
// Toggle mute // Toggle mute
await testEndpoint('Toggle Mute', 'POST', '/mute/toggle'); await testEndpoint('Toggle Mute', 'POST', '/mute/toggle');