capitalized first character in all wshub event key

This commit is contained in:
aynakeya
2025-07-24 18:47:24 +08:00
parent 3bd8945247
commit d11a5a5f76
5 changed files with 237 additions and 3 deletions

View File

@@ -120,7 +120,7 @@ func (s *wsServer) handleWsInfo(w http.ResponseWriter, r *http.Request) {
if data.EventID == "" { if data.EventID == "" {
continue continue
} }
eventCacheData, _ := json.Marshal(data) eventCacheData, _ := toCapitalizedJSON(data)
err := client.conn.WriteMessage(websocket.TextMessage, eventCacheData) err := client.conn.WriteMessage(websocket.TextMessage, eventCacheData)
if err != nil { if err != nil {
s.log.Warn("write message failed", err) s.log.Warn("write message failed", err)

70
plugin/wshub/utils.go Normal file
View File

@@ -0,0 +1,70 @@
package wshub
import (
"encoding/json"
"fmt"
"unicode"
)
// capitalize is a helper function to safely capitalize the first letter of a string.
// It's robust against empty strings.
func capitalize(s string) string {
if s == "" {
return ""
}
r := []rune(s)
r[0] = unicode.ToUpper(r[0])
return string(r)
}
// capitalizeKeys recursively traverses an interface{} and capitalizes the keys of any maps it finds.
func capitalizeKeys(data interface{}) interface{} {
// Use a type switch to handle the different types of data we might encounter.
switch value := data.(type) {
// If it's a map, we iterate over its keys and values.
case map[string]interface{}:
newMap := make(map[string]interface{})
for k, v := range value {
// Capitalize the key and recursively process the value.
newMap[capitalize(k)] = capitalizeKeys(v)
}
return newMap
// If it's a slice, we iterate over its elements.
case []interface{}:
// The slice itself doesn't have keys, but its elements might.
newSlice := make([]interface{}, len(value))
for i, v := range value {
// Recursively process each element in the slice.
newSlice[i] = capitalizeKeys(v)
}
return newSlice
// For any other type (string, int, bool, etc.), return it as is.
default:
return data
}
}
// toCapitalizedJSON marshals any data structure (including structs) to a JSON string
// with all keys having their first letter capitalized.
func toCapitalizedJSON(payload interface{}) ([]byte, error) {
// Step 1: Marshal the data to JSON. This respects the `json` tags on any structs.
tempJSON, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("failed to perform initial marshal: %w", err)
}
// Step 2: Unmarshal the JSON into a generic interface{}.
// This converts all JSON objects into map[string]interface{}, regardless of the original type.
var genericData interface{}
if err := json.Unmarshal(tempJSON, &genericData); err != nil {
return nil, fmt.Errorf("failed to unmarshal into generic interface: %w", err)
}
// Step 3: Recursively capitalize the keys of the generic data structure.
capitalizedData := capitalizeKeys(genericData)
// Step 4: Marshal the final, capitalized data structure back to JSON.
return json.MarshalIndent(capitalizedData, "", " ")
}

164
plugin/wshub/utils_test.go Normal file
View File

@@ -0,0 +1,164 @@
package wshub
import (
"encoding/json"
"reflect"
"testing"
)
// --- Example struct that might be used in the Data field ---
type UserDetails struct {
UserIdentifier int `json:"userIdentifier"`
EmailAddress string `json:"emailAddress"`
IsActive bool `json:"isActive"`
Metadata map[string]interface{} `json:"metadata"`
Tags []string `json:"tags"`
}
func TestToCapitalizedJSON(t *testing.T) {
// Define a struct for our table-driven tests
testCases := []struct {
name string // Name of the test case
input interface{} // Input to the function
expectedJSON string // The expected JSON output string
expectError bool // Whether we expect an error
}{
{
name: "Simple Struct",
input: UserDetails{
UserIdentifier: 101,
EmailAddress: "test@example.com",
IsActive: true,
},
expectedJSON: `{
"UserIdentifier": 101,
"EmailAddress": "test@example.com",
"IsActive": true,
"Metadata": null,
"Tags": null
}`,
},
{
name: "Struct with Nested Map",
input: UserDetails{
UserIdentifier: 102,
EmailAddress: "another@example.com",
IsActive: false,
Metadata: map[string]interface{}{
"lastLogin": "2024-01-01T12:00:00Z",
"loginCount": 5,
},
Tags: []string{"beta", "tester"},
},
expectedJSON: `{
"UserIdentifier": 102,
"EmailAddress": "another@example.com",
"IsActive": false,
"Metadata": {
"LastLogin": "2024-01-01T12:00:00Z",
"LoginCount": 5
},
"Tags": ["beta", "tester"]
}`,
},
{
name: "Simple Map",
input: map[string]interface{}{
"firstName": "John",
"lastName": "Doe",
},
expectedJSON: `{
"FirstName": "John",
"LastName": "Doe"
}`,
},
{
name: "Nested Map and Slice",
input: map[string]interface{}{
"event": "user.created",
"payload": map[string]interface{}{
"userName": "jdoe",
"roles": []interface{}{
"editor",
map[string]interface{}{"permissionLevel": 4},
},
},
},
expectedJSON: `{
"Event": "user.created",
"Payload": {
"UserName": "jdoe",
"Roles": [
"editor",
{
"PermissionLevel": 4
}
]
}
}`,
},
{
name: "Top-level Slice with Structs",
input: []UserDetails{
{UserIdentifier: 201, EmailAddress: "user1@test.com"},
{UserIdentifier: 202, EmailAddress: "user2@test.com"},
},
expectedJSON: `[
{
"UserIdentifier": 201, "EmailAddress": "user1@test.com", "IsActive": false, "Metadata": null, "Tags": null
},
{
"UserIdentifier": 202, "EmailAddress": "user2@test.com", "IsActive": false, "Metadata": null, "Tags": null
}
]`,
},
{
name: "Nil Input",
input: nil,
expectedJSON: `null`,
},
{
name: "Empty map",
input: map[string]interface{}{},
expectedJSON: `{}`,
},
}
// --- Test Runner ---
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Call the function we are testing
actualBytes, err := toCapitalizedJSON(tc.input)
// Check for an unexpected error
if !tc.expectError && err != nil {
t.Fatalf("ToCapitalizedJSON() returned an unexpected error: %v", err)
}
// Check for an expected error that did not occur
if tc.expectError && err == nil {
t.Fatalf("ToCapitalizedJSON() was expected to return an error, but it did not")
}
// To reliably compare JSON, we unmarshal both the actual and expected
// results into a generic interface{} and use reflect.DeepEqual.
// This avoids issues with whitespace and key ordering.
var actualResult interface{}
if err := json.Unmarshal(actualBytes, &actualResult); err != nil {
t.Fatalf("Failed to unmarshal actual result: %v", err)
}
var expectedResult interface{}
if err := json.Unmarshal([]byte(tc.expectedJSON), &expectedResult); err != nil {
t.Fatalf("Failed to unmarshal expected JSON: %v", err)
}
// Compare the results
if !reflect.DeepEqual(actualResult, expectedResult) {
// Use MarshalIndent to get a pretty-printed version for easier comparison
prettyActual, _ := json.MarshalIndent(actualResult, "", " ")
prettyExpected, _ := json.MarshalIndent(expectedResult, "", " ")
t.Errorf("Result does not match expected.\nGot:\n%s\n\nWant:\n%s", string(prettyActual), string(prettyExpected))
}
})
}
}

View File

@@ -10,7 +10,6 @@ import (
"AynaLivePlayer/pkg/event" "AynaLivePlayer/pkg/event"
"AynaLivePlayer/pkg/i18n" "AynaLivePlayer/pkg/i18n"
"AynaLivePlayer/pkg/logger" "AynaLivePlayer/pkg/logger"
"encoding/json"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/data/binding"
@@ -183,7 +182,7 @@ func (w *WsHub) registerEvents() {
EventID: e.Id, EventID: e.Id,
Data: e.Data, Data: e.Data,
} }
val, err := json.Marshal(ed) val, err := toCapitalizedJSON(ed)
if err != nil { if err != nil {
w.log.Errorf("failed to marshal event data %v", err) w.log.Errorf("failed to marshal event data %v", err)
return return

View File

@@ -16,6 +16,7 @@
---- ----
Finished Finished
- 2024.07.24 : 修复网易云修复wshub大小写问题
- 2024.07.07 : QQ音乐 - 2024.07.07 : QQ音乐
- 2024.06.30 : 添加vlc核心修复若干buggui框架更新修复点歌限制为1时可能出现的无法点歌的问题 - 2024.06.30 : 添加vlc核心修复若干buggui框架更新修复点歌限制为1时可能出现的无法点歌的问题
- 2024.05.27 : 修复web弹幕获取到0个host的时候闪退的问题 - 2024.05.27 : 修复web弹幕获取到0个host的时候闪退的问题