fix v14
This commit is contained in:
@@ -18,6 +18,8 @@ import (
|
||||
|
||||
"github.com/tamakyi/TamaBox/internal/conf"
|
||||
"github.com/tamakyi/TamaBox/internal/db"
|
||||
"github.com/tamakyi/TamaBox/internal/i18n"
|
||||
"github.com/tamakyi/TamaBox/internal/mail"
|
||||
)
|
||||
|
||||
// 本文件提供站方处置能力。项目刻意不做管理后台,因此运营动作全部落在 CLI:
|
||||
@@ -38,8 +40,10 @@ var Restrict = &cli.Command{
|
||||
&cli.StringFlag{Name: "domain", Usage: "Custom domain of the user"},
|
||||
&cli.StringFlag{Name: "level", Value: string(db.RestrictionL1), Usage: "Restriction level: L1 / L2 / L3 / L4"},
|
||||
&cli.IntFlag{Name: "days", Value: 7, Usage: "Effective days, counted from now"},
|
||||
&cli.StringFlag{Name: "reason", Usage: "Reason recorded for audit (required)"},
|
||||
&cli.StringFlag{Name: "reason", Usage: "Reason recorded for audit (required, not shown to the user)"},
|
||||
&cli.BoolFlag{Name: "yes", Usage: "Skip confirmation"},
|
||||
&cli.BoolFlag{Name: "force", Usage: "Required when --days exceeds 90 (abnormal duration)"},
|
||||
&cli.BoolFlag{Name: "no-notify", Usage: "Do not email the restriction notice to the user"},
|
||||
},
|
||||
Action: runRestrict,
|
||||
}
|
||||
@@ -119,6 +123,12 @@ func runRestrict(ctx *cli.Context) error {
|
||||
if days <= 0 {
|
||||
return errors.New("--days must be positive")
|
||||
}
|
||||
// 不设硬上限(确实存在长期封禁场景),但超过 90 天必须显式加 --force:
|
||||
// 这类值几乎都是手误(多打一个 0),靠 --yes 挡不住重复执行的历史命令。
|
||||
if days > restrictDaysForceThreshold && !ctx.Bool("force") {
|
||||
return errors.Errorf("--days %d 超过 %d 天,属于异常时长;确认无误请加 --force 重试",
|
||||
days, restrictDaysForceThreshold)
|
||||
}
|
||||
reason := strings.TrimSpace(ctx.String("reason"))
|
||||
if reason == "" {
|
||||
return errors.New("--reason is required (recorded for audit)")
|
||||
@@ -152,6 +162,23 @@ func runRestrict(ctx *cli.Context) error {
|
||||
fmt.Println("当前处置: 无")
|
||||
}
|
||||
fmt.Printf("本次处置: %s,%d 天,原因:%s\n", level, days, reason)
|
||||
if days > restrictDaysForceThreshold {
|
||||
fmt.Printf("⚠️ 超长处置:%d 天,到期 %s(常规处置为 7–30 天)\n",
|
||||
days, time.Now().AddDate(0, 0, days).Format("2006-01-02 15:04"))
|
||||
}
|
||||
|
||||
// 二次确认:异常时长时,即使已给 --force,交互模式下还要再输入一次级别。
|
||||
// 脚本里 `--force --yes` 两个显式开关本身就是两次确认,不再额外拦截。
|
||||
if days > restrictDaysForceThreshold && !ctx.Bool("yes") {
|
||||
logrus.Warn("这是超长处置。请再次输入级别名(如 L3)确认:")
|
||||
var confirmLevel string
|
||||
if _, err := fmtScan(&confirmLevel); err != nil {
|
||||
return errors.Wrap(err, "read confirmation")
|
||||
}
|
||||
if strings.TrimSpace(confirmLevel) != level {
|
||||
return errors.New("已取消(级别不匹配)")
|
||||
}
|
||||
}
|
||||
|
||||
if !ctx.Bool("yes") {
|
||||
logrus.Warn("输入大写 YES 确认执行(该操作可经 unrestrict 撤销):")
|
||||
@@ -182,9 +209,63 @@ func runRestrict(ctx *cli.Context) error {
|
||||
}).Info("处置已生效")
|
||||
fmt.Printf("已生效,到期时间 %s(到期自动解除,无需手工操作)\n",
|
||||
restriction.ExpiresAt.Format("2006-01-02 15:04"))
|
||||
|
||||
if !ctx.Bool("no-notify") {
|
||||
notifyRestrictionOwner(user, level, restriction.ExpiresAt)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// restrictDaysForceThreshold 超过这个天数需要 --force。不做硬上限:长期封禁是真实需求。
|
||||
const restrictDaysForceThreshold = 90
|
||||
|
||||
// notifyRestrictionOwner 把处置结果通知箱主(按他自己的界面语言)。
|
||||
//
|
||||
// 为什么必须通知:处置是平台单方面限制用户功能,不告知就变成「暗箱」,
|
||||
// 用户既无法自查也无法申诉。邮件里只给「级别 + 到期时间 + 规则链接」,
|
||||
// 不引用 --reason(那是运维审计备注),也不引用被举报内容原文。
|
||||
//
|
||||
// 发送失败只告警、不回滚:处置已经写入,邮件不可用不应让运营者误以为没生效。
|
||||
func notifyRestrictionOwner(user *db.User, level string, expires time.Time) {
|
||||
if user.Email == "" {
|
||||
fmt.Println("(该账号没有邮箱,跳过处置通知)")
|
||||
return
|
||||
}
|
||||
|
||||
lang := user.Language
|
||||
if lang == "" {
|
||||
lang = conf.App.DefaultLang
|
||||
}
|
||||
levelDesc := i18n.T(lang, "restriction.level."+level)
|
||||
notice := mail.RestrictionNotice{
|
||||
To: user.Email,
|
||||
Heading: i18n.T(lang, "mail.restriction.heading"),
|
||||
Greeting: i18n.T(lang, "mail.restriction.greeting", user.Name),
|
||||
Body: i18n.T(lang, "mail.restriction.body", user.Domain, level, levelDesc, expires.Format("2006-01-02 15:04")),
|
||||
Note: i18n.T(lang, "mail.restriction.note"),
|
||||
RulesText: i18n.T(lang, "mail.restriction.rules"),
|
||||
RulesURL: strings.TrimRight(conf.App.ExternalURL, "/") + "/rules",
|
||||
Appeal: i18n.T(lang, "mail.restriction.appeal"),
|
||||
}
|
||||
// 加超时:SMTP 不可达时 gomail 的裸 Dial 会一直等到系统 TCP 超时(可达两分钟),
|
||||
// 运营者站在终端前会以为命令卡死。超时只影响通知,不影响已写入的处置。
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- mail.SendRestrictionNoticeMail(notice) }()
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
logrus.WithError(err).WithField("user_id", user.ID).Warn("处置通知邮件发送失败(处置已生效)")
|
||||
fmt.Println("⚠️ 通知邮件发送失败(处置已生效)。请检查 [mail] 配置,或用 --no-notify 跳过通知。")
|
||||
return
|
||||
}
|
||||
case <-time.After(20 * time.Second):
|
||||
logrus.WithField("user_id", user.ID).Warn("处置通知邮件发送超时(处置已生效)")
|
||||
fmt.Println("⚠️ 通知邮件发送超时(处置已生效)。请检查 SMTP 配置。")
|
||||
return
|
||||
}
|
||||
fmt.Printf("已发送处置通知邮件至 %s(语言 %s)\n", user.Email, i18n.Resolve(lang))
|
||||
}
|
||||
|
||||
func runUnrestrict(ctx *cli.Context) error {
|
||||
database, err := openDatabase()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user