// Copyright 2022 E99p1ant. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package censor import ( "context" "encoding/json" "fmt" "strings" ) type ForbiddenType string const ( ForbiddenTypeSpam ForbiddenType = "spam" ForbiddenTypeAd ForbiddenType = "ad" ForbiddenTypePolitics ForbiddenType = "politics" ForbiddenTypeTerrorism ForbiddenType = "terrorism" ForbiddenTypeAbuse ForbiddenType = "abuse" ForbiddenTypePorn ForbiddenType = "porn" ForbiddenTypeFlood ForbiddenType = "flood" ForbiddenTypeContraband ForbiddenType = "contraband" ForbiddenTypeMeaningless ForbiddenType = "meaningless" ForbiddenTypeHarmful ForbiddenType = "harmful" ) func (f ForbiddenType) String() string { return map[ForbiddenType]string{ ForbiddenTypeSpam: "含垃圾信息", ForbiddenTypeAd: "广告", ForbiddenTypePolitics: "涉政", ForbiddenTypeTerrorism: "暴恐", ForbiddenTypeAbuse: "辱骂", ForbiddenTypePorn: "色情", ForbiddenTypeFlood: "灌水", ForbiddenTypeContraband: "违禁", ForbiddenTypeMeaningless: "无意义", ForbiddenTypeHarmful: "不良场景", }[f] } type TextCensorResponse struct { SourceName string `json:"source_name"` Pass bool `json:"pass"` ForbiddenType ForbiddenType `json:"forbidden_type"` Hint string `json:"hint"` Confidence float64 `json:"confidence"` RawResponse json.RawMessage `json:"raw_response"` } func (r *TextCensorResponse) ToJSON() []byte { jsonBytes, _ := json.Marshal(r) return jsonBytes } func (r *TextCensorResponse) ErrorMessage() string { errorMessage := "内容安全检查不通过" if r.ForbiddenType != "" { errorMessage += fmt.Sprintf(" [%s]", r.ForbiddenType.String()) } if r.Hint != "" { errorMessage += fmt.Sprintf(",相关标签:%q", r.Hint) } return errorMessage } type TextCensor interface { Censor(ctx context.Context, text string) (*TextCensorResponse, error) String() string } var TrustedHosts = []string{ "xhslink.com", // 小红书 "weibo.com", // 微博 "b23.tv", // Bilibili "acfun.cn", "aixifan.com", "github.com", // 自有域名:旧域 tama.guru 保留至 2026-12 到期前的过渡期结束, // 期间用户分享的旧链接仍需视为可信(不被判为广告);新域 shiroko.one 为主域。 "tama.guru", "shiroko.one", "337845818.best", "x.com", "bilibili.com", } // RemoveTrustedURL removes the trusted URL from the text, // in order to bypass the URL censoring. // 仅当可信域名后紧跟路径分隔符(/ ? #)或位于字符串结尾时才视为可信, // 避免 github.com.evil.tld 被朴素替换误判为 github.com 之子域而漏审。 func RemoveTrustedURL(str string) string { for _, host := range TrustedHosts { for _, protocol := range []string{"http://", "https://"} { needle := protocol + host var b strings.Builder rest := str for { idx := strings.Index(rest, needle) if idx < 0 { b.WriteString(rest) break } b.WriteString(rest[:idx]) after := rest[idx+len(needle):] if len(after) == 0 || after[0] == '/' || after[0] == '?' || after[0] == '#' { // 可信域名边界:跳过该前缀,从域名之后继续(域名本身被移除,不进入审核)。 rest = after } else { // 非可信域名边界(如 github.com.evil):保留前缀,从其后继续扫描。 b.WriteString(needle) rest = after } } str = b.String() } } return str }