119 lines
3.4 KiB
Go
119 lines
3.4 KiB
Go
// 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",
|
|
"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
|
|
}
|