// Force Subscribe middleware - checks if a user has joined all required channels // before the bot responds. Channels are configured via FORCE_SUB env var (stored in DB). package plugins import ( "fmt" "github.com/Jisin0/Go-Filter-Bot/utils" "github.com/PaulSonOfLars/gotgbot/v2" "github.com/PaulSonOfLars/gotgbot/v2/ext" ) // forceSubText is the message sent when a user hasn't joined required channels. const forceSubText = `⚠️ Sᴜʙsᴄʀɪʙᴇ Rᴇǫᴜɪʀᴇᴅ! You must join the channels below before using this bot. After joining, click the ✅ I've Joined button.` // CheckForceSub verifies the user has joined all force-sub channels. // Returns true if the user is allowed (subscribed / admin / no force sub set). // Returns false and sends a join-prompt message if the user hasn't subscribed. func CheckForceSub(bot *gotgbot.Bot, ctx *ext.Context) bool { user := ctx.EffectiveUser if user == nil || user.Id == 0 { return true // anonymous admin – let through } // Bot admins bypass force-sub if utils.IsAdmin(user.Id) { return true } channels := DB.GetForceSubChannels() if len(channels) == 0 { return true } var notJoined []int64 for _, chanID := range channels { member, err := bot.GetChatMember(chanID, user.Id, nil) if err != nil { // Channel not accessible to bot – skip rather than block user continue } status := member.GetStatus() if status == "left" || status == "kicked" { notJoined = append(notJoined, chanID) } } if len(notJoined) == 0 { return true // user is subscribed to all channels } // Build one button per unjoined channel + verify button var rows [][]gotgbot.InlineKeyboardButton for _, chanID := range notJoined { invite := buildInviteLink(bot, chanID) if invite == "" { continue } chat, err := bot.GetChat(chanID, nil) title := fmt.Sprintf("Channel %d", chanID) if err == nil { title = chat.Title } rows = append(rows, []gotgbot.InlineKeyboardButton{{ Text: "📢 Join " + title, Url: invite, }}) } rows = append(rows, []gotgbot.InlineKeyboardButton{{ Text: "✅ I've Joined", CallbackData: "checksub", }}) if ctx.EffectiveMessage != nil { ctx.EffectiveMessage.Reply(bot, forceSubText, &gotgbot.SendMessageOpts{ //nolint:errcheck ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: rows}, }) } return false } // buildInviteLink returns a public or exported invite link for a channel. func buildInviteLink(bot *gotgbot.Bot, chanID int64) string { chat, err := bot.GetChat(chanID, nil) if err != nil { return "" } if chat.Username != "" { return "https://t.me/" + chat.Username } link, err := bot.ExportChatInviteLink(chanID, nil) if err != nil { return "" } return link } // CbCheckSub handles the "✅ I've Joined" callback button. // //nolint:errcheck func CbCheckSub(bot *gotgbot.Bot, ctx *ext.Context) error { update := ctx.CallbackQuery userID := update.From.Id channels := DB.GetForceSubChannels() var notJoined []int64 for _, chanID := range channels { member, err := bot.GetChatMember(chanID, userID, nil) if err != nil { continue } status := member.GetStatus() if status == "left" || status == "kicked" { notJoined = append(notJoined, chanID) } } if len(notJoined) == 0 { update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{ Text: "✅ Verified! You can now use the bot.", ShowAlert: true, }) update.Message.Delete(bot, nil) } else { update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{ Text: "❌ You haven't joined all required channels yet!", ShowAlert: true, }) } return nil }