Void Presence Application Release
Track current and past releases, prereleases, and published builds.
Track current and past releases, prereleases, and published builds.
Latest GitHub release appears at the top.
Prereleases and nightly builds are ordered by release date.
View downloads analyticsIf your Rich Presence buttons are not visible in Discord, see the status page for details.
Read setup & troubleshootingdiscord-token-config for both read and write operations, preventing third-party plugins from ever touching sensitive Discord token data by filename.readConfig and writeConfig now enforce an explicit deny-list for sensitive configuration keys (including discord-token-config) and are ready for an allow-list-based policy, significantly reducing the risk of file-based data exfiltration through the plugin API surface.i18next.setupIntervalControl to eliminate silent failures. The logic now directly overwrites corrupted or non-numeric localStorage entries with valid parameters.window.electronAPI.setActivityInterval broadcast during initialization, completely removing timing gaps between UI states and the background process.ERR_DLOPEN_FAILED / Could not load the "sharp" module) on Apple Silicon Mac devices by correcting how native C++ binary dependencies (libvips) are handled and read by the OS..dmg installer and a portable .zip are published with every release..deb, .rpm, and a portable .zip are published with every release.sysctl, system_profiler) and Linux (/proc/cpuinfo, /sys/class/thermal, sensors, /sys/class/drm) in addition to Windows.updates.ts picks the correct installer extension (.exe / .dmg / .deb) and launch method per platform. The self-update flow opens the DMG on macOS and runs pkexec dpkg -i on Linux.pluginInstallFromUrlSuccess / pluginInstallFromUrlFailed), with translations available in English, Russian, and Turkish for consistent feedback across languages.plugins:install-from-url handler so hot reloading is applied only to folder-based plugins (GitHub tree/zip installs), while single-file .js plugins are downloaded and stored without immediate hot-load, reducing unnecessary runtime surface.onInstallPluginFromUrl handler now accepts a single structured payload and routes it into a typed IPC call, preventing accidental extension of the deep link surface with unvalidated flags.setActiveView shell controller, making all navigation state changes flow through a single, predictable code path..node binaries); the app automatically runs npm install followed by electron-rebuild to recompile native modules against the correct Electron ABI, with a .rebuilt marker to skip redundant rebuilds on subsequent launches.plugin-name/index.js) instead of a single .js file, enabling plugins to include a worker.js, package.json with dependencies, and a manifest.json with metadata.worker.js, it is automatically launched in a Worker thread (same pattern as the builtin hardware plugin), allowing CPU-intensive work to run off the main thread.PluginContext now exposes writeConfig(name, data) and a smart readConfig(name) that reads from plugins-data/{pluginId}/ first, falling back to shared userData configs (buttons, imageCycles, etc.).external-plugins-state.json; state is restored on startAll without needing a manual settings entry per plugin.pluginDir in PluginContext — plugins receive the path to their own folder via ctx.pluginDir, enabling them to resolve local worker scripts, assets, or config files relative to themselves.voidpresence://install-plugin?zip=<github-tree-url> now downloads a folder plugin directly from GitHub via the Contents API (no zip required), with a markPluginInstalling flag that prevents the file watcher from firing prematurely.getNextButtons and readButtonsConfig, cycling through button pairs the same way the default plugin does.select control on the hardware plugin card instead of a standalone section on the main config page.voidpresence.site/plugins (browse community plugins) and the GitHub plugins folder (submit your own).shell:open-external IPC — a generic openExternal(url) API is now available in the renderer for opening any validated https:// URL in the system browser.startAllPlugins no longer blocks IPC registration — plugin loading (including npm install and electron-rebuild) now runs in the background via void startAllPlugins(), so IPC handlers register immediately and the renderer never gets "no handler registered" errors on startup.spawn EINVAL fix on Windows — npm install and electron-rebuild now use npm.cmd / npx.cmd with shell: false on Windows, eliminating the EINVAL and DEP0190 errors caused by passing .cmd executables with shell: true and an args array.startPluginsWatcher now handles both .js files and directory-based plugins, triggering hot-load or unload correctly when a folder is added or removed from the plugins directory.systeminformation replaces wmic/powershell/nvidia-smi calls; CPU load uses os.cpus() diff (no WMI), RAM uses os.totalmem/freemem, GPU stats are cached for 30 seconds, cutting CPU usage significantly.sendLog and sendToast call in plugin-manager.ts and the install handler in ipc.ts now goes through t() with keys in en, ru, and tr locales.build:manifest script — node scripts/build-manifest.js auto-generates plugins-manifest.json by scanning the plugins/ folder and extracting metadata (author, description, tags, preview.slides) directly from module.exports in each plugin file."folder": true flag and a GitHub tree sourceUrl; the install button sends ?zip=<tree-url> to the deep link handler.page.tsx using the GitHub API before the page is sent to the client, eliminating avatar fetch waterfalls in the browser.plugins directory: adding, editing, or removing a .js file automatically refreshes the plugin list and restarts the plugin without restarting the app.plugins folder, and immediately makes it available in the plugins page.controls (toggles, selects, inputs), which are rendered dynamically on the plugins page and wired to IPC methods, allowing plugins to expose rich configuration without touching core UI code.PresencePayload are always in sync with what you see on the plugins page.getPayload presence), conflict-checked against built-in plugins, and errors are surfaced in the logs and toast notifications instead of silently breaking the activity pipeline.package.json and dependencies; the app runs npm install in the plugin’s directory when needed, keeping the core desktop bundle lean while letting plugins use heavier libraries if required.image-cycles GIFs) now rely on shared config data instead of bundling their own image processing code, reducing plugin startup overhead and keeping the client more stable.settings:set-video-filter). The renderer now passes the boolean state correctly, and the main-process handler applies the change reliably, so the filter switches as expected..on('value')) to look up strictly targeted author resources instead of listening to the global database root, completely forgetting cascade server crashes under traffic spikes.executeJavaScript string evaluations of untrusted external protocol variables, routing incoming payload structures natively over isolated IPC channels (webContents.send).authorId foreign key instead of baking mutable author names and avatars directly into configurations.uniqueAuthorIds) in global queries and Server-Sent Events (SSE) streams, fetching and combining real-time profile data dynamically instead of downloading entire raw collection snapshots.users branch.buildConfigToOwnerMap/findUserByConfig) in individual fetch, delete, and streaming endpoints, replacing whole-table memory processing with direct key-based lookups..on('value')) to look up strictly targeted author resources instead of listening to the global database root, completely preventing cascade server crashes under traffic spikes..update() properties.@electron-forge/maker-squirrel, removing legacy installation pipeline configs and preventing unneeded NuGet tools overhead during project setup.@electron/rebuild), hot-reloaders (electron-reloader), cross-platform environment helpers (cross-env), and unnecessary Linux distribution makers (maker-deb, maker-rpm) to minimize the project setup size and drastically speed up npm install execution times.@typescript-eslint/eslint-plugin, @typescript-eslint/parser, and eslint-plugin-import, leaving only the core eslint setup for simplified code quality maintenance.} at the end of the executeJavaScript template string, which was breaking the injected IIFE and causing an Unexpected token '}' during URL import.author nickname in Realtime Database alongside authorTag, so streams and listings can always show the creator name.authorName.authorAvatar URL resolved from the author profile, so the site and stream UI can display the creator’s avatar without extra lookups.author field when writing configs; it keeps all payload fields except kind, including the author nickname.authorName, the uploader calls /v1/authors/{authorId}/configs to fetch user.name and user.avatar, and uses them as author and authorAvatar for the new config.The main reason for introducing the internal REST API endpoint is to move all sensitive update and upload logic off the client and into the backend. By doing this, the Electron app no longer needs to know about internal author identifiers or touch the database layer directly. Instead, it only talks to a stable HTTP surface, and the server is responsible for resolving author IDs, enforcing permissions, and shaping responses.
From a security perspective, this effectively closes off direct database exposure and reduces the blast radius of any client-side issue. The client sees opaque IDs and sanitized payloads, while the backend can evolve its schema, validation and authorization rules without leaking internals to the outside world. This also makes it easier to apply API security best practices such as “do not expose more data than necessary”, strict rate limiting and centralized error handling in one place.
This also opens the door to working with sensitive integrations entirely on the API side, including Discord tokens and other secret-bound flows. With that logic moved out of the Electron client, it becomes possible to reuse the same backend for future web or bot versions of Presence without exposing secrets in the desktop app.
The new version was already ready to ship, but 3.0.1 kept it blocked because of the weekly contract requirement. This delayed the release even though the update itself was prepared and fully functional.
This issue has worried me for a long time, because with only the database address it would be possible to expose or extract a lot of sensitive information. By sensitive data here, I also mean abuse risks such as artificially inflating activity, stats, or presence counts through author IDs and related internal fields. Again, we are not storing tokens or client IDs, but fake engagement, spammed presences, and manipulated statistics are still a real problem and something I want to prevent.
https://api.voidpresence.site/v1/github/application that proxies the latest GitHub release for the main app, returning the tag, installer asset name, download URL, and release notes body as JSON..txt file.api.voidpresence.site instead of calling GitHub directly from the client.IMPORTANT: THIS RELEASE INTRODUCES A NEW VOID PRESENCE INSTALLER. LEGACY UPDATES WILL CONTINUE TO WORK FOR 1 WEEK FROM THE RELEASE DATE (28/06/2026). AFTER THAT, ONLY THE NEW INSTALLER‑BASED UPDATE FLOW WILL BE SUPPORTED. IF AUTO‑UPDATE DOES NOT WORK, YOU SEE ERRORS, OR VOID PRESENCE DOES NOT START AFTER UPDATE, YOU MUST OPEN THE RELEASE PAGE AND UPDATE MANUALLY USING THE INSTALLER FROM THIS RELEASE.
updateOverlay.close value instead of a hardcoded label, so the text matches the active language.IMPORTANT: THIS RELEASE INTRODUCES A NEW VOID PRESENCE INSTALLER. LEGACY UPDATES WILL CONTINUE TO WORK FOR 1 WEEK FROM THE RELEASE DATE (28/06/2026). AFTER THAT, ONLY THE NEW INSTALLER‑BASED UPDATE FLOW WILL BE SUPPORTED. IF AUTO‑UPDATE DOES NOT WORK, YOU SEE ERRORS, OR VOID PRESENCE DOES NOT START AFTER UPDATE, YOU MUST OPEN THE RELEASE PAGE AND UPDATE MANUALLY USING THE INSTALLER FROM THIS RELEASE.
Void.Presence.Setup.<version>.exe directly into the app directory instead of a temporary location, so the installer always runs next to the current executable.void-updates → void-presence pipeline ensures that each release exposes one up‑to‑date installer asset that the client can safely use for upgrades.Void.Presence.Setup.<version>.exe from Assets, and run it manually to migrate your installation to the new installer flow.setupPresenceControls helper so one place manages both Restart and Stop actions.restartDiscordRich and stopDiscordRich are called from setupPresenceControls() and update RPC state (RPC_RESTARTING, RPC_DISABLED) as well as the current presence info.setupToasts() now exposes additional helpers for presence-related events, including restart and stop, so users always get immediate visual feedback after clicking Restart or Stop.vpConfigs array from local storage.deepCloneState) instead of a direct reference from vpConfigs, so changes in the UI no longer corrupt the original profile.buildBaseStateFromConfig, ensuring correct clientId, intervals, time modes and activity type even for partial configs.FullState.status-configs cloud branch — status presets are stored in their own collection, keeping them isolated from activity configs.voidpresence://auth?... protocol to instantly link your web profile with the desktop app.name and provider from the protocol URL and shows them next to your author ID./status-configs path and respect Firebase rules for this branch.uploadConfig and uploadStatusConfig parameters to remove implicit any diagnostics.buttonPairs, cycles, imageCycles, party for configs; statusCycles for statuses) are sent to the cloud.voidpresence:// URLs when the app is already running, including focus/restore logic and pending URL queue.config.searchConfigsPlaceholder, status.discordTokenPlaceholder, activity.clientIdPlaceholder, recent.appNotFound, and recent.fetchFailed.updatePlaceholders() called in language change handlers.details.add and buttons.add now display their translated text normally instead of showing the key itself.updatePlaceholders(), making it easier to maintain and extend.timestamp-mode-btn and settings-language-selector both call updatePlaceholders() after setting language.averageColor from the first imageCycles[0].largeImage using sharp, so each card gets a color derived from its preview image.averageColor smoothly after a short delay, which makes the UI feel softer and more polished.averageColor alongside the other fields.sharp for color extraction — the image is resized and sampled efficiently before calculating the average hex color.statusCycles from cloud upload — the statusCycles field is now deleted before uploading config to cloud to prevent unnecessary data storage in cloud:uploadConfig IPC handler.<div data-i18n="config.uploadCurrent">Upload Current</div> to match the overall page styling.Get-WmiObject MSAcpi_ThermalZoneTemperature) in getCpuTemperature() to improve CPU temp reading reliability on Windows when the first CimInstance method fails.Buttons and DETAILS, keeping rich presence output cleaner.details now shows only the load bar, while state includes the label, temperature, and load percentage for a cleaner RPC layout.resolveCoverUrlFromITunes now handles failures more cleanly and avoids unnecessary extra work.clearActivity() behavior was removed from places where it could wipe active presence too aggressively.Version 2.13.13 is UNSTABLE To update, download and reinstall Void Presence again from the latest release manually.
Version 2.13.13 is UNSTABLE To update, download and reinstall Void Presence again from the latest release manually.
Version 2.13.13 is UNSTABLE To update, download and reinstall Void Presence again from the latest release.
.client-row span width to 85px with word-break to prevent long text cutoffVersion 2.13.13 is UNSTABLE To update, download and reinstall Void Presence again from the latest release.
retry_after to prevent repeated 429 errors due to network ping/timezone differenceslanguage.json and can be changed in settings. Translations are loaded via IPC from main process to renderer.Fix: The app now attempts a second send before logging the failure, and status updates were cleaned up to match the new flow
updateExistingLogItem helper function - extracts common log update logic for reuseIssue: Log spam with repeated Custom status apply error: fetch failed messages every few seconds
Fix: Errors now consolidate into a single line that updates with the latest error message using the new helper function
rpcEnabled check in SMTC worker - worker now verifies RPC is enabled before auto-startingrpcEnabled check in Hardware worker - worker now verifies RPC is enabled before auto-startingrpcStarted = false reset - flag reset when toggling RPC on to prevent duplicate startsIssue: RPC would immediately turn back on after being toggled off due to hardware/SMTC workers auto-starting it
Fix: Workers now respect rpcEnabled setting and won't start RPC if it's disabled
statusEnabledBrowser toggle - new setting to enable Custom Status in browser modesetStatusEnabledBrowser() IPC method - new Electron API method for browser mode togglestatusEnabledBrowser to settings - stored in localStorage and persisted across restartsisDiscordTokenValid() function - eliminated unnecessary API call that caused "token invalid" errors after ~1 minuteRate Limits:
retry_after seconds before next attempt/users/@me/settingsretry_after from Discord's 429 response and waits the exact amount of seconds before retrying.retry_after seconds (e.g. 9.6s) before next custom status attempt.discordToken field is automatically removed from configData before uploading.\AppData\Roaming\Void Presence\discord-token-config.Your Discord token gives full access to your account. This fix ensures that even if you share a config publicly, your token stays private and local.
\AppData\Roaming\Void Presence\discord-token-configYour Discord token is now safer — the tutorial makes it crystal clear that this sensitive credential should never be shared. The app continues to store your token locally on your machine only, with no server storage.
currentIndex, lastSignature, hasEverBeenReady, and hasLoggedReadyOnce are properly reset when custom status is disabled, when token is missing, or when token is invalid./users/@me and stops gracefully when the token is invalid (401/403), detecting that the user has logged out.CUSTOM_STATUS_SEARCHING_DISCORD, CUSTOM_STATUS_CONNECTING, CUSTOM_STATUS_READY, CUSTOM_STATUS_DISABLED, and CUSTOM_STATUS_ERROR, so you always know what's happening.updateIntervalSec) was being reset to the default value (30) on load, even when a custom value (e.g. 5 or 10) was saved. Now the saved interval is correctly preserved and applied from localStorage without being overwritten by the default.tutorial-inline-open-dev, get-author-id, `get-video-id'), via the appropriate IPC channels.science and copy the header authorization as a token.nowPlaying (SMTC) or hardwareStats (hardware) message first.rpcStarted flag to guarantee RPC initializes exactly once, even if multiple worker messages arrive.hardwareMonitorEnabled is enabled, using the same style as other stats.PresencePayload with priority and split state into activePayload (media) and fallbackPayload (hardware) — the visible payload is now always active ?? fallback, без смешивания.start/end pair.details/state in Discord; media presence now fully overrides hardware while playback is active.PresencePayload typing by adding the priority field to the shared type, so internal priority logic no longer triggers “object literal may only specify known properties” during build.localStorage and refreshBarStyleVisibility().stopDiscordRich() now awaits savePersistOffsetIfNeeded() — config file always updated before exit.updatePersistOffsetIfNeeded() fire-and-forget → awaited in stopDiscordRich() — Promise always resolves before shutdown.setTimestampConfig() Promise hangs → Emergency save on stop with await guarantee.reattachDnDForProfiles rebinds DnD for all profile‑related lists after applying a config.createListManager, but wired it to the shared DnD helper so newly rendered rows automatically participate in sorting.applyStateToUIAndLists.······) on the left side of all list rows:
······ rotated 90° so they appear vertical on the left.draggable attribute now lives only on .drag-handle, not on the whole row.drag‑handle across all list‑rendering helpers:
createRow generic helper now includes .drag-handle.renderRecentApps now includes .drag-handle for recent app entries.dragstart was triggered somewhere other than the handle.Connecting RPC Client ID: <id>).App not found or Fetch failed) and are visually dimmed./applications/{ID}/rpc endpointoverflow: hidden from config panels — long data lists (cycles, buttons, recent apps) now properly scroll instead of clipping contentpersist timestamp offset reset on app restart after a power loss — persistOffsetSec is now preserved in the config and no longer gets overwritten by renderer state.length > 18) before saving to client-config.json.clientId: null after restart — empty strings from inputs no longer overwrite valid IDs.client-config.json, settings.json, image-cycles.json, party-config.json, timestamp-config.json, etc.) are now written via temporary file + rename when possible.tmp‑file cleanup.live-set-party, live-set-images, and related IPC handlers now safely recover from ENOENT / EPERM filesystem errors during config writes.(optional) placeholder text:
{ placeholder: 'State (optional)', value: entry.state || '' }.state values are represented with a clear, optional‑labelled placeholder instead of leaving misleading empty labels.now / range / persist and nowMode.EPERM: operation not permitted and ENOENT: no such file or directory errors when saving:
image-cycles.jsonclient-config.jsonparty-config.jsonvoidpresence://auth?authorId=....authorId is saved into localStorage and the config-author-input field;authorId copy‑paste is required — the profile is pre‑connected.Guaranteed First Activity Push
Discord RPC now always pushes config cycles activity immediately on ready via pushActivity(null).
Fixed complete index reset (cycleIndex=imageIndex=buttonIndex=partyIndex=0) — first cycle guaranteed on startup.
Session State Consistency
Proper initialization of currentTitle, lastSmTcStatus/Position, and lastJsonSignature for seamless pollJsonLoop() transitions.
attachDnD<T> generic with precise drop zones (drop-target-top/drop-target-bottom), stable hover states via dragenter/dragleave, and TypeScript-safe dataset handling.
Smoother dragging, no flickering, accurate drop positioning — fully reliable across all list types.Unified List Management
All user-editable lists (buttons, cycles, images, party, time blocks) now share a common createListManager<T> helper.
This reduces duplicated DOM-rendering and localStorage-saving code, making the config UI modules much more DRY and maintainable.
Reusable Row-Creation Helpers
Each config row (createButtonPairRow, createCycleRow, createTimeRow, etc.) is now built on top of a central createRow helper.
Row-related DOM creation, event listeners, onUpdate and onRemove hooks are now centralized, improving consistency across features and reducing boilerplate.
Config Module Organization The config-related logic has been split into clearly separated modules:
list-renderers – common list-handling and createListManager;rows – row-creation helpers;clientId-controls – clientId, intervals, toasts, drag-and-drop setup;mode-controls – timestamp and “now mode” logic;time-controls – time-cycles handling.
This makes it easier to locate, update, and test specific pieces of the UI.Helper-Driven Architecture Many duplicated patterns (DOM-element creation, localStorage I/O, drag-and-drop wiring) have been moved into reusable helpers instead of being scattered inline across multiple functions. This improves type-safety, testability, and makes future UI changes less error-prone.
Type-Safe Attachment Layer DnD-attaching and list-rendering now use a generic-style pattern, so the same logic can be applied to different list types without manual code duplication. TypeScript errors are easier to catch upfront, and the structure is more predictable for contributors.
No UI Behavior Changes From the user’s perspective, nothing has changed:
>= 1) instead of raw JavaScript milliseconds.
This fixes validation errors such as:
child "activity" fails because [child "timestamps" fails because [child "start" fails because ["start" must be larger than or equal to 1]]]
when updating activity via SET_ACTIVITY.Consistent Time Format for Discord
All internal time calculations still use JavaScript milliseconds (from Date.now()), but before sending data to Discord, they are converted to Unix seconds using a small helper function.
This ensures that the activity.timestamps.start and activity.timestamps.end fields always match Discord’s expected format and range.
Safe Timestamp Conversion Layer A dedicated conversion helper now:
Math.floor(ms / 1000).< 1), so Discord never receives zero or negative timestamps.activity.timestamps object right before the SET_ACTIVITY request is sent.No Behavior Changes for Existing Features All existing behaviors (such as “now”, “range”, “persist” timestamp modes and playback progress from the media session) remain the same from the user’s point of view. Only the way timestamps are formatted for Discord has changed, making the integration more stable and standards‑compliant.
dotenv Dependency
The project no longer depends on the dotenv package for loading environment variables.ready Handler Adjusted
The localClient.on('ready') handler now:
NowPlaying snapshot from SMTC.pushActivity(np) once and only then sets sendStatus('ACTIVE').lastJsonSignature based on the initial snapshot so that the polling loop does not immediately re‑send the same payload.live-set-timestamp IPC handler so that switching timestamp modes (now, range, persist) no longer forces a Discord Rich Presence restart. Mode, range, and display settings now apply live without interrupting the active RPC session, making timestamp experimentation feel more responsive in the UI.mode, rangeMin, rangeMax, persistOffsetSec, nowMode, timeCycles) now flows through the shared setTimestampConfig helper, ensuring consistent validation, rounding, and safety checks across both regular and live update paths.persistOffsetSec timestamp configuration so that large jumps are ignored. Incoming persistOffsetSec values are still rounded to the nearest 5 seconds, but if a new value is more than 10 seconds higher than the currently stored offset, the change is discarded and the existing value is preserved. This prevents accidental or noisy updates from causing sudden, unrealistic jumps in the persisted timestamp.uploadBtn.innerHTML) to better match the rest of the UI, improving visual consistency and making the upload action more noticeable without changing its behavior.Centralized Main Config Module
All JSON config read/write logic (client-config, buttons, cycles, image-cycles, party, timestamp, settings) has been consolidated into a single main/config.ts module, using a generic Validator<T> and safe helpers readJsonWithSchema / writeJsonSafe for consistent validation and error handling.
Async Settings API
Legacy synchronous loadSettings / saveSettings have been replaced with asynchronous readSettings / writeSettings, and readFiltersState can now rely on the shared Settings validator, reducing duplication and improving code clarity.
tag: broken
UpdateInfo shape for updates, including changelogMd for the inline changelog.UpdateInfo shape for updates, including changelogMd for the inline changelog.Lighter Gray Theme Refresh: Switched from dark theme to a softer gray palette for reduced eye strain and modern feel — lighter, more comfortable for long sessions.
New Tutorials: Added guided tutorials on the main screen and config pages for easier onboarding.
Overall UI Lightness: Streamlined layouts, smoother animations, and better readability across all modes.
tag: stable
basic, while exports always include the current mode for clearer sharing and backup behavior.basic across the app (UI state, local config and Electron settings) to avoid mismatched behavior on fresh installs.fade-in, translateY) for all views and modals.TypeError: Cannot read properties of undefined (reading 'state') that occurred when reducing the number of entries in Cycles or Image Cycles while the app was running.cycleIndex against the current array length. If the configuration is saved with fewer entries, the index automatically resets to 0 instead of crashing the update loop.cycleIndex management across both Basic and Advanced RPC modules.v2.3.4 - Fix Ipc Modules & SMTC Worker
Basic / Advanced RPC modes
Added switchable RPC mode: Basic (classic Rich Presence without SMTC) and Advanced (Advanced Rich Presence with media player and SMTC worker support).
The mode is saved in the settings and automatically applied the next time the app is launched.
Safe RPC restart without duplicate sessions
Restarting Discord Rich Presence now correctly stops an active session before starting a new one, reusing the single path and the startDiscordRich callback.
This prevents the accumulation of background RPC connections and eliminates duplicate activity updates.
The SMTC worker is enabled only in Advanced mode
The SMTC worker now starts only in Advanced mode and stops automatically when switching to Basic.
This reduces overhead costs in scenarios where media integration is not needed.
Consistent activity interval handling
Activity update intervals are now redirected via the common function setActivityInterval, which takes into account the selected RPC mode.
This ensures predictable timing behavior in both Basic and Advanced modes.
basic/advanced) and delegates the start/stop calls and interval settings to the appropriate modules.startDiscordRich helper, which minimizes duplicate logic and simplifies RPC lifecycle management.SMTC_USER_DATA and polls the media status at a fixed interval, publishing only current changes.Reduced CPU usage for SMTC worker
The SMTC worker now caches the media session status and updates the now-playing' file.json only for actual track changes or playback status.
This reduces the number of accesses to the system API and writes to disk, reducing the load in the background.
Optimized now-playing.json polling in RPC
The Discord RPC module reads now-playing.json uses a single secure method and reuses the received data instead of re-reading and parsing the file in several places.
Unnecessary accesses to the file system are removed, which reduces peak CPU consumption.
Smarter Rich Presence updates
Discord activity updates are now performed only when the track signature (name, status, position) changes, and not on every tick of the timer.
This reduces the number of SET_ACTIVITY requests and helps you avoid hitting Discord's update frequency limits.
Track cover caching with size limit
A limited cache of track covers has been added to avoid requesting iTunes, TheAudioDB, and MusicBrainz during each update cycle.
The cache is automatically cleared when the limit is reached, keeping memory usage under control.
NowPlaying publishing path, which minimizes duplicate work.readNowPlayingSafe) and transmits the result directly to pushActivity, eliminating repeated parsing of JSON.Correct timestamp persistence
Saving settings via SAVE and RESTART PRESENCE no longer resets persistOffsetSec to 0.
The current offset value (for example, 110 seconds) is now saved until the user explicitly resets.
Reset saved timestamp is working correctly
The reset saved timestamp button now:
resets only the saved timestamp (the persistOffsetSec field),
does not touch mode, rangeMin, RangeMax, nowMode and timeCycles,
Secure saving of time configuration
The updated logic of setTimestampConfig now:
merges with the current config (readTimestampConfig),
does not overwrite persistOffsetSec if it is not passed from renderer,
now uses the currentpersistOffsetSec' if the field was not passed from the UI.setTimestampConfig from renderer during SAVE and RESTART PRESENCE no longer resets the internal 'saved time'.resetPersistTimestamp + overwrite only `persistOffsetSec').Basic vs Music and Youtube) with seamless switching — Music and Youtube now intelligently detects and displays music activity (Spotify) and YouTube playback alongside custom presence data.sendLog integration for critical events like SMTC worker lifecycle.RPC Mode now shows real music and YouTube activity in Discord status!
now), randomized ranges (range), or a persistent start point (persist) for your activity timer.Save and Restart RPC now only adds a new history entry for a client ID if it does not already exist, avoiding overwriting names or timestamps of existing entries.autoHideOnStart enabled — createMainWindow now always called on app launchstartDiscordRich call, preventing conflicts and double updatesapp.whenReady(), hide only if autoHideOnStart=truegetMainWindow() + show() instead of createMainWindow() on clickUpload Current button in profile cards so it now uploads the full state of the selected profile instead of the current in-memory UI state.cfg.state.config-page.ts to add a dedicated handler for per-profile cloud upload that mirrors the global Upload Currentblur event handling.Removed the built-in auto-update helper due to unstable behavior and unnecessary complexity in production. The app now falls back to a simple version check and a link to the latest GitHub release, letting users decide when to download updates manually.
app-update.yml errors and other side effects caused by the auto-update setup in Windows builds.Expanded the version system with optional auto‑update flow and refined cloud config UX, building on top of the 1.4.x version check and warnings.
info / warn levels for update flow eventsLogLevel (info, warn, error, success) and consistent renderer stylingmain/, ipc/, logging/, cloud/, and updates/ modules for better maintainabilityAdded GitHub version check on startup with in‑app warning logs and status indicator when a newer Void Presence build is available.
app.getVersion()OUTDATED_VERSION RPC status with orange status chip and dot, indicating that an update is availableinfo, warn, error, success) from the main process, with colored indicators in the rendererwarn level (border, background, dot color) matching existing error/success semanticsrenderer/ folder for UI logic, state handling, and view wiringbridge/ and ipc/ modules for safer main–renderer communicationconfig/ (load/save, validation) and profiles/ (profiles list, active profile, cloud payload)Full TypeScript renderer and improved cloud config upload stability.
renderer.js to TypeScript with strict typing for Window, electronAPI, VoidPresenceCtx, cycles, and buttonsFileList → File, nullable fields for cycles and images)localStorage usage for configs, intervals, and auto-start/auto-hide flagsCloud config upload – users can now upload and share their Discord presence configurations directly to the cloud.
upload-config)push() for unique config IDselectronAPI.uploadConfigDownload the latest version "v1.4.0" and extract it to any location.
Your existing configurations from v1.3.2+ will continue working seamlessly. Local configs auto-sync with cloud on first launch.
Tray behavior fix – the window now correctly moves to the tray only when closing, while the minimize button keeps it visible in the taskbar.
Improvements:
Bug Fixes:
Download the latest version "v1.3.2" and extract it to any location. Your existing configurations and profiles will not work if you upgrade from v1.3.1 or lower.
Smarter profiles workflow – importing and managing presets is now simpler and more transparent directly from the config view.
Improvements:
.json file anywhere on the app, with dashed borders around the edges when the drop zone is activeNew Features:
Bug Fixes:
Richer session controls – managing Void Presence is now more convenient and transparent directly from the main view.
Improvements:
DISABLED, RESTARTING, DISCONNECTED, etc., reflected in both the status chip and logsNew Features:
Bug Fixes:
Not respondingDownload the latest v1.3.0 release and extract it to any location.
Your existing configurations and profiles will keep working as before, with the new session controls and profile details available right away after updating.
New pages and config tools – Void Presence is now easier to configure and maintain without touching your existing setups.
Improvements:
.json configs onto the +Config area.jsonNew Features:
Bug Fixes:
Download the latest v1.2.0 release and extract it to any location. Your existing configurations will continue to work as before, but you can now manage them through the new Config page.
Discord buttons are now optional – you no longer need to add button pairs to use Void Presence. Configure just your Client ID and status cycles, and the app will work perfectly without buttons.
Improvements:
Bug Fixes:
Download the latest release and extract to any location. No changes needed to existing configurations – existing button setups will continue to work as before.
v1.0.0-alpha Void Presence
Advanced Discord Rich Presence manager with customizable button pairs, text cycles, and image rotation. Status updates every 30 seconds with full control over your Discord activity display.
Easy Setup: Download the ZIP file from GitHub, extract it, run Void Presence.exe, and configure your Discord status in seconds.
Code button → Download ZIPVoid Presence.exeSAVE buttonGet your Application ID from Discord Developer Portal:
Ctrl+, – Show/hide windowCtrl+R – Restart Rich PresenceCtrl+Q – Quit applicationMade with ❤️ by Devollox
Void Presence – Control your Discord presence. Own your story.