Skip to content

Conversation

@Cazzar
Copy link
Owner

@Cazzar Cazzar commented May 19, 2025

Summary by CodeRabbit

  • New Features

    • Added support for toggling VTube Studio expressions directly from Stream Deck with a new "Expression Toggle" action.
    • Introduced a property inspector UI for configuring expression toggling and fade time settings.
    • Implemented an expression cache to keep the Stream Deck in sync with VTube Studio expressions.
    • Added handling and display of hotkey and expression events from VTube Studio.
    • Introduced new API requests and responses for expression activation and state management.
  • Enhancements

    • Updated plugin manifest to include the new expression toggle action.
    • Extended WebSocket client to support new expression and hotkey events.
    • Improved expression state management and event handling.
    • Refined JSON serialization behavior for API requests.
  • Other Changes

    • Upgraded target framework to .NET 9.0.
    • Minor logging and configuration adjustments.

…lugin

Implemented the ExpressionToggleAction to handle toggling VTube Studio expressions via Stream Deck, supported by a new ExpressionCache class. Added related API requests, events, and models to VTube Studio API integration, and updated the plugin to target .NET 9.0.
@coderabbitai
Copy link

coderabbitai bot commented May 19, 2025

Walkthrough

This update introduces new features for managing VTube Studio expressions via a Stream Deck plugin. It adds classes for handling expression state and activation requests/responses, implements an expression cache, and creates a new Stream Deck action for toggling expressions. The update also includes UI components for expression selection, logging adjustments, and extends event handling in the websocket client. Minor configuration and project file updates are included.

Changes

File(s) Change Summary
StreamdeckLib/Models/SetStateRequest.cs Changed SetStateRequest record from internal to public.
StreamdeckLib/Models/StatePayload.cs Renamed property in StatePayload from Payload to State.
StreamdeckLib/StreamDeckConnection.cs Commented out debug logging statements for JSON message handling.
VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs Added new class HotkeyTriggeredEvent to represent hotkey events.
VTubeStudioAPI/Models/Expression.cs Added new class Expression modeling VTube Studio expressions.
VTubeStudioAPI/Requests/ApiRequest.cs Decorated MessageType property with [JsonIgnore].
VTubeStudioAPI/Requests/ExpressionActivationRequest.cs Added new class ExpressionActivationRequest for activating/deactivating expressions.
VTubeStudioAPI/Requests/ExpressionStateRequest.cs Added new class ExpressionStateRequest for querying expression state.
VTubeStudioAPI/Responses/ExpressionStateResponse.cs Added new class ExpressionStateResponse for expression state data.
VTubeStudioAPI/Responses/ResponseType.cs Added new enum values for expression state/activation requests and responses.
VTubeStudioAPI/VTubeStudioWebsocketClient.cs Enabled hotkey event subscription/handling and added expression event handling. Added related static events. Adjusted logging and minor cleanup.
streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs Added new ExpressionToggleAction class for toggling expressions via Stream Deck.
streamdeck-vtubestudio/ExpressionCache.cs Added new ExpressionCache class and related event args for managing expression data.
streamdeck-vtubestudio/Program.cs Enabled debug argument loading and registered ExpressionCache as a singleton service. Enforced service provider validation.
streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/App.vue Added new Vue component for property inspector, allowing expression selection and fade time adjustment.
streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/main.js Added entry script for Vue property inspector, initializing Vuex store and Stream Deck connection.
streamdeck-vtubestudio/manifest.json Added new action entry for "Expression Toggle" to the Stream Deck plugin manifest.
streamdeck-vtubestudio/nlog.config Corrected XML comment delimiters in logging configuration. No logic changes.
streamdeck-vtubestudio/streamdeck-vtubestudio.csproj Changed target framework from .NET 8.0 to .NET 9.0.

Sequence Diagram(s)

sequenceDiagram
    participant StreamDeck as Stream Deck
    participant ExpressionToggleAction as ExpressionToggleAction
    participant ExpressionCache as ExpressionCache
    participant VTSClient as VTubeStudioWebsocketClient
    participant VTubeStudio as VTube Studio

    StreamDeck->>ExpressionToggleAction: Button Pressed
    ExpressionToggleAction->>VTSClient: Send ExpressionActivationRequest
    VTSClient->>VTubeStudio: WebSocket Request (activate/deactivate expression)
    VTubeStudio-->>VTSClient: ExpressionActivationResponse
    VTSClient-->>ExpressionCache: OnExpressionActivation event
    ExpressionCache-->>ExpressionToggleAction: Update expression state
    ExpressionToggleAction->>StreamDeck: Update key state
Loading
sequenceDiagram
    participant PropertyInspector as PropertyInspector UI
    participant StreamDeck as Stream Deck Plugin
    participant ExpressionCache as ExpressionCache

    PropertyInspector->>StreamDeck: Request expressions list
    StreamDeck->>ExpressionCache: Get available expressions
    ExpressionCache-->>StreamDeck: Return expressions list
    StreamDeck-->>PropertyInspector: Provide expressions for dropdown
    PropertyInspector->>StreamDeck: Save settings (expression, fade time)
Loading

Poem

In the garden of code where expressions bloom,
A toggle appears—click!—dispelling the gloom.
With caches and websockets, events hop along,
The Stream Deck now sings an expressive new song.
From Vue to .NET, the features align—
Hooray for new toggles! The carrots are fine! 🥕

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between f1a9e44 and f01a4f8.

📒 Files selected for processing (3)
  • streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs (1 hunks)
  • streamdeck-vtubestudio/ExpressionCache.cs (1 hunks)
  • streamdeck-vtubestudio/manifest.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • streamdeck-vtubestudio/manifest.json
  • streamdeck-vtubestudio/ExpressionCache.cs
  • streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (13)
streamdeck-vtubestudio/nlog.config (1)

11-17: Properly closed the commented-out file logger target
The added closing comment delimiter (-->) on line 16 correctly matches the opening <!-- on line 11, ensuring that the file logger <target> remains fully commented out.
Consider removing the trailing whitespace on line 17 to keep the XML tidy and prevent unnecessary diffs in the future.

StreamdeckLib/StreamDeckConnection.cs (1)

99-103: Remove or clean up commented debug logs
The debug‐level log statements were commented out to reduce noise, but leaving them as commented code clutters the codebase. Consider either removing these lines entirely or guarding them behind a conditional compilation symbol (e.g., #if DEBUG).

Also applies to: 193-193

VTubeStudioAPI/Responses/ExpressionStateResponse.cs (1)

1-19: New ExpressionStateResponse class looks good.

The class is well-structured with appropriate JSON property mappings and required markers for non-nullable properties. It correctly models the expression state response from VTube Studio.

However, I noticed the namespace is VTubeStudioAPI.Responses while importing from VTubeStudioAPI.Models. Consider standardizing namespace conventions across the project.

streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/main.js (2)

6-23: Global StreamDeck connection function implemented.

The function properly initializes the StreamDeck connection and sets up the Vue application with a Vuex store.

Consider adding error handling for the WebSocket connection and Vue initialization to improve robustness. Also, since mutations, actions, and modules are empty, you could simplify by omitting these empty objects.

window.connectElgatoStreamDeckSocket = function (inPort, inPluginUUID, inRegisterEvent, inInfo, inActionInfo) {
    let streamDeck = new StreamDeck(inPort, inPluginUUID, inRegisterEvent, inInfo, inActionInfo);
    console.log("Connected to stream deck")

    let store = createStore({
        state: {
            streamDeck,
        },
-        mutations: {
-        },
-        actions: {
-        },
-        modules: {
-        }
    })

-    createApp(App).use(store).mount('#app')
+    try {
+        createApp(App).use(store).mount('#app')
+    } catch (error) {
+        console.error("Failed to initialize Vue application:", error)
+    }
}

25-27: Remove unnecessary blank lines.

For code cleanliness, consider removing the extra blank lines at the end of the file.

}

-
-
-
VTubeStudioAPI/VTubeStudioWebsocketClient.cs (1)

91-91: Remove duplicate assignment of _tryingToConnect variable

The same variable is being set to false twice consecutively.

                Connect();
                _tryingToConnect = false;
-                _tryingToConnect = false;
streamdeck-vtubestudio/ExpressionCache.cs (1)

22-22: Unused WeakReference list

This static list of WeakReference is populated in the constructor and cleared in the finalizer, but it's never actually used elsewhere in the code.

Consider removing this if it's not serving a purpose, or add a comment explaining its intended future use.

streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/App.vue (3)

56-61: Consider adding error handling for event listeners

The event listeners don't include any error handling. If an error occurs while processing events, it could disrupt the component's functionality.

    this.$store.state.streamDeck.on('didReceiveSettings', settings => console.log(settings))
-    this.$store.state.streamDeck.on('connected', payload => this.settings = payload?.payload?.settings ?? this.settings)
-    this.$store.state.streamDeck.on('sendToPropertyInspector', e => {
-      this.expressions = e.expressions ?? [];
-      this.websocketConnected = e.connected ?? false
-    })
+    this.$store.state.streamDeck.on('connected', payload => {
+      try {
+        this.settings = payload?.payload?.settings ?? this.settings;
+      } catch (error) {
+        console.error('Error processing connected event:', error);
+      }
+    })
+    this.$store.state.streamDeck.on('sendToPropertyInspector', e => {
+      try {
+        this.expressions = e.expressions ?? [];
+        this.websocketConnected = e.connected ?? false;
+      } catch (error) {
+        console.error('Error processing sendToPropertyInspector event:', error);
+      }
+    })

66-68: Empty style section

The style section is empty and can be removed to improve code clarity.

-<style>
-
-</style>

10-10: Consider adding a label for the fade time slider

The fade time slider doesn't have a clear label indicating what it controls, which might confuse users.

-      <input type="range" min="0" max="5" step="0.1" v-model="settings.fadeTime">
+      <div class="sdpi-item-label">Fade Time: {{ settings.fadeTime }}s</div>
+      <input type="range" min="0" max="5" step="0.1" v-model="settings.fadeTime" title="Fade Time">
streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs (3)

18-25: The Expression property has a hardcoded default value.

While having defaults is good, the specific expression "Cat Tail.exp3.json" might not exist in all VTube Studio installations. Consider setting this to null by default and handling the null case, or documenting this as a required setup step.

[JsonProperty("expression")]
-public string? Expression { get; set; } = "Cat Tail.exp3.json";
+public string? Expression { get; set; } = null;

48-58: Extract duplicate expression state logic to a helper method.

The same logic to determine if an expression is active appears in both the Pressed and Tick methods. This should be extracted to a helper method to avoid duplication and improve maintainability.

+ private bool IsExpressionActive() => _expressionCache.Expressions.FirstOrDefault(e => e.File == Settings.Expression)?.Active ?? false;

public override void Tick()
{
    base.Tick();
-    var activate = !_expressionCache.Expressions.FirstOrDefault(e => e.File == Settings.Expression)?.Active ?? false;
+    var activate = !IsExpressionActive();

    Connection.SendMessage(new SetStateRequest
    {
        Payload = new ((uint) (activate ? 1 : 0)),
        Context = ContextId,
    });
}

protected override void Pressed()
{
    if (!_expressionCache.ModelLoaded)
        return;

-    var activate = !_expressionCache.Expressions.FirstOrDefault(e => e.File == Settings.Expression)?.Active ?? false;
+    var activate = !IsExpressionActive();
    
    // rest of the method...
}

65-71: Implement validation in SettingsUpdated.

The SettingsUpdated method is empty, but it could be used to validate the new settings and handle any required UI updates when settings change.

protected override void SettingsUpdated(ExpressionTogglePayload oldSettings, ExpressionTogglePayload newSettings)
{
+    // Validate the new expression exists
+    if (string.IsNullOrEmpty(newSettings.Expression) || 
+        !_expressionCache.Expressions.Any(e => e.File == newSettings.Expression))
+    {
+        Logger.LogWarning("Selected expression '{Expression}' does not exist", newSettings.Expression);
+    }
+    
+    // Update UI state if needed
+    Tick();
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 4d42a63 and f1a9e44.

⛔ Files ignored due to path filters (1)
  • streamdeck-vtubestudio/vts_logo_transparent_darker.png is excluded by !**/*.png
📒 Files selected for processing (19)
  • StreamdeckLib/Models/SetStateRequest.cs (1 hunks)
  • StreamdeckLib/Models/StatePayload.cs (1 hunks)
  • StreamdeckLib/StreamDeckConnection.cs (2 hunks)
  • VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs (1 hunks)
  • VTubeStudioAPI/Models/Expression.cs (1 hunks)
  • VTubeStudioAPI/Requests/ApiRequest.cs (1 hunks)
  • VTubeStudioAPI/Requests/ExpressionActivationRequest.cs (1 hunks)
  • VTubeStudioAPI/Requests/ExpressionStateRequest.cs (1 hunks)
  • VTubeStudioAPI/Responses/ExpressionStateResponse.cs (1 hunks)
  • VTubeStudioAPI/Responses/ResponseType.cs (2 hunks)
  • VTubeStudioAPI/VTubeStudioWebsocketClient.cs (7 hunks)
  • streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs (1 hunks)
  • streamdeck-vtubestudio/ExpressionCache.cs (1 hunks)
  • streamdeck-vtubestudio/Program.cs (3 hunks)
  • streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/App.vue (1 hunks)
  • streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/main.js (1 hunks)
  • streamdeck-vtubestudio/manifest.json (1 hunks)
  • streamdeck-vtubestudio/nlog.config (1 hunks)
  • streamdeck-vtubestudio/streamdeck-vtubestudio.csproj (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
VTubeStudioAPI/Responses/ResponseType.cs (3)
VTubeStudioAPI/Requests/ExpressionStateRequest.cs (1)
  • ExpressionStateRequest (5-10)
VTubeStudioAPI/Requests/ExpressionActivationRequest.cs (1)
  • ExpressionActivationRequest (6-16)
VTubeStudioAPI/Responses/ExpressionStateResponse.cs (1)
  • ExpressionStateResponse (6-19)
streamdeck-vtubestudio/Program.cs (1)
streamdeck-vtubestudio/ExpressionCache.cs (3)
  • ExpressionCache (15-94)
  • ExpressionCache (30-41)
  • ExpressionCache (83-91)
streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/main.js (1)
streamdeck-vtubestudio/PropertyInspector/src/utils/streamdeck.js (1)
  • StreamDeck (1-164)
VTubeStudioAPI/Responses/ExpressionStateResponse.cs (1)
VTubeStudioAPI/Models/Expression.cs (1)
  • Expression (5-24)
VTubeStudioAPI/VTubeStudioWebsocketClient.cs (5)
VTubeStudioAPI/Requests/HotkeyTriggerRequest.cs (1)
  • EventSubscriptionRequest (23-34)
VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs (1)
  • HotkeyTriggeredEvent (5-30)
VTubeStudioAPI/Responses/ExpressionStateResponse.cs (1)
  • ExpressionStateResponse (6-19)
streamdeck-vtubestudio/ExpressionCache.cs (1)
  • OnExpressionState (62-81)
VTubeStudioAPI/ApiEventArgs.cs (1)
  • ApiEventArgs (5-10)
🔇 Additional comments (20)
streamdeck-vtubestudio/nlog.config (1)

25-27: Properly closed the commented-out combined logger rule
The closing --> on line 26 now aligns with the opening comment on line 24, ensuring the <logger name="*"...> rule remains commented without affecting active debug logging.

streamdeck-vtubestudio/streamdeck-vtubestudio.csproj (1)

5-5: Target framework upgraded to .NET 9.0
Updating to net9.0 aligns with the new expression-toggle features. Please verify that all referenced packages (hosting, NLog, WebSocketSharp) and your CI/CD pipelines are compatible with .NET 9.0.

StreamdeckLib/Models/SetStateRequest.cs (1)

3-3: Expose SetStateRequest for external consumption
Changing the record from internal to public is necessary for external components (e.g., Stream Deck plugin) to send state updates. Confirm that no unintended consumers now rely on this type and that your public API surface remains intentional.

StreamdeckLib/Models/StatePayload.cs (1)

3-3: Rename payload property to State for clarity
Switching the positional property from Payload to State improves semantic clarity. Ensure that JSON serialization still matches the Stream Deck protocol’s expectations (i.e., { "state": <value> } under payload).

VTubeStudioAPI/Requests/ApiRequest.cs (1)

2-9: Ignore MessageType in JSON serialization
Adding [JsonIgnore] on the abstract MessageType property prevents it from appearing in outgoing payloads. Verify that derived request classes still include the correct requestType field as required by the VTube Studio API (via your custom converter or another mechanism).

streamdeck-vtubestudio/Program.cs (3)

23-24: Activated debug argument loading from file.

The code now reads debug arguments from a file in the roaming application data folder during DEBUG builds, which is helpful for development but ensure this path is accessible.


54-54: New ExpressionCache service registered.

The ExpressionCache singleton service has been added to the dependency injection container, aligning with the new expression toggling functionality.


64-67: Added service provider validation.

Enabling ValidateOnBuild = true will catch dependency injection issues at build time rather than runtime, improving reliability.

VTubeStudioAPI/Responses/ResponseType.cs (2)

26-27: Added new expression-related request types.

The new request types for expression state and activation align with the added functionality.


57-58: Added corresponding response types for expressions.

These response types properly complement the request types and follow the established naming convention.

streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/main.js (1)

1-5: Appropriate Vue.js imports for the new component.

The imports correctly set up Vue 3 with Vuex for state management.

VTubeStudioAPI/Requests/ExpressionStateRequest.cs (1)

5-10: LGTM! Clean and focused request implementation.

The ExpressionStateRequest class looks well-implemented with a properly defined nullable parameter for optional expression file specification and the correct message type override.

VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs (1)

5-30: LGTM! Well-structured event model.

The HotkeyTriggeredEvent class is properly designed with appropriate JSON property mappings and required annotations for required fields. The model captures all necessary attributes of a hotkey trigger event.

VTubeStudioAPI/Requests/ExpressionActivationRequest.cs (1)

6-16: Well-structured expression activation request class

The ExpressionActivationRequest class is properly implemented with:

  • Appropriate constructor parameters with sensible defaults
  • Properly annotated JSON properties for serialization
  • Correct nullable type for ExpressionFile
  • Proper override of MessageType from base class

This class aligns well with the VTube Studio API design for toggling expressions.

VTubeStudioAPI/VTubeStudioWebsocketClient.cs (3)

46-46: Good to see HotkeyTriggeredEvent subscription being enabled

Enabling this subscription will allow the application to respond to hotkey events from VTube Studio, which is necessary for the expression toggle functionality.


246-248: Event declaration matches the new message types

The static events for handling the new message types are properly declared, matching the implementation in the MessageReceived method.


198-206:

Details

❌ Incorrect review comment

Consider passing response data for ExpressionActivationResponse

For the ExpressionActivationResponse case, you're invoking the event with empty EventArgs, while for other responses you're passing the deserialized response data. If the response contains useful information, consider passing it to subscribers.

            case ResponseType.ExpressionActivationResponse: 
-                OnExpressionActivation?.Invoke(this, EventArgs.Empty);
+                OnExpressionActivation?.Invoke(this, new (response.Data!.ToObject<ExpressionActivationResponse>()!));
                break;

🏁 Script executed:

#!/bin/bash
# Search for the ExpressionActivationResponse class to see if it contains any useful properties
fd --exec grep -A 10 "class ExpressionActivationResponse" {} \;

Length of output: 2328


I wasn’t able to locate the ExpressionActivationResponse definition with the previous command. Let’s search the entire repo and inspect the class to see if it carries any data.


🏁 Script executed:

#!/bin/bash
# Locate and inspect the ExpressionActivationResponse class definition
rg -n "class ExpressionActivationResponse" -A10

Length of output: 49


No payload for ExpressionActivationResponse
I couldn’t find any ExpressionActivationResponse class or payload in the repo—there’s no data to pass to subscribers. Keeping EventArgs.Empty here is appropriate.

Likely an incorrect or invalid review comment.

streamdeck-vtubestudio/ExpressionCache.cs (1)

62-81: Well-implemented thread-safe cache update with error handling

The OnExpressionState method correctly:

  • Uses async/await pattern with semaphore for thread safety
  • Has proper error handling with logging
  • Updates all relevant cache properties
  • Ensures semaphore is released in a finally block

This implementation will provide reliable expression caching even in concurrent scenarios.

streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/App.vue (1)

43-46: Well-implemented settings persistence

The deep watch on settings with automatic saving ensures that user configuration changes are immediately persisted to StreamDeck storage.

streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs (1)

12-15: LGTM - Good use of attributes and inheritance pattern.

The StreamDeck action is properly registered with a unique identifier and inherits from the appropriate base class with a strongly-typed payload.

Comment on lines +3 to +4
namespace VTubeStudioAPI.Models;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Inconsistent namespace naming.

The namespace VTubeStudioAPI.Models differs from the convention used in other files (Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi...). This inconsistency could cause confusion and potential import issues.

Change the namespace to match the project convention:

-namespace VTubeStudioAPI.Models;
+namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Models;
🤖 Prompt for AI Agents
In VTubeStudioAPI/Models/Expression.cs at lines 3 to 4, the namespace is
currently set to VTubeStudioAPI.Models, which is inconsistent with the project's
naming convention. Update the namespace to follow the pattern used in other
files, such as Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Models, to maintain
consistency and avoid import issues.

VTubeStudioWebsocketClient.OnModelLoad -= Refresh;
VTubeStudioWebsocketClient.OnHotkeyTriggeredEvent -= RefreshHotkey;
VTubeStudioWebsocketClient.OnCurrentModelInformation -= Refresh;
VTubeStudioWebsocketClient.OnAuthenticationResponse -= HandleAuthenticated;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing unsubscription for OnExpressionActivation

The class subscribes to the OnExpressionActivation event in the constructor, but doesn't unsubscribe in the finalizer, which could lead to memory leaks.

    ~ExpressionCache()
    {
        instances.Remove(new(this));
        VTubeStudioWebsocketClient.OnExpressionState -= OnExpressionState;
        VTubeStudioWebsocketClient.OnModelLoad -= Refresh;
        VTubeStudioWebsocketClient.OnHotkeyTriggeredEvent -= RefreshHotkey;
        VTubeStudioWebsocketClient.OnCurrentModelInformation -= Refresh;
        VTubeStudioWebsocketClient.OnAuthenticationResponse -= HandleAuthenticated;
+        VTubeStudioWebsocketClient.OnExpressionActivation -= Refresh;
    }
🤖 Prompt for AI Agents
In streamdeck-vtubestudio/ExpressionCache.cs at line 90, the code unsubscribes
from OnAuthenticationResponse but misses unsubscribing from
OnExpressionActivation, which was subscribed in the constructor. Add a line to
unsubscribe from VTubeStudioWebsocketClient.OnExpressionActivation by removing
the HandleExpressionActivated handler in the finalizer or dispose method to
prevent memory leaks.

<NotConnected v-if="!websocketConnected" />
<div v-else>
<div v-if="websocketConnected" class="sdpi-item">
<div class="sdpi-item-label">Size ({{ settings.size ?? '0' }})</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Unexpected reference to settings.size

The template references settings.size but this property is not defined in the data object's settings. This could cause undefined behavior or display issues.

Either:

  1. Remove the reference if it's not needed:
-    <div class="sdpi-item-label">Size ({{ settings.size ?? '0' }})</div>
+    <div class="sdpi-item-label">Expression</div>
  1. Or add the size property to the settings object:
      settings: {
        expression: '',
        fadeTime: 0.5,
+        size: 0,
      }
🤖 Prompt for AI Agents
In streamdeck-vtubestudio/PropertyInspector/src/pages/ExpressionToggle/App.vue
at line 5, the template references settings.size which is not defined in the
data object's settings, potentially causing undefined behavior. To fix this,
either remove the reference to settings.size from the template if it is
unnecessary, or add a size property with a default value to the settings object
in the component's data to ensure it is defined and avoid display issues.

Comment on lines 1 to 72
#nullable enable
using System.Linq;
using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi;
using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Requests;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using StreamDeckLib;
using StreamDeckLib.Models;

namespace Cazzar.StreamDeck.VTubeStudio.Actions;


[StreamDeckAction("dev.cazzar.vtubestudio.expressiontoggle")]
public class ExpressionToggleAction : BaseAction<ExpressionToggleAction.ExpressionTogglePayload>
{
private readonly ExpressionCache _expressionCache;

public class ExpressionTogglePayload
{
[JsonProperty("expression")]
public string? Expression { get; set; } = "Cat Tail.exp3.json";

[JsonProperty("fadeTime")]
public float FadeTime { get; set; } = 0.5f;
}

public ExpressionToggleAction(GlobalSettingsManager gsm, VTubeStudioWebsocketClient vts, IStreamDeckConnection isd, ILogger<ExpressionToggleAction> logger, ExpressionCache expressionCache) : base(gsm, vts, isd, logger)
{
_expressionCache = expressionCache;
}

protected override void Pressed()
{
// if (!_expressionCache.ModelLoaded)
// return;

var activate = !_expressionCache.Expressions.FirstOrDefault(e => e.File == Settings.Expression)?.Active ?? false;

Vts.Send(new ExpressionActivationRequest(Settings.Expression, activate));

Connection.SendMessage(new SetStateRequest
{
Payload = new ((uint) (activate ? 1 : 0)),
Context = ContextId,
});
}

public override void Tick()
{
base.Tick();
var activate = !_expressionCache.Expressions.FirstOrDefault(e => e.File == Settings.Expression)?.Active ?? false;

Connection.SendMessage(new SetStateRequest
{
Payload = new ((uint) (activate ? 1 : 0)),
Context = ContextId,
});
}

protected override object GetClientData() => new
{
sxpressions = _expressionCache.Expressions.DistinctBy(e => e.File).Select(e => new { e.Name, e.File }),
};

protected override void Released()
{
}

protected override void SettingsUpdated(ExpressionTogglePayload oldSettings, ExpressionTogglePayload newSettings)
{
}
} No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add null-checking and error handling for robust operation.

The code should include additional null checks and error handling to make it more robust, especially since the Stream Deck plugin operates in an environment where network issues or timing problems could occur.

Consider adding:

  1. Null checks for Settings.Expression before using it
  2. Try-catch blocks around network operations
  3. Logging for error conditions

For example:

protected override void Pressed()
{
    if (!_expressionCache.ModelLoaded)
    {
        Logger.LogWarning("Cannot toggle expression: No model is loaded");
        return;
    }

    if (string.IsNullOrEmpty(Settings.Expression))
    {
        Logger.LogWarning("Cannot toggle expression: No expression selected");
        return;
    }

    try
    {
        var activate = !IsExpressionActive();
        
        Logger.LogInformation("Toggling expression {Expression} to {State}", 
            Settings.Expression, activate ? "active" : "inactive");
            
        Vts.Send(new ExpressionActivationRequest(Settings.Expression, activate, Settings.FadeTime));
        
        Connection.SendMessage(new SetStateRequest
        {
            Payload = new ((uint) (activate ? 1 : 0)),
            Context = ContextId,
        });
    }
    catch (Exception ex)
    {
        Logger.LogError(ex, "Failed to toggle expression {Expression}", Settings.Expression);
    }
}
🤖 Prompt for AI Agents
In streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs lines 1 to 72, add
null checks and error handling in the Pressed method to improve robustness.
Specifically, check if Settings.Expression is null or empty before proceeding,
and verify that the model is loaded via _expressionCache.ModelLoaded. Wrap the
network calls to Vts.Send and Connection.SendMessage in a try-catch block to
catch exceptions, and log warnings or errors appropriately using
Logger.LogWarning and Logger.LogError. Also, add informative logging for
toggling actions with Logger.LogInformation to track state changes.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant