package wecom import ( "errors" "strconv" "strings" "github.com/memohai/memoh/internal/channel" ) type adapterConfig struct { BotID string Credential string WSURL string HeartbeatSeconds int AckTimeoutSeconds int WriteTimeoutSeconds int ReadTimeoutSeconds int } type UserConfig struct { ChatID string UserID string } func normalizeConfig(raw map[string]any) (map[string]any, error) { cfg, err := parseConfig(raw) if err != nil { return nil, err } out := map[string]any{ "botId": cfg.BotID, "secret": cfg.Credential, } if cfg.WSURL != "" { out["wsUrl"] = cfg.WSURL } if cfg.HeartbeatSeconds > 0 { out["heartbeatSeconds"] = cfg.HeartbeatSeconds } if cfg.AckTimeoutSeconds > 0 { out["ackTimeoutSeconds"] = cfg.AckTimeoutSeconds } if cfg.WriteTimeoutSeconds > 0 { out["writeTimeoutSeconds"] = cfg.WriteTimeoutSeconds } if cfg.ReadTimeoutSeconds > 0 { out["readTimeoutSeconds"] = cfg.ReadTimeoutSeconds } return out, nil } func normalizeUserConfig(raw map[string]any) (map[string]any, error) { cfg, err := parseUserConfig(raw) if err != nil { return nil, err } out := map[string]any{} if cfg.ChatID != "" { out["chat_id"] = cfg.ChatID } if cfg.UserID != "" { out["user_id"] = cfg.UserID } return out, nil } func parseConfig(raw map[string]any) (adapterConfig, error) { cfg := adapterConfig{ BotID: strings.TrimSpace(channel.ReadString(raw, "botId", "bot_id")), Credential: strings.TrimSpace(channel.ReadString(raw, "secret")), WSURL: strings.TrimSpace(channel.ReadString(raw, "wsUrl", "ws_url")), } if value, ok := readInt(raw, "heartbeatSeconds", "heartbeat_seconds"); ok { cfg.HeartbeatSeconds = value } if value, ok := readInt(raw, "ackTimeoutSeconds", "ack_timeout_seconds"); ok { cfg.AckTimeoutSeconds = value } if value, ok := readInt(raw, "writeTimeoutSeconds", "write_timeout_seconds"); ok { cfg.WriteTimeoutSeconds = value } if value, ok := readInt(raw, "readTimeoutSeconds", "read_timeout_seconds"); ok { cfg.ReadTimeoutSeconds = value } if cfg.BotID == "" || cfg.Credential == "" { return adapterConfig{}, errors.New("wecom botId and secret are required") } return cfg, nil } func parseUserConfig(raw map[string]any) (UserConfig, error) { cfg := UserConfig{ ChatID: strings.TrimSpace(channel.ReadString(raw, "chatId", "chat_id")), UserID: strings.TrimSpace(channel.ReadString(raw, "userId", "user_id")), } if cfg.ChatID == "" && cfg.UserID == "" { return UserConfig{}, errors.New("wecom user config requires chat_id or user_id") } return cfg, nil } func resolveTarget(raw map[string]any) (string, error) { cfg, err := parseUserConfig(raw) if err != nil { return "", err } if cfg.ChatID != "" { return "chat_id:" + cfg.ChatID, nil } return "user_id:" + cfg.UserID, nil } func normalizeTarget(raw string) string { kind, id, ok := parseTarget(raw) if !ok { return "" } return kind + ":" + id } func parseTarget(raw string) (kind string, id string, ok bool) { v := strings.TrimSpace(raw) if v == "" { return "", "", false } v = strings.TrimPrefix(v, "wecom:") v = strings.TrimPrefix(v, "workwx:") v = strings.TrimSpace(v) lv := strings.ToLower(v) switch { case strings.HasPrefix(lv, "chat_id:"): id = strings.TrimSpace(v[len("chat_id:"):]) return "chat_id", id, id != "" case strings.HasPrefix(lv, "chat:"): id = strings.TrimSpace(v[len("chat:"):]) return "chat_id", id, id != "" case strings.HasPrefix(lv, "group:"): id = strings.TrimSpace(v[len("group:"):]) return "chat_id", id, id != "" case strings.HasPrefix(lv, "user_id:"): id = strings.TrimSpace(v[len("user_id:"):]) return "user_id", id, id != "" case strings.HasPrefix(lv, "user:"): id = strings.TrimSpace(v[len("user:"):]) return "user_id", id, id != "" default: return "chat_id", v, true } } func matchBinding(raw map[string]any, criteria channel.BindingCriteria) bool { cfg, err := parseUserConfig(raw) if err != nil { return false } if value := strings.TrimSpace(criteria.Attribute("chat_id")); value != "" && value == cfg.ChatID { return true } if value := strings.TrimSpace(criteria.Attribute("user_id")); value != "" && value == cfg.UserID { return true } if criteria.SubjectID != "" && (criteria.SubjectID == cfg.ChatID || criteria.SubjectID == cfg.UserID) { return true } return false } func buildUserConfig(identity channel.Identity) map[string]any { out := map[string]any{} if v := strings.TrimSpace(identity.Attribute("chat_id")); v != "" { out["chat_id"] = v } if v := strings.TrimSpace(identity.Attribute("user_id")); v != "" { out["user_id"] = v } return out } func readInt(raw map[string]any, keys ...string) (int, bool) { for _, key := range keys { value, ok := raw[key] if !ok { continue } switch v := value.(type) { case int: return v, true case int32: return int(v), true case int64: return int(v), true case float64: return int(v), true case float32: return int(v), true case string: trimmed := strings.TrimSpace(v) if trimmed == "" { continue } parsed, err := strconv.Atoi(trimmed) if err != nil { continue } return parsed, true } } return 0, false }