The Go module name has been changed from `carrot_bbs` to `with_you`, which requires all import paths to be updated throughout the codebase and by external consumers. BREAKING CHANGE: Module name changed from carrot_bbs to with_you. All imports and dependencies must be updated from carrot_bbs/* to with_you/*.
318 lines
7.9 KiB
Go
318 lines
7.9 KiB
Go
package hook
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"with_you/internal/repository"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type UserAvatarModerationHook struct {
|
|
name string
|
|
postAIService PostAIService
|
|
userRepo repository.UserRepository
|
|
strictMode bool
|
|
}
|
|
|
|
func NewUserAvatarModerationHook(postAIService PostAIService, userRepo repository.UserRepository, strictMode bool) *UserAvatarModerationHook {
|
|
return &UserAvatarModerationHook{
|
|
name: "moderation.user.avatar.ai",
|
|
postAIService: postAIService,
|
|
userRepo: userRepo,
|
|
strictMode: strictMode,
|
|
}
|
|
}
|
|
|
|
func (h *UserAvatarModerationHook) Register(manager *Manager) {
|
|
manager.Register(&Hook{
|
|
Name: h.name,
|
|
HookType: HookUserAvatarPreModerate,
|
|
Priority: PriorityHigh,
|
|
Func: h.Execute,
|
|
Async: false,
|
|
Timeout: 30 * time.Second,
|
|
})
|
|
}
|
|
|
|
func (h *UserAvatarModerationHook) Execute(ctx *HookContext) error {
|
|
data, ok := ctx.Data.(*UserAvatarModerateHookData)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
result, ok := ctx.Metadata["result"].(*ModerationResult)
|
|
if !ok || result == nil {
|
|
return nil
|
|
}
|
|
|
|
if result.RejectReason != "" || result.NeedsReview {
|
|
return nil
|
|
}
|
|
|
|
if h.postAIService == nil || !h.postAIService.IsEnabled() {
|
|
result.Approved = true
|
|
result.ReviewedBy = "system"
|
|
return nil
|
|
}
|
|
|
|
err := h.postAIService.ModerateImage(ctx.Ctx, data.AvatarURL)
|
|
if err != nil {
|
|
if userErr, ok := err.(interface{ UserMessage() string }); ok {
|
|
if revErr, ok := err.(reviewableError); ok && revErr.IsReview() {
|
|
result.Approved = false
|
|
result.NeedsReview = true
|
|
result.RejectReason = userErr.UserMessage()
|
|
result.ReviewedBy = "ai"
|
|
zap.L().Info("User avatar flagged for manual review by AI",
|
|
zap.String("user_id", data.UserID),
|
|
zap.String("reason", result.RejectReason),
|
|
)
|
|
return nil
|
|
}
|
|
result.Approved = false
|
|
result.RejectReason = userErr.UserMessage()
|
|
result.ReviewedBy = "ai"
|
|
zap.L().Info("User avatar rejected by AI moderation",
|
|
zap.String("user_id", data.UserID),
|
|
zap.String("reason", result.RejectReason),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
if h.strictMode {
|
|
result.Approved = false
|
|
result.Error = err
|
|
result.ReviewedBy = "ai"
|
|
zap.L().Error("AI avatar moderation failed in strict mode, reject",
|
|
zap.String("user_id", data.UserID),
|
|
zap.Error(err),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
result.Approved = true
|
|
result.ReviewedBy = "system"
|
|
zap.L().Warn("AI moderation error, fallback approve avatar",
|
|
zap.String("user_id", data.UserID),
|
|
zap.Error(err),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
result.Approved = true
|
|
result.ReviewedBy = "ai"
|
|
return nil
|
|
}
|
|
|
|
type UserCoverModerationHook struct {
|
|
name string
|
|
postAIService PostAIService
|
|
userRepo repository.UserRepository
|
|
strictMode bool
|
|
}
|
|
|
|
func NewUserCoverModerationHook(postAIService PostAIService, userRepo repository.UserRepository, strictMode bool) *UserCoverModerationHook {
|
|
return &UserCoverModerationHook{
|
|
name: "moderation.user.cover.ai",
|
|
postAIService: postAIService,
|
|
userRepo: userRepo,
|
|
strictMode: strictMode,
|
|
}
|
|
}
|
|
|
|
func (h *UserCoverModerationHook) Register(manager *Manager) {
|
|
manager.Register(&Hook{
|
|
Name: h.name,
|
|
HookType: HookUserCoverPreModerate,
|
|
Priority: PriorityHigh,
|
|
Func: h.Execute,
|
|
Async: false,
|
|
Timeout: 30 * time.Second,
|
|
})
|
|
}
|
|
|
|
func (h *UserCoverModerationHook) Execute(ctx *HookContext) error {
|
|
data, ok := ctx.Data.(*UserCoverModerateHookData)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
result, ok := ctx.Metadata["result"].(*ModerationResult)
|
|
if !ok || result == nil {
|
|
return nil
|
|
}
|
|
|
|
if result.RejectReason != "" || result.NeedsReview {
|
|
return nil
|
|
}
|
|
|
|
if h.postAIService == nil || !h.postAIService.IsEnabled() {
|
|
result.Approved = true
|
|
result.ReviewedBy = "system"
|
|
return nil
|
|
}
|
|
|
|
err := h.postAIService.ModerateImage(ctx.Ctx, data.CoverURL)
|
|
if err != nil {
|
|
if userErr, ok := err.(interface{ UserMessage() string }); ok {
|
|
if revErr, ok := err.(reviewableError); ok && revErr.IsReview() {
|
|
result.Approved = false
|
|
result.NeedsReview = true
|
|
result.RejectReason = userErr.UserMessage()
|
|
result.ReviewedBy = "ai"
|
|
zap.L().Info("User cover flagged for manual review by AI",
|
|
zap.String("user_id", data.UserID),
|
|
zap.String("reason", result.RejectReason),
|
|
)
|
|
return nil
|
|
}
|
|
result.Approved = false
|
|
result.RejectReason = userErr.UserMessage()
|
|
result.ReviewedBy = "ai"
|
|
zap.L().Info("User cover rejected by AI moderation",
|
|
zap.String("user_id", data.UserID),
|
|
zap.String("reason", result.RejectReason),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
if h.strictMode {
|
|
result.Approved = false
|
|
result.Error = err
|
|
result.ReviewedBy = "ai"
|
|
zap.L().Error("AI cover moderation failed in strict mode, reject",
|
|
zap.String("user_id", data.UserID),
|
|
zap.Error(err),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
result.Approved = true
|
|
result.ReviewedBy = "system"
|
|
zap.L().Warn("AI moderation error, fallback approve cover",
|
|
zap.String("user_id", data.UserID),
|
|
zap.Error(err),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
result.Approved = true
|
|
result.ReviewedBy = "ai"
|
|
return nil
|
|
}
|
|
|
|
type UserBioModerationHook struct {
|
|
name string
|
|
postAIService PostAIService
|
|
sensitiveService SensitiveService
|
|
userRepo repository.UserRepository
|
|
strictMode bool
|
|
}
|
|
|
|
func NewUserBioModerationHook(postAIService PostAIService, sensitiveService SensitiveService, userRepo repository.UserRepository, strictMode bool) *UserBioModerationHook {
|
|
return &UserBioModerationHook{
|
|
name: "moderation.user.bio.ai",
|
|
postAIService: postAIService,
|
|
sensitiveService: sensitiveService,
|
|
userRepo: userRepo,
|
|
strictMode: strictMode,
|
|
}
|
|
}
|
|
|
|
func (h *UserBioModerationHook) Register(manager *Manager) {
|
|
manager.Register(&Hook{
|
|
Name: h.name,
|
|
HookType: HookUserBioPreModerate,
|
|
Priority: PriorityHigh,
|
|
Func: h.Execute,
|
|
Async: false,
|
|
Timeout: 30 * time.Second,
|
|
})
|
|
}
|
|
|
|
func (h *UserBioModerationHook) Execute(ctx *HookContext) error {
|
|
data, ok := ctx.Data.(*UserBioModerateHookData)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
result, ok := ctx.Metadata["result"].(*ModerationResult)
|
|
if !ok || result == nil {
|
|
return nil
|
|
}
|
|
|
|
if result.RejectReason != "" || result.NeedsReview {
|
|
return nil
|
|
}
|
|
|
|
if h.sensitiveService != nil {
|
|
hasSensitive, words := h.sensitiveService.Check(ctx.Ctx, data.Bio)
|
|
if hasSensitive {
|
|
result.Approved = false
|
|
result.RejectReason = "内容包含敏感词: " + strings.Join(words, ", ")
|
|
result.ReviewedBy = "sensitive_word"
|
|
zap.L().Info("User bio rejected by sensitive word filter",
|
|
zap.String("user_id", data.UserID),
|
|
zap.Strings("words", words),
|
|
)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if h.postAIService == nil || !h.postAIService.IsEnabled() {
|
|
result.Approved = true
|
|
result.ReviewedBy = "system"
|
|
return nil
|
|
}
|
|
|
|
err := h.postAIService.ModerateBio(ctx.Ctx, data.Bio)
|
|
if err != nil {
|
|
if userErr, ok := err.(interface{ UserMessage() string }); ok {
|
|
if revErr, ok := err.(reviewableError); ok && revErr.IsReview() {
|
|
result.Approved = false
|
|
result.NeedsReview = true
|
|
result.RejectReason = userErr.UserMessage()
|
|
result.ReviewedBy = "ai"
|
|
zap.L().Info("User bio flagged for manual review by AI",
|
|
zap.String("user_id", data.UserID),
|
|
zap.String("reason", result.RejectReason),
|
|
)
|
|
return nil
|
|
}
|
|
result.Approved = false
|
|
result.RejectReason = userErr.UserMessage()
|
|
result.ReviewedBy = "ai"
|
|
zap.L().Info("User bio rejected by AI moderation",
|
|
zap.String("user_id", data.UserID),
|
|
zap.String("reason", result.RejectReason),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
if h.strictMode {
|
|
result.Approved = false
|
|
result.Error = err
|
|
result.ReviewedBy = "ai"
|
|
zap.L().Error("AI bio moderation failed in strict mode, reject",
|
|
zap.String("user_id", data.UserID),
|
|
zap.Error(err),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
result.Approved = true
|
|
result.ReviewedBy = "system"
|
|
zap.L().Warn("AI moderation error, fallback approve bio",
|
|
zap.String("user_id", data.UserID),
|
|
zap.Error(err),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
result.Approved = true
|
|
result.ReviewedBy = "ai"
|
|
return nil
|
|
}
|