Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion StreamdeckLib/Models/SetStateRequest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace StreamDeckLib.Models
{
record SetStateRequest : ContextMessage
public record SetStateRequest : ContextMessage
{
public override string Event => "setState";
public StatePayload Payload { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion StreamdeckLib/Models/StatePayload.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace StreamDeckLib.Models
{
public record StatePayload(uint Payload);
public record StatePayload(uint State);

record TitlePayload(string? Title, int Target, int State);
}
6 changes: 3 additions & 3 deletions StreamdeckLib/StreamDeckConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ private async Task<WebSocketCloseStatus> ReceiveAsync()

textBuffer.Append(Encoding.UTF8.GetString(buffer, 0, result.Count));
if (!result.EndOfMessage) continue;
_logger.LogDebug("Got JSON data: {ToString}", textBuffer.ToString());
//_logger.LogDebug("Got JSON data: {ToString}", textBuffer.ToString());

var message = JsonConvert.DeserializeObject<EventMessage>(textBuffer.ToString(), new StreamDeckMessageConverter());
_logger.LogDebug("Got message of {Event} JSON data: {ToString}", message?.Event, textBuffer.ToString());
//_logger.LogDebug("Got message of {Event} JSON data: {ToString}", message?.Event, textBuffer.ToString());

switch (message)
{
Expand Down Expand Up @@ -190,7 +190,7 @@ public async Task SendMessage(EventMessage message)
{
await _send.WaitAsync();
var json = JsonConvert.SerializeObject(message, new JsonSerializerSettings() { ContractResolver = new DefaultContractResolver() { NamingStrategy = new CamelCaseNamingStrategy() }, });
_logger.LogDebug("Sending message of {Type} with JSON data {Json}", message.Event, json);
//_logger.LogDebug("Sending message of {Type} with JSON data {Json}", message.Event, json);

var buf = Encoding.UTF8.GetBytes(json);
await _socket.SendAsync(new ArraySegment<byte>(buf), WebSocketMessageType.Text, true, _cancelSource.Token);
Expand Down
30 changes: 30 additions & 0 deletions VTubeStudioAPI/Events/HotkeyTriggeredEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Newtonsoft.Json;

namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Events;

public class HotkeyTriggeredEvent
{
[JsonProperty("hotkeyID")]
public required string Id { get; set; }

[JsonProperty("hotkeyName")]
public required string Name { get; set; }

[JsonProperty("hotkeyAction")]
public required string Action { get; set; }

[JsonProperty("hotkeyFile")]
public required string File { get; set; }

[JsonProperty("hotkeyTriggeredByAPI")]
public bool ApiTriggered { get; set; }

[JsonProperty("modelID")]
public required string ModelId { get; set; }

[JsonProperty("modelName")]
public required string ModelName { get; set; }

[JsonProperty("isLive2DItem")]
public bool IsLive2DItem { get; set; }
}
24 changes: 24 additions & 0 deletions VTubeStudioAPI/Models/Expression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Newtonsoft.Json;

namespace VTubeStudioAPI.Models;

Comment on lines +3 to +4
Copy link
Copy Markdown
Contributor

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.

public class Expression
{
[JsonProperty("name")]
public required string Name { get; set; }

[JsonProperty("file")]
public required string File { get; set; }

[JsonProperty("active")]
public bool Active { get; set; }

[JsonProperty("deactivateWhenKeyIsLetGo")]
public bool DeactivateWhenKeyIsLetGo { get; set; }

[JsonProperty("autoDeactivateAfterSeconds")]
public bool AutoDeactivateAfterSeconds { get; set; }
Comment thread
Cazzar marked this conversation as resolved.

[JsonProperty("secondsRemaining")]
public int SecondsRemaining { get; set; }
}
2 changes: 2 additions & 0 deletions VTubeStudioAPI/Requests/ApiRequest.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Responses;
using Newtonsoft.Json;

namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Requests;

public abstract class ApiRequest
{
[JsonIgnore]
public abstract RequestType MessageType { get; }
}
16 changes: 16 additions & 0 deletions VTubeStudioAPI/Requests/ExpressionActivationRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Responses;
using Newtonsoft.Json;

namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Requests;

public class ExpressionActivationRequest(string? expressionFile = null, bool activate = true, float fadeTime = 0.5f) : ApiRequest
{
[JsonProperty("expressionFile")]
public string? ExpressionFile { get; set; } = expressionFile;
[JsonProperty("active")]
public bool? Activate { get; set; } = activate;
[JsonProperty("fadeTime")]
public float FadeTime { get; set; } = fadeTime;

public override RequestType MessageType { get; } = RequestType.ExpressionActivationRequest;
}
10 changes: 10 additions & 0 deletions VTubeStudioAPI/Requests/ExpressionStateRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Responses;

namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Requests;

public class ExpressionStateRequest(string? expressionFile = null) : ApiRequest
{
public string? ExpressionFile { get; set; } = expressionFile;

public override RequestType MessageType { get; } = RequestType.ExpressionStateRequest;
}
19 changes: 19 additions & 0 deletions VTubeStudioAPI/Responses/ExpressionStateResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Newtonsoft.Json;
using VTubeStudioAPI.Models;

namespace VTubeStudioAPI.Responses;

public class ExpressionStateResponse
{
[JsonProperty("modelLoaded")]
public bool ModelLoaded { get; set; }

[JsonProperty("modelName")]
public required string ModelName { get; set; }

[JsonProperty("modelID")]
public required string ModelId { get; set; }

[JsonProperty("expressions")]
public required List<Expression> Expressions { get; set; }
}
4 changes: 4 additions & 0 deletions VTubeStudioAPI/Responses/ResponseType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public enum RequestType
ParameterDeletionRequest,
InjectParameterDataRequest,
EventSubscriptionRequest,
ExpressionStateRequest,
ExpressionActivationRequest,
}

public enum ResponseType
Expand Down Expand Up @@ -52,6 +54,8 @@ public enum ResponseType
ModelMovedEvent,
ModelConfigChangedEvent,
HotkeyTriggeredEvent,
ExpressionStateResponse,
ExpressionActivationResponse,
}

/// <summary>
Expand Down
22 changes: 17 additions & 5 deletions VTubeStudioAPI/VTubeStudioWebsocketClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi.Responses;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using VTubeStudioAPI.Responses;
using WebSocketSharp;

namespace Cazzar.StreamDeck.VTubeStudio.VTubeStudioApi;
Expand Down Expand Up @@ -42,7 +43,7 @@ private void HandleAuthResponse(object? sender, ApiEventArgs<AuthenticationRespo

Send(new EventSubscriptionRequest<object>("ModelMovedEvent"));
Send(new EventSubscriptionRequest<object>("ModelConfigChangedEvent"));
// Send(new EventSubscriptionRequest<object>("HotkeyTriggeredEvent"));
Send(new EventSubscriptionRequest<object>("HotkeyTriggeredEvent"));
}

public void Send(ApiRequest request, string? requestId = null)
Expand Down Expand Up @@ -77,7 +78,7 @@ public void SetConnection(string host, ushort port)

public void ConnectIfNeeded()
{
_logger.LogDebug("Ws is null? {WsNull}, ws alive? {WsAlive}, trying to connect? {TryingToConnect}", _ws == null, _ws?.IsAlive, _tryingToConnect);
//_logger.LogDebug("Ws is null? {WsNull}, ws alive? {WsAlive}, trying to connect? {TryingToConnect}", _ws == null, _ws?.IsAlive, _tryingToConnect);
if (_ws is not null && _ws.IsAlive) return;
if (_tryingToConnect) return;

Expand All @@ -87,6 +88,7 @@ public void ConnectIfNeeded()
{
Connect();
_tryingToConnect = false;
_tryingToConnect = false;
}
);
}
Expand Down Expand Up @@ -131,6 +133,7 @@ private void MessageReceived(object? sender, MessageEventArgs e)

var response = JsonConvert.DeserializeObject<ApiResponse>(e.Data);
if (response is null) return;
_logger.LogDebug("Got message: {JsonString}", e.Data);
_logger.LogDebug("Got message of type: {Type}", response.MessageType);

switch (response.MessageType)
Expand Down Expand Up @@ -192,9 +195,15 @@ private void MessageReceived(object? sender, MessageEventArgs e)
case ResponseType.ModelConfigChangedEvent:
OnModelConfigChangedEvent?.Invoke(this, new (response.Data!.ToObject<ModelConfigChangedEvent>()!));
break;
// case ResponseType.HotkeyTriggeredEvent:
// OnHotkeyTriggeredEvent?.Invoke(this, new (response.Data!.ToObject<HotkeyTriggeredEvent>()!));
// break;
case ResponseType.HotkeyTriggeredEvent:
OnHotkeyTriggeredEvent?.Invoke(this, new (response.Data!.ToObject<HotkeyTriggeredEvent>()!));
break;
case ResponseType.ExpressionStateResponse:
OnExpressionState?.Invoke(this, new (response.Data!.ToObject<ExpressionStateResponse>()!));
break;
case ResponseType.ExpressionActivationResponse:
OnExpressionActivation?.Invoke(this, EventArgs.Empty);
break;
default: throw new ArgumentOutOfRangeException();
}
}
Expand Down Expand Up @@ -234,5 +243,8 @@ public void Reconnect()
public static event EventHandler<ApiEventArgs<CurrentModelResponse>>? OnCurrentModelInformation;
public static event EventHandler<ApiEventArgs<ModelMoveEvent>>? OnModelMove;
public static event EventHandler<ApiEventArgs<ModelConfigChangedEvent>>? OnModelConfigChangedEvent;
public static event EventHandler<ApiEventArgs<HotkeyTriggeredEvent>>? OnHotkeyTriggeredEvent;
public static event EventHandler<ApiEventArgs<ExpressionStateResponse>>? OnExpressionState;
public static event EventHandler<EventArgs>? OnExpressionActivation;
#endregion
}
72 changes: 72 additions & 0 deletions streamdeck-vtubestudio/Actions/ExpressionToggleAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,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, Settings.FadeTime));

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
{
expressions = _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)
{
}
}
Loading