mirror of
				https://github.com/langhuihui/monibuca.git
				synced 2025-10-31 03:16:32 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			325 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package m7s
 | |
| 
 | |
| import (
 | |
| 	"net/http"
 | |
| 	"reflect"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"gopkg.in/yaml.v3"
 | |
| )
 | |
| 
 | |
| func getIndent(line string) int {
 | |
| 	return len(line) - len(strings.TrimLeft(line, " "))
 | |
| }
 | |
| 
 | |
| func addCommentsToYAML(yamlData []byte) []byte {
 | |
| 	lines := strings.Split(string(yamlData), "\n")
 | |
| 	var result strings.Builder
 | |
| 	var commentBuffer []string
 | |
| 	var keyLineBuffer string
 | |
| 	var keyLineIndent int
 | |
| 	inMultilineValue := false
 | |
| 
 | |
| 	for _, line := range lines {
 | |
| 		trimmedLine := strings.TrimSpace(line)
 | |
| 		indent := getIndent(line)
 | |
| 
 | |
| 		if strings.HasPrefix(trimmedLine, "_description:") {
 | |
| 			description := strings.TrimSpace(strings.TrimPrefix(trimmedLine, "_description:"))
 | |
| 			commentBuffer = append(commentBuffer, "# "+description)
 | |
| 		} else if strings.HasPrefix(trimmedLine, "_enum:") {
 | |
| 			enum := strings.TrimSpace(strings.TrimPrefix(trimmedLine, "_enum:"))
 | |
| 			commentBuffer = append(commentBuffer, "# 可选值: "+enum)
 | |
| 		} else if strings.HasPrefix(trimmedLine, "_value:") {
 | |
| 			valueStr := strings.TrimSpace(strings.TrimPrefix(trimmedLine, "_value:"))
 | |
| 			if valueStr != "" && valueStr != "{}" && valueStr != "[]" {
 | |
| 				// Single line value
 | |
| 				result.WriteString(strings.Repeat(" ", keyLineIndent))
 | |
| 				result.WriteString(keyLineBuffer)
 | |
| 				result.WriteString(": ")
 | |
| 				result.WriteString(valueStr)
 | |
| 				if len(commentBuffer) > 0 {
 | |
| 					result.WriteString(" ")
 | |
| 					for j, c := range commentBuffer {
 | |
| 						c = strings.TrimSpace(strings.TrimPrefix(c, "#"))
 | |
| 						result.WriteString("# " + c)
 | |
| 						if j < len(commentBuffer)-1 {
 | |
| 							result.WriteString(" ")
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				result.WriteString("\n")
 | |
| 			} else {
 | |
| 				// Multi-line value (struct/map)
 | |
| 				for _, comment := range commentBuffer {
 | |
| 					result.WriteString(strings.Repeat(" ", keyLineIndent))
 | |
| 					result.WriteString(comment)
 | |
| 					result.WriteString("\n")
 | |
| 				}
 | |
| 				result.WriteString(strings.Repeat(" ", keyLineIndent))
 | |
| 				result.WriteString(keyLineBuffer)
 | |
| 				result.WriteString(":")
 | |
| 				result.WriteString("\n")
 | |
| 				inMultilineValue = true
 | |
| 			}
 | |
| 			commentBuffer = nil
 | |
| 			keyLineBuffer = ""
 | |
| 			keyLineIndent = 0
 | |
| 		} else if strings.Contains(trimmedLine, ":") {
 | |
| 			// This is a key line
 | |
| 			if keyLineBuffer != "" { // flush previous key line
 | |
| 				result.WriteString(strings.Repeat(" ", keyLineIndent) + keyLineBuffer + ":\n")
 | |
| 			}
 | |
| 			inMultilineValue = false
 | |
| 			keyLineBuffer = strings.TrimSuffix(trimmedLine, ":")
 | |
| 			keyLineIndent = indent
 | |
| 		} else if inMultilineValue {
 | |
| 			// These are the lines of a multiline value
 | |
| 			if trimmedLine != "" {
 | |
| 				result.WriteString(line + "\n")
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if keyLineBuffer != "" {
 | |
| 		result.WriteString(strings.Repeat(" ", keyLineIndent) + keyLineBuffer + ":\n")
 | |
| 	}
 | |
| 
 | |
| 	// Final cleanup to remove empty lines and special keys
 | |
| 	finalOutput := []string{}
 | |
| 	for _, line := range strings.Split(result.String(), "\n") {
 | |
| 		trimmed := strings.TrimSpace(line)
 | |
| 		if trimmed == "" || strings.HasPrefix(trimmed, "_") {
 | |
| 			continue
 | |
| 		}
 | |
| 		finalOutput = append(finalOutput, line)
 | |
| 	}
 | |
| 
 | |
| 	return []byte(strings.Join(finalOutput, "\n"))
 | |
| }
 | |
| 
 | |
| func (s *Server) api_Config_YAML_All(rw http.ResponseWriter, r *http.Request) {
 | |
| 	query := r.URL.Query()
 | |
| 	filterName := query.Get("name")
 | |
| 	shouldMergeCommon := query.Get("common") != "false"
 | |
| 
 | |
| 	configSections := []struct {
 | |
| 		name string
 | |
| 		data any
 | |
| 	}{}
 | |
| 
 | |
| 	// 1. Get common config if it needs to be merged.
 | |
| 	var commonConfig map[string]any
 | |
| 	if shouldMergeCommon {
 | |
| 		if c, ok := extractStructConfig(reflect.ValueOf(s.Plugin.GetCommonConf())).(map[string]any); ok {
 | |
| 			commonConfig = c
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 2. Process global config.
 | |
| 	if filterName == "" || filterName == "global" {
 | |
| 		if globalConf, ok := extractStructConfig(reflect.ValueOf(s.ServerConfig)).(map[string]any); ok {
 | |
| 			if shouldMergeCommon && commonConfig != nil {
 | |
| 				mergedConf := make(map[string]any)
 | |
| 				for k, v := range commonConfig {
 | |
| 					mergedConf[k] = v
 | |
| 				}
 | |
| 				for k, v := range globalConf {
 | |
| 					mergedConf[k] = v // Global overrides common
 | |
| 				}
 | |
| 				configSections = append(configSections, struct {
 | |
| 					name string
 | |
| 					data any
 | |
| 				}{"global", mergedConf})
 | |
| 			} else {
 | |
| 				configSections = append(configSections, struct {
 | |
| 					name string
 | |
| 					data any
 | |
| 				}{"global", globalConf})
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 3. Process plugin configs.
 | |
| 	for _, meta := range plugins {
 | |
| 		if filterName != "" && !strings.EqualFold(meta.Name, filterName) {
 | |
| 			continue
 | |
| 		}
 | |
| 		name := strings.ToLower(meta.Name)
 | |
| 		configType := meta.Type
 | |
| 		if configType.Kind() == reflect.Ptr {
 | |
| 			configType = configType.Elem()
 | |
| 		}
 | |
| 
 | |
| 		if pluginConf, ok := extractStructConfig(reflect.New(configType)).(map[string]any); ok {
 | |
| 			pluginConf["enable"] = map[string]any{
 | |
| 				"_value":       true,
 | |
| 				"_description": "在global配置disableall时能启用特定插件",
 | |
| 			}
 | |
| 			if shouldMergeCommon && commonConfig != nil {
 | |
| 				mergedConf := make(map[string]any)
 | |
| 				for k, v := range commonConfig {
 | |
| 					mergedConf[k] = v
 | |
| 				}
 | |
| 				for k, v := range pluginConf {
 | |
| 					mergedConf[k] = v // Plugin overrides common
 | |
| 				}
 | |
| 				configSections = append(configSections, struct {
 | |
| 					name string
 | |
| 					data any
 | |
| 				}{name, mergedConf})
 | |
| 			} else {
 | |
| 				configSections = append(configSections, struct {
 | |
| 					name string
 | |
| 					data any
 | |
| 				}{name, pluginConf})
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 4. Serialize each section and combine.
 | |
| 	var yamlParts []string
 | |
| 	for _, section := range configSections {
 | |
| 		if section.data == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 		partMap := map[string]any{section.name: section.data}
 | |
| 		partYAML, err := yaml.Marshal(partMap)
 | |
| 		if err != nil {
 | |
| 			http.Error(rw, err.Error(), http.StatusInternalServerError)
 | |
| 			return
 | |
| 		}
 | |
| 		yamlParts = append(yamlParts, string(partYAML))
 | |
| 	}
 | |
| 
 | |
| 	finalYAML := strings.Join(yamlParts, "")
 | |
| 
 | |
| 	rw.Header().Set("Content-Type", "text/yaml; charset=utf-8")
 | |
| 	rw.Write(addCommentsToYAML([]byte(finalYAML)))
 | |
| }
 | |
| 
 | |
| func extractStructConfig(v reflect.Value) any {
 | |
| 	if v.Kind() == reflect.Ptr {
 | |
| 		if v.IsNil() {
 | |
| 			return nil
 | |
| 		}
 | |
| 		v = v.Elem()
 | |
| 	}
 | |
| 	if v.Kind() != reflect.Struct {
 | |
| 		return nil
 | |
| 	}
 | |
| 	m := make(map[string]any)
 | |
| 	for i := 0; i < v.NumField(); i++ {
 | |
| 		field := v.Type().Field(i)
 | |
| 		if !field.IsExported() {
 | |
| 			continue
 | |
| 		}
 | |
| 		// Filter out Plugin and UnimplementedApiServer
 | |
| 		fieldType := field.Type
 | |
| 		if fieldType.Kind() == reflect.Ptr {
 | |
| 			fieldType = fieldType.Elem()
 | |
| 		}
 | |
| 		if fieldType.Name() == "Plugin" || fieldType.Name() == "UnimplementedApiServer" {
 | |
| 			continue
 | |
| 		}
 | |
| 		yamlTag := field.Tag.Get("yaml")
 | |
| 		if yamlTag == "-" {
 | |
| 			continue
 | |
| 		}
 | |
| 		fieldName := strings.Split(yamlTag, ",")[0]
 | |
| 		if fieldName == "" {
 | |
| 			fieldName = strings.ToLower(field.Name)
 | |
| 		}
 | |
| 		m[fieldName] = extractFieldConfig(field, v.Field(i))
 | |
| 	}
 | |
| 	return m
 | |
| }
 | |
| 
 | |
| func extractFieldConfig(field reflect.StructField, value reflect.Value) any {
 | |
| 	result := make(map[string]any)
 | |
| 	description := field.Tag.Get("desc")
 | |
| 	enum := field.Tag.Get("enum")
 | |
| 	if description != "" {
 | |
| 		result["_description"] = description
 | |
| 	}
 | |
| 	if enum != "" {
 | |
| 		result["_enum"] = enum
 | |
| 	}
 | |
| 
 | |
| 	kind := value.Kind()
 | |
| 	if kind == reflect.Ptr {
 | |
| 		if value.IsNil() {
 | |
| 			value = reflect.New(value.Type().Elem())
 | |
| 		}
 | |
| 		value = value.Elem()
 | |
| 		kind = value.Kind()
 | |
| 	}
 | |
| 
 | |
| 	switch kind {
 | |
| 	case reflect.Struct:
 | |
| 		if dur, ok := value.Interface().(time.Duration); ok {
 | |
| 			result["_value"] = extractDurationConfig(field, dur)
 | |
| 		} else {
 | |
| 			result["_value"] = extractStructConfig(value)
 | |
| 		}
 | |
| 	case reflect.Map, reflect.Slice:
 | |
| 		if value.IsNil() {
 | |
| 			result["_value"] = make(map[string]any)
 | |
| 			if kind == reflect.Slice {
 | |
| 				result["_value"] = make([]any, 0)
 | |
| 			}
 | |
| 		} else {
 | |
| 			result["_value"] = value.Interface()
 | |
| 		}
 | |
| 	default:
 | |
| 		result["_value"] = extractBasicTypeConfig(field, value)
 | |
| 	}
 | |
| 
 | |
| 	if description == "" && enum == "" {
 | |
| 		return result["_value"]
 | |
| 	}
 | |
| 
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| func extractBasicTypeConfig(field reflect.StructField, value reflect.Value) any {
 | |
| 	if value.IsZero() {
 | |
| 		if defaultValue := field.Tag.Get("default"); defaultValue != "" {
 | |
| 			return parseDefaultValue(defaultValue, field.Type)
 | |
| 		}
 | |
| 	}
 | |
| 	return value.Interface()
 | |
| }
 | |
| 
 | |
| func extractDurationConfig(field reflect.StructField, value time.Duration) any {
 | |
| 	if value == 0 {
 | |
| 		if defaultValue := field.Tag.Get("default"); defaultValue != "" {
 | |
| 			return defaultValue
 | |
| 		}
 | |
| 	}
 | |
| 	return value.String()
 | |
| }
 | |
| 
 | |
| func parseDefaultValue(defaultValue string, t reflect.Type) any {
 | |
| 	switch t.Kind() {
 | |
| 	case reflect.String:
 | |
| 		return defaultValue
 | |
| 	case reflect.Bool:
 | |
| 		return defaultValue == "true"
 | |
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | |
| 		if v, err := strconv.ParseInt(defaultValue, 10, 64); err == nil {
 | |
| 			return v
 | |
| 		}
 | |
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 | |
| 		if v, err := strconv.ParseUint(defaultValue, 10, 64); err == nil {
 | |
| 			return v
 | |
| 		}
 | |
| 	case reflect.Float32, reflect.Float64:
 | |
| 		if v, err := strconv.ParseFloat(defaultValue, 64); err == nil {
 | |
| 			return v
 | |
| 		}
 | |
| 	}
 | |
| 	return defaultValue
 | |
| }
 | 
