Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+359
View File
@@ -0,0 +1,359 @@
package truthutil
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"time"
"unicode"
"github.com/mozillazg/go-pinyin"
"91porn-server/app/appg"
"91porn-server/common/constant/redisconst"
"91porn-server/common/localcache"
"91porn-server/common/log"
"91porn-server/models/v/filtermod"
)
// CheckIsValid 判断评论、私信等内容是否合法
func CheckIsValid(a string, checkType int) bool {
// 忽略emo表情 参考:https://apps.timwhitlock.info/emoji/tables/unicode#block-6c-other-additional-symbols
reg := regexp.MustCompile(`[\x{1F600}-\x{1F64F}\x{1F444}-\x{1F445}\x{1F488}-\x{1F48B}]+`)
a = reg.ReplaceAllString(a, "")
// 检查白名单
if valid := checkWhiteListChars(a); !valid {
return false
}
// 检查后台敏感词
if valid := checkSensitiveWords(a, checkType); !valid {
return false
}
// 检查url和数字
if valid := checkURIAndNumber(a); !valid {
return false
}
// 检查评论频率
if valid := checkRepeat(a); !valid {
return false
}
return true
}
// 仅允许常规字符
func checkWhiteListChars(s string) bool {
// 全符号文本
cv, err := NewCommentValidator(`^[\d\p{P}\p{S}\s]*$`)
if err != nil {
return false
}
valid := cv.Validate(s)
if valid {
return false
}
cv, err = NewCommentValidator("^[A-Za-z0-9*!,.?,。?!、 \u4e00-\u9fa5]+$")
if err != nil {
log.Error(fmt.Sprintf("checkWhiteListChars Invalid pattern err:%v", err))
return false
}
valid = cv.Validate(s)
if !valid {
return false
}
return true
}
func checkSensitiveWords(a string, checkType int) bool {
// 获取缓存数据
str, err := appg.Redis.Get(redisconst.SensitiveWordsCache)
if err != nil {
log.Warn(fmt.Sprintf("redis get SensitiveWords:%v", err))
}
var sensitiveWords []string
if str != nil {
if err = json.Unmarshal([]byte(*str), &sensitiveWords); err != nil {
log.Warn(fmt.Sprintf("redis get SensitiveWords parse err:%v", err))
}
} else {
words, err := filtermod.GetAllFilterWords()
if err != nil || len(words) <= 0 {
return true
}
for _, w := range words {
sensitiveWords = append(sensitiveWords, w.Word)
}
if len(sensitiveWords) > 0 {
// 放入缓存
bytes, _ := json.Marshal(sensitiveWords)
err := appg.Redis.Set(redisconst.SensitiveWordsCache, bytes, 10*time.Minute)
if err != nil {
log.Warn(fmt.Sprintf("redis set SensitiveWords err:%v", err))
}
}
}
a = strings.ReplaceAll(a, " ", "")
a = strings.ReplaceAll(a, "\n", "")
a = strings.ToUpper(a)
for _, s := range sensitiveWords {
filterStr := strings.ReplaceAll(s, " ", "")
filterStr = strings.ReplaceAll(filterStr, "\n", "")
filterStr = strings.ToUpper(filterStr)
if checkType == 0 {
// 个人用户名,简介等
if strings.Contains(a, filterStr) {
return false
}
} else {
// 用于评论
// 2026-3 改为精确匹配
if a == filterStr {
return false
}
}
}
return true
}
// checkURIAndNumber 检查qq号或网址
func checkURIAndNumber(a string) bool {
a = strings.ReplaceAll(a, "匚", "c")
a = strings.ReplaceAll(a, "匸", "c")
a = strings.ReplaceAll(a, "丅", "t")
a = strings.ReplaceAll(a, "卩", "p")
a = strings.ReplaceAll(a, "卪", "p")
a = strings.ReplaceAll(a, "讠", "i")
a = strings.ReplaceAll(a, "", ".")
a = strings.ReplaceAll(a, "、", ".")
a = strings.ReplaceAll(a, ",", ".")
a = strings.ReplaceAll(a, "", ".")
a = strings.ReplaceAll(a, "。", ".")
a = strings.ReplaceAll(a, "俛", "免")
eRegexp := regexp.MustCompile("[^\u4e00-\u9fa50-9a-zA-Z.。+]+")
effective := eRegexp.ReplaceAllString(a, "")
ra := []rune(effective)
final := ""
args := pinyin.NewArgs()
args.Fallback = func(r rune, a pinyin.Args) []string {
return []string{string(r)}
}
pys := pinyin.LazyConvert(effective, &args)
numMap := make(map[int]bool)
var diffNumCount int
var haveJia bool
var haveWei bool
var yue bool
var pao bool
var wang bool
var mian bool
var fei bool
for i, py := range pys {
c := string(ra[i])
n := -1
switch py {
case "ling", "ning", "lin", "nin", "O", "0":
n = 0
if py == "0" || py == "o" || py == "O" {
c = "o"
} else {
c = "0"
}
case "yi", "1":
n = 1
if py == "yi" {
c = "e"
}
case "er", "2":
n = 2
c = "2"
case "san", "sa", "shan", "3":
n = 3
c = "3"
case "si", "shi", "4":
n = 4
c = "4"
case "wu", "5":
n = 5
c = "5"
case "liu", "lu", "niu", "nu", "6":
n = 6
c = "6"
case "qi", "7":
n = 7
c = "7"
case "ba", "pa", "8":
n = 8
c = "8"
case "jiu", "9":
n = 9
c = "9"
case "dian":
c = "."
// "xi" 是常见汉字读音,不转成 "c",避免“嘻嘻”等正常内容被误判为 .cc 域名。
case "se":
c = "c"
case "ti":
c = "t"
case "en", "eng":
c = "n"
case "ou":
c = "o"
case "pi":
c = "p"
case "jia", "+":
haveJia = true
case "wei", "v", "V", "w", "W", "qun", "qiong", "kou", "qiu", "q", "Q":
haveWei = true
case "wang":
wang = true
case "yue", "yo":
yue = true
case "pao", "bao":
pao = true
case "mian":
mian = true
case "fei":
fei = true
}
if n >= 0 {
if _, ok := numMap[n]; !ok {
numMap[n] = true
diffNumCount++
}
}
final += c
}
lRegexp := regexp.MustCompile(`[^a-zA-Z0-57-9]+`)
letter := lRegexp.ReplaceAllString(final, "")
//fmt.Println(effective)
//fmt.Println(final)
//fmt.Println(alpha)
// 数字检查
if diffNumCount >= 4 || len(letter) >= 5 {
return false
}
if haveJia && haveWei {
return false
}
alphaRegexp := regexp.MustCompile(`[^a-zA-Z0-9.]+`)
alpha := alphaRegexp.ReplaceAllString(final, "")
// 网址检查
if r := regexp.MustCompile(`(?i)\w*(aaa|abb|abc|ac|aco|ad|ads|ae|aeg|af|afl|ag|ai|aig|al|am|anz|ao|aol|app|aq|ar|art|as|at|au|aw|aws|ax|axa|az|ba|bar|bb|bbc|bbt|bcg|bcn|bd|be|bet|bf|bg|bh|bi|bid|bio|biz|bj|bm|bms|bmw|bn|bo|bom|boo|bot|box|br|bs|bt|buy|bv|bw|by|bz|bzh|ca|cab|cal|cam|car|cat|cba|cbn|cc|cd|ceo|cf|cfa|cfd|cg|ch|ci|ck|cl|cm|cn|co|com|cpa|cr|crs|cu|cv|cw|cx|cy|cz|dad|day|dds|de|dev|dhl|diy|dj|dk|dm|dnp|do|dog|dot|dtv|dvr|dz|eat|ec|eco|edu|ee|eg|er|es|esq|et|eu|eus|fan|fi|fit|fj|fk|fly|fm|fo|foo|fox|fr|frl|ftr|fun|fyi|hbo|hiv|hk|hkt|hm|hn|hot|how|hr|ht|hu|ibm|ice|icu|id|ie|ifm|il|im|in|inc|ing|ink|int|io|iq|ir|is|ist|it|itv|jcb|je|jio|jll|jm|jmp|jnj|jo|jot|joy|jp|ke|kfh|kg|kh|ki|kia|kim|km|kn|kp|kpn|kr|krd|kw|ky|kz|la|lat|law|lb|lc|lds|li|lk|llc|llp|lol|lpl|lr|ls|lt|ltd|lu|lv|ly|ma|man|map|mba|mc|md|me|med|men|mg|mh|mil|mit|mk|ml|mlb|mls|mm|mma|mn|mo|moe|moi|mom|mov|mp|mq|mr|ms|msd|mt|mtn|mtr|mu|mv|mw|mx|my|mz|na|nab|nba|nc|ne|nec|net|new|nf|nfl|ng|ngo|nhk|ni|nl|no|now|np|nr|nra|nrw|ntt|nu|nyc|nz|obi|om|one|ong|onl|ooo|org|ott|ovh|pa|pay|pe|pet|pf|pg|ph|phd|pid|pin|pk|pl|pm|pn|pnc|pr|pro|pru|ps|pt|pub|pw|pwc|py|qa|re|red|ren|ril|rio|rip|ro|rs|ru|run|rw|rwe|sa|sap|sas|sb|sbi|sbs|sc|scb|sd|se|sew|sex|sfr|sg|sh|si|sj|sk|ski|sky|sl|sm|sn|so|soy|spa|sr|srl|ss|st|stc|su|sv|sx|sy|sz|tab|tax|tc|tci|td|tdk|tel|tf|tg|th|thd|tj|tjx|tk|tl|tm|tn|to|top|tr|trv|tt|tui|tv|tvs|tw|tz|ua|ubs|ug|uk|uno|uol|ups|us|uy|uz|va|vc|ve|vet|vg|vi|vig|vin|vip|vn|vu|wed|wf|win|wme|wow|ws|wtc|wtf|xin|xxx|xyz|ye|you|yt|yun|za|zip|zm|zw)`); r != nil {
// 匹配成功
if r.MatchString(alpha) {
return false
}
}
if r := regexp.MustCompile(`(?i)\w+\.[0-1a-z]{2,}`); r != nil {
// 匹配成功
if r.MatchString(alpha) {
return false
}
}
// 综合检查
if (wang || yue || pao || (mian && fei)) && len(letter) >= 3 {
return false
}
return true
}
// checkRepeat 30分钟内超过5条一样的 拉入12小时黑名单
func checkRepeat(a string) bool {
numRegexp := regexp.MustCompile("[^\u4e00-\u9fa5a-zA-Z]+")
a = numRegexp.ReplaceAllString(a, "")
var mLimit, cLimit = 30, 5
var black = 12
var hasHan bool
//var hasAlpha bool
rmap := make(map[int32]bool)
var rc int
for _, r := range a {
if unicode.Is(unicode.Han, r) {
hasHan = true
}
//if unicode.IsDigit(r) || isEnLetter(r) {
// hasAlpha = true
//}
if _, ok := rmap[r]; !ok {
rmap[r] = true
rc++
}
}
// 必须包含汉字并且字母或数字 并且5个以上不重复字符
if !hasHan || rc < 5 {
return true
}
// 记录评论map
r := []rune(a)
if len(r) > 30 {
s1 := r[:30]
a = string(s1)
}
bKey := "cmt-b-" + a
key := "cmt-" + a
// 查看黑名单
_, ok := localcache.C.Get(bKey)
if ok {
return false
}
// 记录次数
c, ok2 := localcache.C.Get(key)
if !ok2 {
localcache.C.Set(key, 1, time.Duration(mLimit)*time.Minute)
} else {
// 超过次数加入黑名单
ci := c.(int)
if ci+1 >= cLimit {
localcache.C.Set(bKey, true, time.Duration(black)*time.Hour)
localcache.C.Delete(key)
return false
}
_ = localcache.C.Increment(key, 1)
}
return true
}
// 判断英文字母
func isEnLetter(s rune) bool {
return (s >= 97 && s <= 122) || (s >= 65 && s <= 90)
}
type CommentValidator struct {
pattern *regexp.Regexp
}
func NewCommentValidator(pattern string) (*CommentValidator, error) {
re, err := regexp.Compile(pattern)
if err != nil {
return nil, err
}
return &CommentValidator{pattern: re}, nil
}
func (cv *CommentValidator) Validate(comment string) bool {
return cv.pattern.MatchString(comment)
}
+38
View File
@@ -0,0 +1,38 @@
package truthutil
import "testing"
func TestCheckURIAndNumberAllowsXiGreeting(t *testing.T) {
t.Parallel()
for _, content := range []string{
"嘻嘻",
"嘻嘻,我在这里",
"西西",
} {
content := content
t.Run(content, func(t *testing.T) {
t.Parallel()
if !checkURIAndNumber(content) {
t.Fatalf("checkURIAndNumber(%q) = false, want true", content)
}
})
}
}
func TestCheckURIAndNumberStillRejectsURLs(t *testing.T) {
t.Parallel()
for _, content := range []string{
"abc.com",
"abc点com",
} {
content := content
t.Run(content, func(t *testing.T) {
t.Parallel()
if checkURIAndNumber(content) {
t.Fatalf("checkURIAndNumber(%q) = true, want false", content)
}
})
}
}