diff --git a/.gitignore b/.gitignore index 4cfda1d..625331e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ config.*.toml db/geecaptcha.db captcha-bot dict/dec_* -.vscode \ No newline at end of file +.vscode +vendor \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 165f0b8..488885f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,8 +19,9 @@ RUN go build -ldflags '-linkmode external -extldflags "-static"' -o captcha-bot FROM alpine:latest -COPY --from=builder /app/captcha-bot /work/captcha-bot +COPY --from=builder /app /work WORKDIR /work +EXPOSE 80 ENTRYPOINT ["./captcha-bot"] diff --git a/config/config.toml b/config/config.toml index 57af796..b6831f4 100644 --- a/config/config.toml +++ b/config/config.toml @@ -19,9 +19,9 @@ max_backups=3 #消息模板 [message] -join_hint="欢迎 [%s](%s) 加入 %s\n ⚠️本群已开启新成员验证功能,未通过验证的用户无法发言 \n超时未完成验证会被移除本群! \n⏱本条消息 %d 秒后自动删除\n👇点击下方按钮自助解除禁言" -captcha_image="欢迎您加入[%s]!\n⚠本群已开启新成员验证功能。\n👆为了证明您不是机器人,请发送以上图片验证码内容\n🤖机器人将自动验证您发送的验证码内容是否正确\n⏱本条验证消息有效期[%d]秒" -verification_complete="恭喜您成功通过[🤖人机验证],系统已为您解除禁言限制。\n如若还是无法发言,请重启telegram客户端\n请同时注意群内发言规范,以防永久禁止" +join_hint="Welcom [%s](%s) to join %s\\!\n ⚠️ This group has enabled new member verification\\.\n User who failed to complete verification in time will be removed from this group\\!\n ⏱ This message will be deleted after %d seconds automatically\\.\n 👇 Click the button below to self\\-unmute\\." +captcha_image="Welcome to join[%s]!\n⚠This group has enabled new member verification.\n👆Please send the captcha above\n🤖I'll check if it is correct\n⏱This message is vaild in [%d]seconds" +verification_complete="Welcome to join us!\nYou can restart telegram if you still can't speak.\nPlease also pay attention to the norms of communication to avoid permanent bans." block_hint="\\#封禁预警\n[%s](%s) 请注意,您的消息中含有部分违禁词 \n⚠️您已被系统判断为高风险用户,已被封禁\n系统已向超管发送预警信息,若由超管判定为误杀,会及时将您解除封禁。\n您的违禁词包含:%s" #广告阻止 diff --git a/main.go b/main.go index 3002ce0..3659361 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,39 @@ package main import ( "fmt" - "net/http" - "github.com/assimon/captcha-bot/bootstrap" + "io" + "log" + "net/http" + "strconv" + "time" ) +var counter = 0 + func main() { go func() { http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { + counter++ + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("counter", strconv.Itoa(counter)) fmt.Fprintf(w, "OK") }) - http.ListenAndServe(":8080", nil) + _ = http.ListenAndServe(":80", nil) + }() + + go func() { + t := time.Tick(time.Second * 10) + for { + if resp, err := http.Get("https://captcha-bot-zg9p.onrender.com/healthz"); err != nil { + log.Default().Printf("error: " + err.Error()) + } else { + if b, err := io.ReadAll(resp.Body); err == nil { + log.Default().Printf("health: " + string(b)) + } + } + <-t + } }() bootstrap.Start() diff --git a/telegram/handle.go b/telegram/handle.go index 6523f95..bdeb119 100644 --- a/telegram/handle.go +++ b/telegram/handle.go @@ -64,7 +64,7 @@ func StartCaptcha(c tb.Context) error { File: tb.FromDisk(imgUrl), Caption: captchaMessage, } - refreshCaptchaImageBtn := captchaMessageMenu.Data("🔁刷新验证码", "refreshCaptchaImageBtn", captchaId) + refreshCaptchaImageBtn := captchaMessageMenu.Data("🔁Refresh", "refreshCaptchaImageBtn", captchaId) Bot.Handle(&refreshCaptchaImageBtn, refreshCaptcha()) captchaMessageMenu.Inline( captchaMessageMenu.Row(refreshCaptchaImageBtn), @@ -72,7 +72,7 @@ func StartCaptcha(c tb.Context) error { botMsg, err := Bot.Send(c.Chat(), sendMessage, captchaMessageMenu) if err != nil { log.Sugar.Error("[StartCaptcha] send image captcha err:", err) - return c.Send("服务器异常~,请稍后再试") + return c.Send("Service is busy,please try later") } err = service.SetCaptchaCodeMessageIdByCaptchaId(captchaId, botMsg.ID) if err != nil { @@ -165,12 +165,12 @@ func VerificationProcess(c tb.Context) error { captchaId, ok := captchaIdObj.(string) if !ok { log.Sugar.Error("Value is not a string") - return c.Send("服务器异常~,请稍后再试") + return c.Send("Service is busy,please try later") } captchaRecord, err := service.GetRecordByCaptchaId(captchaId) if err != nil { log.Sugar.Error("[VerificationProcess] GetRecordByCaptchaId err:", err) - return c.Send("服务器异常~,请稍后再试") + return c.Send("Service is busy,please try later") } if captchaRecord.ID <= 0 || captchaRecord.TelegramUserId != c.Sender().ID || captchaRecord.CaptchaStatus != model.CaptchaStatusPending { return c.Send("您在该群没有待验证记录,或已超时,请重新加入后验证") @@ -178,7 +178,7 @@ func VerificationProcess(c tb.Context) error { // 验证 replyCode := c.Message().Text if !captcha.VerifyCaptcha(captchaRecord.CaptchaCode, replyCode) { - return c.Send("验证码错误,请重新输入!") + return c.Send("Incorrect captcha") } // 解禁用户 err = Bot.Restrict(&tb.Chat{ID: captchaRecord.TelegramChatId}, &tb.ChatMember{ @@ -187,7 +187,7 @@ func VerificationProcess(c tb.Context) error { }) if err != nil { log.Sugar.Error("[OnTextMessage] unban err:", err) - return c.Send("服务器异常~,请稍后重试~") + return c.Send("Service is busy,please try later") } err = service.SuccessRecordByCaptchaId(captchaId) if err != nil { @@ -205,6 +205,8 @@ func VerificationProcess(c tb.Context) error { func UserJoinGroup(c tb.Context) error { var err error + m := c.ChatMember().NewChatMember.Role + _ = m if c.ChatMember().NewChatMember.Role != tb.Member { return nil } @@ -232,10 +234,10 @@ func UserJoinGroup(c tb.Context) error { chat.Title, config.SystemC.JoinHintAfterDelTime) captchaId := uuid.NewV4().String() - doCaptchaBtn := joinMessageMenu.URL("👉🏻点我开始人机验证🤖", fmt.Sprintf("https://t.me/%s?start=%s", Bot.Me.Username, captchaId)) + doCaptchaBtn := joinMessageMenu.URL("👉🏻To Verify🤖", fmt.Sprintf("https://t.me/%s?start=%s", Bot.Me.Username, captchaId)) var ( - manageBanBtn = joinMessageMenu.Data("👮‍管理员禁止🈲", "manageBanBtn", captchaId) - managePassBtn = joinMessageMenu.Data("👮‍管理员通过✅", "managePassBtn", captchaId) + manageBanBtn = joinMessageMenu.Data("👮Ban🈲", "manageBanBtn", captchaId) + managePassBtn = joinMessageMenu.Data("👮Pass✅", "managePassBtn", captchaId) ) // 按钮点击事件 Bot.Handle(&manageBanBtn, ManageBan(), isManageMiddleware) @@ -366,7 +368,7 @@ func refreshCaptcha() func(c tb.Context) error { }) } return c.Respond(&tb.CallbackResponse{ - Text: "验证码已刷新~", + Text: "Refreshed", }) } } diff --git a/telegram/root.go b/telegram/root.go index 7a5b376..86f4570 100644 --- a/telegram/root.go +++ b/telegram/root.go @@ -1,11 +1,15 @@ package telegram import ( + "log" + "net/http" + "net/url" + "os" + "time" + "github.com/assimon/captcha-bot/util/config" ulog "github.com/assimon/captcha-bot/util/log" tb "gopkg.in/telebot.v3" - "log" - "time" ) var Bot *tb.Bot @@ -13,7 +17,13 @@ var Bot *tb.Bot // BotStart 机器人启动 func BotStart() { setting := tb.Settings{ - Token: config.TelegramC.BotToken, + Token: func() string { + if token := os.Getenv("bot_token"); len(token) > 0 { + return token + } else { + return config.TelegramC.BotToken + } + }(), Updates: 100, Poller: &tb.LongPoller{Timeout: 10 * time.Second, AllowedUpdates: []string{ "message", @@ -25,9 +35,17 @@ func BotStart() { ulog.Sugar.Error(err) }, } - // 反向代理 + // 代理 if config.TelegramC.ApiProxy != "" { - setting.URL = config.TelegramC.ApiProxy + trans := &http.Transport{ + Proxy: func(_ *http.Request) (*url.URL, error) { + return url.Parse(config.TelegramC.ApiProxy) + }, + } + setting.Client = &http.Client{ + Timeout: time.Minute, + Transport: trans, + } } var err error Bot, err = tb.NewBot(setting) @@ -92,7 +110,7 @@ func isManage(chat *tb.Chat, userId int64) bool { // isRoot 判断是否为超管 func isRoot(userid int64) bool { - for _, id := range config.TelegramC.ManageUsers { + for _, id := range config.TelegramC.GetManageUsers() { if userid == id { return true } diff --git a/util/config/config.go b/util/config/config.go index 61058c5..367e549 100644 --- a/util/config/config.go +++ b/util/config/config.go @@ -3,6 +3,8 @@ package config import ( "log" "os" + "strconv" + "strings" "github.com/spf13/viper" ) @@ -23,6 +25,21 @@ type Telegram struct { ManageUsers []int64 `mapstructure:"manage_users"` } +func (tel *Telegram) GetManageUsers() []int64 { + if len(tel.ManageUsers) == 0 { + if users := os.Getenv("managers"); len(users) > 0 { + if userArr := strings.Split(users, ","); len(userArr) > 0 { + tel.ManageUsers = make([]int64, len(userArr)) + for i, uid := range userArr { + uid64, _ := strconv.Atoi(uid) + tel.ManageUsers[i] = int64(uid64) + } + } + } + } + return tel.ManageUsers +} + var TelegramC Telegram type Log struct { @@ -42,6 +59,21 @@ type Message struct { var MessageC Message +func (msg *Message) FromEnv() { + if joinHint := os.Getenv("msg__join_hint"); len(joinHint) > 0 { + msg.JoinHint = joinHint + } + if captchaImage := os.Getenv("msg__captcha_image"); len(captchaImage) > 0 { + msg.CaptchaImage = captchaImage + } + if verificationComplete := os.Getenv("msg__verification_complete"); len(verificationComplete) > 0 { + msg.VerificationComplete = verificationComplete + } + if blockHint := os.Getenv("msg__block_hint"); len(blockHint) > 0 { + msg.BlockHint = blockHint + } +} + type AdBlock struct { NumberOfForbiddenWords int `mapstructure:"number_of_forbidden_words"` BlockTime int64 `mapstructure:"block_time"` @@ -80,6 +112,8 @@ func InitConfig() { err = viper.UnmarshalKey("message", &MessageC) if err != nil { log.Fatal("load config message err:", err) + } else { + MessageC.FromEnv() } err = viper.UnmarshalKey("adblock", &AdBlockC) if err != nil {