From c04047078caf1127fe44b159d260e59aa1e67aec Mon Sep 17 00:00:00 2001 From: Natsumi Date: Sat, 23 Aug 2025 11:45:29 +1200 Subject: [PATCH] Add Discord RPC options --- Dotnet/Discord.cs | 5 +- src/localization/en/en.json | 12 ++- src/shared/constants/discord.js | 22 ++++- src/stores/settings/discordPresence.js | 118 +++++++++++++++++++++---- src/types/globals.d.ts | 3 +- src/views/Settings/Settings.vue | 47 +++++++++- 6 files changed, 180 insertions(+), 27 deletions(-) diff --git a/Dotnet/Discord.cs b/Dotnet/Discord.cs index 745dfaae..d81f9693 100644 --- a/Dotnet/Discord.cs +++ b/Dotnet/Discord.cs @@ -156,7 +156,8 @@ namespace VRCX string buttonText, string buttonUrl, string appId, - int activityType) + int activityType, + int statusDisplayType) { _lock.EnterWriteLock(); try @@ -212,7 +213,7 @@ namespace VRCX } _presence.Type = (ActivityType)activityType; - _presence.StatusDisplay = StatusDisplayType.Details; + _presence.StatusDisplay = (StatusDisplayType)statusDisplayType; Button[] buttons = []; if (!string.IsNullOrEmpty(buttonUrl)) diff --git a/src/localization/en/en.json b/src/localization/en/en.json index 70ce83ac..40613289 100644 --- a/src/localization/en/en.json +++ b/src/localization/en/en.json @@ -516,13 +516,17 @@ "discord_presence": { "discord_presence": { "header": "Discord Presence", - "description": "* Only active while VRChat is running.", + "description": "* Only active whilst in an instance.", "enable": "Enable", "enable_tooltip": "Recommended to disable Rich Presence in VRChat config.json to stop it from conflicting", - "instance_type_player_count": "Instance type/player count", + "instance_type_player_count": "Instance type & player count", "join_button": "Join button (public only)", - "show_details_in_private": "Show world details in private", - "show_images": "Show world images" + "show_details_in_private": "Show world details while in private", + "show_images": "Show world images", + "show_current_platform": "Show current platform", + "world_integration": "World integration", + "world_integration_tooltip": "Show \"Watching\\Listening to\" video for Popcorn Palace, PyPyDance, VRDancing and LS Media", + "display_world_name_as_discord_status": "Display world name as Discord status" }, "rpc": { "vr": "VR", diff --git a/src/shared/constants/discord.js b/src/shared/constants/discord.js index bf6ae61e..40bcc1fd 100644 --- a/src/shared/constants/discord.js +++ b/src/shared/constants/discord.js @@ -18,11 +18,31 @@ class ActivityType { } } +class StatusDisplayType { + static _Name = 0; + static _State = 1; + static _Details = 2; + + static get Name() { + return this._Name; + } + static get State() { + return this._State; + } + static get Details() { + return this._Details; + } +} + Object.freeze(ActivityType); +Object.freeze(StatusDisplayType); Object.defineProperty(ActivityType, '_Playing', { writable: false }); Object.defineProperty(ActivityType, '_Listening', { writable: false }); Object.defineProperty(ActivityType, '_Watching', { writable: false }); Object.defineProperty(ActivityType, '_Competing', { writable: false }); +Object.defineProperty(StatusDisplayType, '_Name', { writable: false }); +Object.defineProperty(StatusDisplayType, '_State', { writable: false }); +Object.defineProperty(StatusDisplayType, '_Details', { writable: false }); -export { ActivityType }; +export { ActivityType, StatusDisplayType }; diff --git a/src/stores/settings/discordPresence.js b/src/stores/settings/discordPresence.js index 4e59d986..7e4ed3dd 100644 --- a/src/stores/settings/discordPresence.js +++ b/src/stores/settings/discordPresence.js @@ -14,7 +14,10 @@ import { useGameLogStore } from '../gameLog'; import { useLocationStore } from '../location'; import { useUpdateLoopStore } from '../updateLoop'; import { useUserStore } from '../user'; -import { ActivityType } from '../../shared/constants/discord'; +import { + ActivityType, + StatusDisplayType +} from '../../shared/constants/discord'; import { useI18n } from 'vue-i18n-bridge'; export const useDiscordPresenceSettingsStore = defineStore( @@ -33,6 +36,9 @@ export const useDiscordPresenceSettingsStore = defineStore( discordHideInvite: true, discordJoinButton: false, discordHideImage: false, + discordShowPlatform: true, + discordWorldIntegration: true, + discordWorldNameAsDiscordStatus: false, isDiscordActive: false, lastLocationDetails: { tag: '', @@ -55,13 +61,22 @@ export const useDiscordPresenceSettingsStore = defineStore( discordInstance, discordHideInvite, discordJoinButton, - discordHideImage + discordHideImage, + discordShowPlatform, + discordWorldIntegration, + discordWorldNameAsDiscordStatus ] = await Promise.all([ configRepository.getBool('discordActive', false), configRepository.getBool('discordInstance', true), configRepository.getBool('discordHideInvite', true), configRepository.getBool('discordJoinButton', false), - configRepository.getBool('discordHideImage', false) + configRepository.getBool('discordHideImage', false), + configRepository.getBool('discordShowPlatform', true), + configRepository.getBool('discordWorldIntegration', true), + configRepository.getBool( + 'discordWorldNameAsDiscordStatus', + false + ) ]); state.discordActive = discordActive; @@ -69,6 +84,10 @@ export const useDiscordPresenceSettingsStore = defineStore( state.discordHideInvite = discordHideInvite; state.discordJoinButton = discordJoinButton; state.discordHideImage = discordHideImage; + state.discordShowPlatform = discordShowPlatform; + state.discordWorldIntegration = discordWorldIntegration; + state.discordWorldNameAsDiscordStatus = + discordWorldNameAsDiscordStatus; } const discordActive = computed(() => state.discordActive); @@ -76,6 +95,13 @@ export const useDiscordPresenceSettingsStore = defineStore( const discordHideInvite = computed(() => state.discordHideInvite); const discordJoinButton = computed(() => state.discordJoinButton); const discordHideImage = computed(() => state.discordHideImage); + const discordShowPlatform = computed(() => state.discordShowPlatform); + const discordWorldIntegration = computed( + () => state.discordWorldIntegration + ); + const discordWorldNameAsDiscordStatus = computed( + () => state.discordWorldNameAsDiscordStatus + ); function setDiscordActive() { state.discordActive = !state.discordActive; @@ -106,6 +132,28 @@ export const useDiscordPresenceSettingsStore = defineStore( state.discordHideImage ); } + function setDiscordShowPlatform() { + state.discordShowPlatform = !state.discordShowPlatform; + configRepository.setBool( + 'discordShowPlatform', + state.discordShowPlatform + ); + } + function setDiscordWorldIntegration() { + state.discordWorldIntegration = !state.discordWorldIntegration; + configRepository.setBool( + 'discordWorldIntegration', + state.discordWorldIntegration + ); + } + function setDiscordWorldNameAsDiscordStatus() { + state.discordWorldNameAsDiscordStatus = + !state.discordWorldNameAsDiscordStatus; + configRepository.setBool( + 'discordWorldNameAsDiscordStatus', + state.discordWorldNameAsDiscordStatus + ); + } initDiscordPresenceSettings(); @@ -162,9 +210,30 @@ export const useDiscordPresenceSettingsStore = defineStore( ); } - let platform = gameStore.isGameNoVR - ? t('view.settings.discord_presence.rpc.desktop') - : t('view.settings.discord_presence.rpc.vr'); + let platform = ''; + if (state.discordShowPlatform) { + if (gameStore.isGameRunning) { + platform = gameStore.isGameNoVR + ? ` (${t('view.settings.discord_presence.rpc.desktop')})` + : ` (${t('view.settings.discord_presence.rpc.vr')})`; + } else { + switch (userStore.currentUser.presence.platform) { + case 'web': + break; + case 'standalonewindows': + platform = ` (PC)`; + break; + case 'android': + platform = ` (Android)`; + break; + case 'ios': + platform = ` (iOS)`; + break; + default: + platform = ` (${userStore.currentUser.presence.platform})`; + } + } + } if (L.groupAccessType) { if (L.groupAccessType === 'public') { state.lastLocationDetails.groupAccessType = t( @@ -179,26 +248,26 @@ export const useDiscordPresenceSettingsStore = defineStore( switch (L.accessType) { case 'public': state.lastLocationDetails.joinUrl = getLaunchURL(L); - state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_public')} #${L.instanceName} (${platform})`; + state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_public')} #${L.instanceName}${platform}`; break; case 'invite+': - state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_invite_plus')} #${L.instanceName} (${platform})`; + state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_invite_plus')} #${L.instanceName}${platform}`; break; case 'invite': - state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_invite')} #${L.instanceName} (${platform})`; + state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_invite')} #${L.instanceName}${platform}`; break; case 'friends': - state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_friend')} #${L.instanceName} (${platform})`; + state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_friend')} #${L.instanceName}${platform}`; break; case 'friends+': - state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_friend_plus')} #${L.instanceName} (${platform})`; + state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_friend_plus')} #${L.instanceName}${platform}`; break; case 'group': - state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_group')} #${L.instanceName} (${platform})`; + state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_group')} #${L.instanceName}${platform}`; try { const groupName = await getGroupName(L.groupId); if (groupName) { - state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_group')}${state.lastLocationDetails.groupAccessType}(${groupName}) #${L.instanceName} (${platform})`; + state.lastLocationDetails.accessName = `${t('dialog.new_instance.access_type_group')}${state.lastLocationDetails.groupAccessType}(${groupName}) #${L.instanceName}${platform}`; } } catch (e) { console.error( @@ -252,6 +321,9 @@ export const useDiscordPresenceSettingsStore = defineStore( let stateText = state.lastLocationDetails.accessName; let endTime = 0; let activityType = ActivityType.Playing; + let statusDisplayType = state.discordWorldNameAsDiscordStatus + ? StatusDisplayType.Details + : StatusDisplayType.Name; let appId = '883308884863901717'; let bigIcon = 'vrchat'; let detailsUrl = state.lastLocationDetails.worldLink; @@ -282,7 +354,10 @@ export const useDiscordPresenceSettingsStore = defineStore( buttonUrl = ''; } - if (isRpcWorld(state.lastLocationDetails.tag)) { + if ( + isRpcWorld(state.lastLocationDetails.tag) && + state.discordWorldIntegration + ) { // custom world rpc if ( state.lastLocationDetails.worldId === @@ -293,6 +368,7 @@ export const useDiscordPresenceSettingsStore = defineStore( 'wrld_04899f23-e182-4a8d-b2c7-2c74c7c15534' ) { activityType = ActivityType.Listening; + statusDisplayType = StatusDisplayType.Details; appId = '784094509008551956'; bigIcon = 'pypy'; } else if ( @@ -302,6 +378,7 @@ export const useDiscordPresenceSettingsStore = defineStore( 'wrld_dd6d2888-dbdc-47c2-bc98-3d631b2acd7c' ) { activityType = ActivityType.Listening; + statusDisplayType = StatusDisplayType.Details; appId = '846232616054030376'; bigIcon = 'vr_dancing'; } else if ( @@ -311,6 +388,7 @@ export const useDiscordPresenceSettingsStore = defineStore( 'wrld_2d40da63-8f1f-4011-8a9e-414eb8530acd' ) { activityType = ActivityType.Listening; + statusDisplayType = StatusDisplayType.Details; appId = '939473404808007731'; bigIcon = 'zuwa_zuwa_dance'; } else if ( @@ -324,6 +402,7 @@ export const useDiscordPresenceSettingsStore = defineStore( 'wrld_f767d1c8-b249-4ecc-a56f-614e433682c8' ) { activityType = ActivityType.Watching; + statusDisplayType = StatusDisplayType.Details; appId = '968292722391785512'; bigIcon = 'ls_media'; } else if ( @@ -333,6 +412,7 @@ export const useDiscordPresenceSettingsStore = defineStore( 'wrld_27c7e6b2-d938-447e-a270-3d1a873e2cf3' ) { activityType = ActivityType.Watching; + statusDisplayType = StatusDisplayType.Details; appId = '1095440531821170820'; if ( !state.discordHideImage && @@ -374,6 +454,7 @@ export const useDiscordPresenceSettingsStore = defineStore( appId = '883308884863901717'; // default VRChat app id bigIcon = 'vrchat'; activityType = ActivityType.Playing; + statusDisplayType = StatusDisplayType.Name; } if (details.length < 2) { // 글자 수가 짧으면 업데이트가 안된다.. @@ -399,7 +480,8 @@ export const useDiscordPresenceSettingsStore = defineStore( buttonText, // button text buttonUrl, // button url appId, // app id - activityType // activity type + activityType, // activity type + statusDisplayType // status display type ); } @@ -423,12 +505,18 @@ export const useDiscordPresenceSettingsStore = defineStore( discordHideInvite, discordJoinButton, discordHideImage, + discordShowPlatform, + discordWorldIntegration, + discordWorldNameAsDiscordStatus, setDiscordActive, setDiscordInstance, setDiscordHideInvite, setDiscordJoinButton, setDiscordHideImage, + setDiscordShowPlatform, + setDiscordWorldIntegration, + setDiscordWorldNameAsDiscordStatus, updateDiscord, saveDiscordOption }; diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index e4119227..0b3245c7 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -139,7 +139,8 @@ declare global { buttonText: string, buttonUrl: string, appId: string, - activityType: number + activityType: number, + statusDisplayType: number ): Promise; SetActive(active: boolean): Promise; }; diff --git a/src/views/Settings/Settings.vue b/src/views/Settings/Settings.vue index 767b166d..1b84af90 100644 --- a/src/views/Settings/Settings.vue +++ b/src/views/Settings/Settings.vue @@ -1189,14 +1189,25 @@
{{ t('view.settings.discord_presence.discord_presence.description') }}
+
+ {{ t('view.settings.discord_presence.discord_presence.enable_tooltip') }} +
+
+ + + @@ -1881,9 +1910,16 @@ const { cachedGroups } = storeToRefs(useGroupStore()); const { cachedAvatars, cachedAvatarNames } = storeToRefs(useAvatarStore()); const { showConsole } = useVrcxStore(); - const { discordActive, discordInstance, discordHideInvite, discordJoinButton, discordHideImage } = storeToRefs( - useDiscordPresenceSettingsStore() - ); + const { + discordActive, + discordInstance, + discordHideInvite, + discordJoinButton, + discordHideImage, + discordShowPlatform, + discordWorldIntegration, + discordWorldNameAsDiscordStatus + } = storeToRefs(useDiscordPresenceSettingsStore()); const { disableGameLogDialog } = useGameLogStore(); const { setDiscordActive, @@ -1891,6 +1927,9 @@ setDiscordHideInvite, setDiscordJoinButton, setDiscordHideImage, + setDiscordShowPlatform, + setDiscordWorldIntegration, + setDiscordWorldNameAsDiscordStatus, saveDiscordOption } = useDiscordPresenceSettingsStore(); const {