Spaces:
Sleeping
Sleeping
Auto index concept update
Browse files- database/database.go +29 -11
- main.go +9 -0
- plugins/basics.go +28 -18
- plugins/bulkindex.go +2 -1
- plugins/filter.go +280 -605
- plugins/index.go +20 -20
database/database.go
CHANGED
|
@@ -848,18 +848,36 @@ func (db *Database) CleanGhostFilters() int64 {
|
|
| 848 |
return count
|
| 849 |
}
|
| 850 |
|
| 851 |
-
//
|
| 852 |
-
//
|
| 853 |
-
func (
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 861 |
if err != nil {
|
| 862 |
fmt.Printf("db.SaveMfilterByFileID: %v\n", err)
|
|
|
|
| 863 |
}
|
| 864 |
-
return
|
| 865 |
}
|
|
|
|
| 848 |
return count
|
| 849 |
}
|
| 850 |
|
| 851 |
+
// randShortID generates a random alphanumeric string of given length.
|
| 852 |
+
// Safe for Telegram deep link start parameters (max 64 chars).
|
| 853 |
+
func randShortID(n int) string {
|
| 854 |
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
| 855 |
+
b := make([]byte, n)
|
| 856 |
+
seed := time.Now().UnixNano()
|
| 857 |
+
for i := range b {
|
| 858 |
+
seed = seed*6364136223846793005 + 1442695040888963407
|
| 859 |
+
b[i] = chars[((seed>>33)&0x7fffffffffffffff)%int64(len(chars))]
|
| 860 |
+
}
|
| 861 |
+
return string(b)
|
| 862 |
+
}
|
| 863 |
+
|
| 864 |
+
// SaveMfilterByFileID saves a filter with a short random _id (safe for Telegram start params).
|
| 865 |
+
// Deduplication is done by checking the 'file' (telegram file_id) field to avoid double-indexing.
|
| 866 |
+
// Returns true if a new document was inserted, false if it already existed.
|
| 867 |
+
func (db *Database) SaveMfilterByFileID(data *Filter) (bool, error) {
|
| 868 |
+
// Check if this telegram file_id already exists
|
| 869 |
+
existing := db.Mcol.FindOne(context.TODO(), bson.D{{Key: "file", Value: data.FileID}})
|
| 870 |
+
if existing.Err() == nil {
|
| 871 |
+
return false, nil // already indexed
|
| 872 |
+
}
|
| 873 |
+
|
| 874 |
+
// Generate a short random _id safe for Telegram deep links (max 64 chars with "file_" prefix = 59 chars)
|
| 875 |
+
data.ID = randShortID(15)
|
| 876 |
+
|
| 877 |
+
_, err := db.Mcol.InsertOne(context.TODO(), data)
|
| 878 |
if err != nil {
|
| 879 |
fmt.Printf("db.SaveMfilterByFileID: %v\n", err)
|
| 880 |
+
return false, err
|
| 881 |
}
|
| 882 |
+
return true, nil
|
| 883 |
}
|
main.go
CHANGED
|
@@ -80,6 +80,15 @@ func main() {
|
|
| 80 |
// Send restart alert to log channel
|
| 81 |
go logger.SendRestart(b)
|
| 82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
// Always run the auto-delete daemon in the background to support dynamic settings
|
| 84 |
go autodelete.RunAutodel(b)
|
| 85 |
|
|
|
|
| 80 |
// Send restart alert to log channel
|
| 81 |
go logger.SendRestart(b)
|
| 82 |
|
| 83 |
+
// Auto-cleanup ghost/reference docs on every startup
|
| 84 |
+
go func() {
|
| 85 |
+
removed := plugins.DB.CleanGhostFilters()
|
| 86 |
+
if removed > 0 {
|
| 87 |
+
fmt.Printf("startup: cleaned %d ghost docs from DB\n", removed)
|
| 88 |
+
logger.Send(b, fmt.Sprintf("🧹 <b>Startup cleanup:</b> removed <code>%d</code> ghost documents from DB.", removed))
|
| 89 |
+
}
|
| 90 |
+
}()
|
| 91 |
+
|
| 92 |
// Always run the auto-delete daemon in the background to support dynamic settings
|
| 93 |
go autodelete.RunAutodel(b)
|
| 94 |
|
plugins/basics.go
CHANGED
|
@@ -172,14 +172,12 @@ func CbEdit(bot *gotgbot.Bot, update *ext.Context) error {
|
|
| 172 |
}
|
| 173 |
|
| 174 |
editMarkup := gotgbot.InlineKeyboardMarkup{InlineKeyboard: markup}
|
| 175 |
-
chatID := update.CallbackQuery.Message.GetChat().Id
|
| 176 |
-
msgID := update.CallbackQuery.Message.GetMessageId()
|
| 177 |
|
| 178 |
-
//
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
_, _, err := origMsg.EditCaption(bot, &gotgbot.EditMessageCaptionOpts{
|
| 184 |
Caption: text,
|
| 185 |
ParseMode: gotgbot.ParseModeHTML,
|
|
@@ -191,20 +189,32 @@ func CbEdit(bot *gotgbot.Bot, update *ext.Context) error {
|
|
| 191 |
return nil
|
| 192 |
}
|
| 193 |
|
| 194 |
-
//
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
})
|
| 204 |
if err != nil {
|
| 205 |
-
fmt.Printf("cbedit: %v\n", err)
|
| 206 |
}
|
| 207 |
-
|
| 208 |
return nil
|
| 209 |
}
|
| 210 |
|
|
|
|
| 172 |
}
|
| 173 |
|
| 174 |
editMarkup := gotgbot.InlineKeyboardMarkup{InlineKeyboard: markup}
|
|
|
|
|
|
|
| 175 |
|
| 176 |
+
// Try to get the full message object
|
| 177 |
+
origMsg, isMsg := update.CallbackQuery.Message.(*gotgbot.Message)
|
| 178 |
+
|
| 179 |
+
// If it's a photo message (sent with START_IMAGE), edit caption
|
| 180 |
+
if isMsg && len(origMsg.Photo) > 0 {
|
| 181 |
_, _, err := origMsg.EditCaption(bot, &gotgbot.EditMessageCaptionOpts{
|
| 182 |
Caption: text,
|
| 183 |
ParseMode: gotgbot.ParseModeHTML,
|
|
|
|
| 189 |
return nil
|
| 190 |
}
|
| 191 |
|
| 192 |
+
// Standard text message edit
|
| 193 |
+
if isMsg {
|
| 194 |
+
_, _, err := origMsg.EditText(bot, text, &gotgbot.EditMessageTextOpts{
|
| 195 |
+
ParseMode: gotgbot.ParseModeHTML,
|
| 196 |
+
LinkPreviewOptions: &gotgbot.LinkPreviewOptions{IsDisabled: true},
|
| 197 |
+
ReplyMarkup: editMarkup,
|
| 198 |
+
})
|
| 199 |
+
if err != nil {
|
| 200 |
+
fmt.Printf("cbedit: %v\n", err)
|
| 201 |
+
}
|
| 202 |
+
return nil
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
// Fallback: use raw bot API with chat/message IDs
|
| 206 |
+
chatID := update.CallbackQuery.Message.GetChat().Id
|
| 207 |
+
msgID := update.CallbackQuery.Message.GetMessageId()
|
| 208 |
+
_, _, err := bot.EditMessageText(text, &gotgbot.EditMessageTextOpts{
|
| 209 |
+
ChatId: chatID,
|
| 210 |
+
MessageId: msgID,
|
| 211 |
+
ParseMode: gotgbot.ParseModeHTML,
|
| 212 |
+
LinkPreviewOptions: &gotgbot.LinkPreviewOptions{IsDisabled: true},
|
| 213 |
+
ReplyMarkup: editMarkup,
|
| 214 |
})
|
| 215 |
if err != nil {
|
| 216 |
+
fmt.Printf("cbedit(fallback): %v\n", err)
|
| 217 |
}
|
|
|
|
| 218 |
return nil
|
| 219 |
}
|
| 220 |
|
plugins/bulkindex.go
CHANGED
|
@@ -206,7 +206,8 @@ func runBulkIndex(bot *gotgbot.Bot, chanID, fromID, toID int64, statusMsg *gotgb
|
|
| 206 |
Length: len(keyword),
|
| 207 |
}
|
| 208 |
|
| 209 |
-
|
|
|
|
| 210 |
skipped++
|
| 211 |
} else {
|
| 212 |
indexed++
|
|
|
|
| 206 |
Length: len(keyword),
|
| 207 |
}
|
| 208 |
|
| 209 |
+
inserted, err := DB.SaveMfilterByFileID(f)
|
| 210 |
+
if err != nil || !inserted {
|
| 211 |
skipped++
|
| 212 |
} else {
|
| 213 |
indexed++
|
plugins/filter.go
CHANGED
|
@@ -1,4 +1,6 @@
|
|
| 1 |
-
//
|
|
|
|
|
|
|
| 2 |
|
| 3 |
package plugins
|
| 4 |
|
|
@@ -6,726 +8,399 @@ import (
|
|
| 6 |
"context"
|
| 7 |
"fmt"
|
| 8 |
"regexp"
|
| 9 |
-
"strconv"
|
| 10 |
"strings"
|
| 11 |
-
"time"
|
| 12 |
|
| 13 |
"github.com/Jisin0/Go-Filter-Bot/database"
|
| 14 |
-
"github.com/Jisin0/Go-Filter-Bot/utils"
|
| 15 |
"github.com/Jisin0/Go-Filter-Bot/utils/autodelete"
|
| 16 |
"github.com/Jisin0/Go-Filter-Bot/utils/customfilters"
|
| 17 |
-
"github.com/Jisin0/Go-Filter-Bot/utils/shortener"
|
| 18 |
-
"github.com/PaulSonOfLars/gotgbot/v2"
|
| 19 |
-
"github.com/PaulSonOfLars/gotgbot/v2/ext"
|
| 20 |
"go.mongodb.org/mongo-driver/bson"
|
| 21 |
-
|
| 22 |
|
| 23 |
-
//
|
| 24 |
-
|
| 25 |
-
buttonRegex *regexp.Regexp = regexp.MustCompile(`\[([^\[]+?)\]\((buttonurl|url|alert):(?:/{0,2})(.+?)\)`)
|
| 26 |
-
parseRegex *regexp.Regexp = regexp.MustCompile(`^"([^"]+)"`)
|
| 27 |
-
cbstopRegex *regexp.Regexp = regexp.MustCompile(`stopf\((.+)\)`)
|
| 28 |
-
cbalertRegex *regexp.Regexp = regexp.MustCompile(`alert\((.+)\)`)
|
| 29 |
-
cbfileRegex *regexp.Regexp = regexp.MustCompile(`file\((.+)\)`)
|
| 30 |
-
usernameRegex *regexp.Regexp = regexp.MustCompile(`(?i)@[a-z0-9_]+(?:\s|-)*`)
|
| 31 |
)
|
| 32 |
|
| 33 |
-
|
|
|
|
| 34 |
|
| 35 |
const (
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
maxKeyLength = 20 // maximum length for a keyword of a filter
|
| 39 |
-
|
| 40 |
-
filterSplitCount = 3 // number of subtrings into which input of /filter command should be split
|
| 41 |
-
minButtonParseParams = 4 // Parse of a button should yield atleast this many values
|
| 42 |
-
|
| 43 |
-
alertCacheDuration = 3000 // number of seconds an alert should be cache by the client
|
| 44 |
)
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
| 49 |
)
|
| 50 |
|
| 51 |
-
//
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
| 57 |
}
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
//
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
if
|
| 67 |
-
|
| 68 |
-
} else {
|
| 69 |
-
cacheKey = database.FilterCacheKey(chatID, []string{message})
|
| 70 |
}
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
return
|
| 74 |
}
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
results = DB.SearchMfilterClassic(chatID, message)
|
| 81 |
}
|
| 82 |
-
|
| 83 |
-
database.SetCachedFilterResults(cacheKey, results)
|
| 84 |
-
return results
|
| 85 |
}
|
| 86 |
|
| 87 |
-
//
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
if name == "" {
|
| 94 |
-
name = strings.SplitN(f.Content, "\n", 2)[0]
|
| 95 |
-
name = usernameRegex.ReplaceAllString(name, "")
|
| 96 |
-
name = strings.TrimSpace(name)
|
| 97 |
-
}
|
| 98 |
-
if name == "" {
|
| 99 |
-
name = f.Text
|
| 100 |
-
}
|
| 101 |
-
// Strip HTML tags for button display
|
| 102 |
-
name = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(name, "")
|
| 103 |
-
name = strings.TrimSpace(name)
|
| 104 |
-
if len(name) > 58 {
|
| 105 |
-
name = name[:55] + "..."
|
| 106 |
}
|
| 107 |
-
return name
|
| 108 |
-
}
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
}
|
| 114 |
|
| 115 |
-
|
|
|
|
|
|
|
| 116 |
|
| 117 |
-
|
| 118 |
-
if
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
query = results[0].Text
|
| 122 |
-
}
|
| 123 |
-
sendVJResults(results, bot, update, query, chatID, messageID)
|
| 124 |
-
return
|
| 125 |
}
|
|
|
|
| 126 |
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
Text: "📁 " + btnText,
|
| 133 |
-
CallbackData: fmt.Sprintf("file(%s)", f.ID),
|
| 134 |
-
}})
|
| 135 |
-
}
|
| 136 |
-
|
| 137 |
-
text := fmt.Sprintf("🔍 <b>Results found: %d</b>\n<i>Click a button to get the file sent to you.</i>", len(results))
|
| 138 |
-
m, err := update.Reply(bot, text, &gotgbot.SendMessageOpts{
|
| 139 |
-
ParseMode: gotgbot.ParseModeHTML,
|
| 140 |
-
ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: rows},
|
| 141 |
-
ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID, AllowSendingWithoutReply: true},
|
| 142 |
-
})
|
| 143 |
-
if err == nil && m != nil && s.AutoDelete > 0 {
|
| 144 |
-
autodelete.InsertAutodel(autodelete.AutodelData{ChatID: chatID, MessageID: m.MessageId}, s.AutoDelete*60)
|
| 145 |
-
}
|
| 146 |
-
} else {
|
| 147 |
-
botUser := bot.User.Username
|
| 148 |
-
text := fmt.Sprintf("🔍 <b>Results found: %d</b>\n\n", len(results))
|
| 149 |
-
for i, f := range results {
|
| 150 |
-
btnText := fileDisplayName(f)
|
| 151 |
-
link := fmt.Sprintf("https://t.me/%s?start=file_%s", botUser, f.ID)
|
| 152 |
-
text += fmt.Sprintf("%d. <a href='%s'>%s</a>\n", i+1, link, btnText)
|
| 153 |
-
}
|
| 154 |
-
text += "\n⚠️ <i>Links open in PM — make sure you've started the bot!</i>"
|
| 155 |
-
|
| 156 |
-
m, err := update.Reply(bot, text, &gotgbot.SendMessageOpts{
|
| 157 |
-
ParseMode: gotgbot.ParseModeHTML,
|
| 158 |
-
ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID, AllowSendingWithoutReply: true},
|
| 159 |
-
LinkPreviewOptions: &gotgbot.LinkPreviewOptions{IsDisabled: true},
|
| 160 |
-
})
|
| 161 |
-
if err == nil && m != nil && s.AutoDelete > 0 {
|
| 162 |
-
autodelete.InsertAutodel(autodelete.AutodelData{ChatID: chatID, MessageID: m.MessageId}, s.AutoDelete*60)
|
| 163 |
}
|
| 164 |
}
|
|
|
|
| 165 |
}
|
| 166 |
-
func MFilter(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 167 |
-
var (
|
| 168 |
-
chatID int64
|
| 169 |
-
update = ctx.Message
|
| 170 |
-
messageID = update.MessageId
|
| 171 |
-
chatType = update.Chat.Type
|
| 172 |
-
message = update.Text
|
| 173 |
-
)
|
| 174 |
-
|
| 175 |
-
if update.Caption != "" {
|
| 176 |
-
message = update.Caption
|
| 177 |
-
}
|
| 178 |
-
|
| 179 |
-
switch chatType {
|
| 180 |
-
case gotgbot.ChatTypePrivate:
|
| 181 |
-
var ok bool
|
| 182 |
-
|
| 183 |
-
chatID, ok = DB.GetConnection(update.Chat.Id)
|
| 184 |
-
if !ok {
|
| 185 |
-
return nil
|
| 186 |
-
}
|
| 187 |
-
case gotgbot.ChatTypeSupergroup, gotgbot.ChatTypeGroup:
|
| 188 |
-
chatID = update.Chat.Id
|
| 189 |
-
default:
|
| 190 |
-
return nil
|
| 191 |
-
}
|
| 192 |
-
|
| 193 |
-
fields := strings.Fields(message)
|
| 194 |
-
s := database.GetBotSettings()
|
| 195 |
-
results := searchWithCache(chatID, fields, message, s.MultiFilter)
|
| 196 |
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
}
|
| 203 |
-
|
| 204 |
-
return
|
| 205 |
}
|
| 206 |
|
| 207 |
-
//
|
| 208 |
-
//
|
| 209 |
-
//nolint:errcheck // too many
|
| 210 |
-
func NewFilter(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 211 |
-
var (
|
| 212 |
-
c int64
|
| 213 |
-
update = ctx.Message
|
| 214 |
-
)
|
| 215 |
-
|
| 216 |
-
// I didnt wanna create a whole new function for gfilter so ...
|
| 217 |
-
if strings.HasPrefix(update.Text, "/gfilter") {
|
| 218 |
-
if !utils.IsAdmin(ctx.EffectiveUser.Id) {
|
| 219 |
-
update.Reply(bot, "Thats an Admin-only Command :(", &gotgbot.SendMessageOpts{})
|
| 220 |
-
return nil
|
| 221 |
-
}
|
| 222 |
-
|
| 223 |
-
c = globalNumber
|
| 224 |
-
} else {
|
| 225 |
-
// Verifying and getting connections for private chats
|
| 226 |
-
var v bool
|
| 227 |
-
|
| 228 |
-
c, v = customfilters.Verify(bot, ctx)
|
| 229 |
-
if !v {
|
| 230 |
-
return nil
|
| 231 |
-
} else if c == 0 {
|
| 232 |
-
c = ctx.Message.Chat.Id
|
| 233 |
-
}
|
| 234 |
-
}
|
| 235 |
-
|
| 236 |
-
args := strings.SplitN(update.Text, " ", filterSplitCount)
|
| 237 |
-
if len(args) < 2 && (update.ReplyToMessage == nil && len(args) < filterSplitCount) {
|
| 238 |
-
update.Reply(
|
| 239 |
-
bot,
|
| 240 |
-
"Not Enough Parameters :(\n\nExample:- <code>/filter hi hello</code>",
|
| 241 |
-
&gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML},
|
| 242 |
-
)
|
| 243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
return nil
|
| 245 |
}
|
| 246 |
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
|
| 251 |
-
|
| 252 |
-
|
|
|
|
| 253 |
}
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
if e.Err() == nil {
|
| 257 |
-
update.Reply(
|
| 258 |
-
bot,
|
| 259 |
-
fmt.Sprintf("It Looks Like Another Filter For <code>%v</code> Has Already Been Saved In This Chat, If You Want To Stop It First Use The Button Below", key),
|
| 260 |
-
&gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
|
| 261 |
-
{{Text: "Stop Filter", CallbackData: fmt.Sprintf("stopf(%v|%v|y)", key, "local")}},
|
| 262 |
-
}}},
|
| 263 |
-
)
|
| 264 |
-
|
| 265 |
return nil
|
| 266 |
}
|
| 267 |
|
| 268 |
-
|
| 269 |
-
text = parse[1]
|
| 270 |
-
button [][]map[string]string
|
| 271 |
-
)
|
| 272 |
-
|
| 273 |
-
if update.ReplyToMessage != nil {
|
| 274 |
-
text += update.ReplyToMessage.OriginalHTML()
|
| 275 |
-
text += update.ReplyToMessage.OriginalCaptionHTML()
|
| 276 |
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
|
|
|
| 280 |
}
|
| 281 |
}
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
fileID string
|
| 290 |
-
mediaType string
|
| 291 |
-
)
|
| 292 |
|
| 293 |
-
if
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
fileID = msg.Sticker.FileId
|
| 306 |
-
mediaType = "sticker"
|
| 307 |
-
case msg.Animation != nil:
|
| 308 |
-
fileID = msg.Animation.FileId
|
| 309 |
-
mediaType = "animation"
|
| 310 |
-
case msg.Photo != nil:
|
| 311 |
-
fileID = msg.Photo[len(msg.Photo)-1].FileId
|
| 312 |
-
mediaType = "photo"
|
| 313 |
}
|
| 314 |
-
}
|
| 315 |
-
|
| 316 |
-
f := &database.Filter{
|
| 317 |
-
ID: uniqueID,
|
| 318 |
-
ChatID: c,
|
| 319 |
-
Text: key,
|
| 320 |
-
Content: text,
|
| 321 |
-
FileID: fileID,
|
| 322 |
-
Markup: button,
|
| 323 |
-
Alerts: alert,
|
| 324 |
-
Length: len(key),
|
| 325 |
-
MediaType: mediaType,
|
| 326 |
-
}
|
| 327 |
-
|
| 328 |
-
DB.SaveMfilter(f)
|
| 329 |
-
|
| 330 |
-
_, err := update.Reply(
|
| 331 |
-
bot,
|
| 332 |
-
fmt.Sprintf("<i>Successfully Saved A Manual Filter For <code>%v</code> !</i>", key),
|
| 333 |
-
&gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML},
|
| 334 |
-
)
|
| 335 |
-
if err != nil {
|
| 336 |
-
fmt.Println(err)
|
| 337 |
-
}
|
| 338 |
-
|
| 339 |
-
return nil
|
| 340 |
-
}
|
| 341 |
-
|
| 342 |
-
func CbStop(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 343 |
-
// Function to handle callbacks from confirm buttons when stopping a filter
|
| 344 |
-
update := ctx.CallbackQuery
|
| 345 |
-
|
| 346 |
-
c, v := customfilters.Verify(bot, ctx)
|
| 347 |
-
if !v {
|
| 348 |
return nil
|
| 349 |
-
} else if c == 0 {
|
| 350 |
-
c = update.Message.GetChat().Id
|
| 351 |
}
|
| 352 |
|
| 353 |
-
|
| 354 |
-
args := strings.Split(cbstopRegex.FindStringSubmatch(update.Data)[1], "|")
|
| 355 |
-
if len(args) < cbStopParamCount {
|
| 356 |
-
update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Bad Button :(", ShowAlert: true})
|
| 357 |
-
return nil
|
| 358 |
-
}
|
| 359 |
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
erase = args[2]
|
| 364 |
-
)
|
| 365 |
-
|
| 366 |
-
if ftype == "local" {
|
| 367 |
-
if erase == "y" {
|
| 368 |
-
DB.DeleteMfilter(c, key)
|
| 369 |
-
update.Message.EditText(bot, fmt.Sprintf("Manual Filter For <code>%v</code> Was Deleted Successfully !", key), &gotgbot.EditMessageTextOpts{ParseMode: gotgbot.ParseModeHTML})
|
| 370 |
-
} else if erase == "n" {
|
| 371 |
-
update.Message.EditText(bot, fmt.Sprintf(`Are You Sure You Want To Permanently Delete The Manual Filter For %v ?\nClick The "Yes I'm Sure" Button To Confirm `, key), &gotgbot.EditMessageTextOpts{ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{{{Text: "Ignore", CallbackData: "close"}, {Text: "Yes I'm Sure", CallbackData: fmt.Sprintf("stopf(%v|local|y)", key)}}}}, ParseMode: gotgbot.ParseModeHTML})
|
| 372 |
-
}
|
| 373 |
-
} else if ftype == "global" {
|
| 374 |
-
DB.StopGfilter(c, key)
|
| 375 |
-
update.Message.EditText(bot, fmt.Sprintf("Global Filter For <code>%v</code> Has Been Stopped Successfully !", key), &gotgbot.EditMessageTextOpts{ParseMode: gotgbot.ParseModeHTML})
|
| 376 |
}
|
| 377 |
-
|
| 378 |
return nil
|
| 379 |
}
|
| 380 |
|
| 381 |
-
//
|
| 382 |
-
//
|
| 383 |
-
//nolint:errcheck // too many
|
| 384 |
-
func StopMfilter(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 385 |
-
c, v := customfilters.Verify(bot, ctx)
|
| 386 |
-
if !v {
|
| 387 |
-
return nil
|
| 388 |
-
} else if c == 0 {
|
| 389 |
-
c = ctx.Message.Chat.Id
|
| 390 |
-
}
|
| 391 |
-
|
| 392 |
-
var (
|
| 393 |
-
update = ctx.Message
|
| 394 |
-
split = strings.SplitN(update.Text, " ", 2)
|
| 395 |
-
key string
|
| 396 |
-
)
|
| 397 |
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
key = m.Text
|
| 406 |
-
case len(split) < 2:
|
| 407 |
-
update.Reply(bot, "Whoops looks like you forgot to mention a filter to stop !", &gotgbot.SendMessageOpts{})
|
| 408 |
-
default:
|
| 409 |
-
key = split[1]
|
| 410 |
}
|
| 411 |
|
| 412 |
-
//
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
if !k && !ok {
|
| 418 |
-
update.Reply(bot, fmt.Sprintf("I Couldnt Find Any Filter For <code>%v</code> To Stop !", key), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
|
| 419 |
-
return nil
|
| 420 |
}
|
| 421 |
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
case k && ok:
|
| 425 |
-
markup := gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{{{Text: "Local", CallbackData: fmt.Sprintf("stopf(%v|local|y)", key)}, {Text: "Global", CallbackData: fmt.Sprintf("stopf(%v|global|y)", key)}}}}
|
| 426 |
-
|
| 427 |
-
_, err := update.Reply(bot, "Please Select If You Would Like To Stop The Manual Filter (which you saved) Or Global Filter (saved by owners) For <code>"+key+"</code>", &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: markup})
|
| 428 |
-
if err != nil {
|
| 429 |
-
fmt.Println(err)
|
| 430 |
-
}
|
| 431 |
-
case k:
|
| 432 |
-
// Only local
|
| 433 |
-
markup := gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{{{Text: "CONFIRM", CallbackData: fmt.Sprintf("stopf(%v|local|y)", key)}}}}
|
| 434 |
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
fmt.Println(err)
|
| 438 |
-
}
|
| 439 |
-
case ok:
|
| 440 |
-
// Only global
|
| 441 |
-
markup := gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{{{Text: "CONFIRM", CallbackData: fmt.Sprintf("stopf(%v|global|y)", key)}}}}
|
| 442 |
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
|
|
|
|
|
|
| 446 |
}
|
| 447 |
}
|
| 448 |
|
| 449 |
-
|
| 450 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
|
| 452 |
-
|
| 453 |
-
// Function to handle the /filters command
|
| 454 |
-
update := ctx.Message
|
| 455 |
-
c := update.Chat.Id
|
| 456 |
|
| 457 |
-
if
|
| 458 |
-
|
| 459 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
}
|
| 462 |
|
| 463 |
-
text := DB.StringMfilter(c)
|
| 464 |
-
|
| 465 |
-
_, err := update.Reply(bot, "Lɪsᴛ ᴏғ ғɪʟᴛᴇʀs ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ :\n"+text, &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
|
| 466 |
if err != nil {
|
| 467 |
-
fmt.Printf("
|
|
|
|
| 468 |
}
|
| 469 |
|
| 470 |
-
|
| 471 |
-
}
|
| 472 |
-
|
| 473 |
-
// Function to handle callbacks from alert button in saved filters
|
| 474 |
-
//
|
| 475 |
-
//nolint:errcheck // unnecessary
|
| 476 |
-
func CbAlert(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 477 |
-
update := ctx.CallbackQuery
|
| 478 |
-
|
| 479 |
-
args := strings.Split(cbalertRegex.FindStringSubmatch(update.Data)[1], "|")
|
| 480 |
-
if len(args) < 2 {
|
| 481 |
-
update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Bad Request !", ShowAlert: true})
|
| 482 |
-
} else {
|
| 483 |
-
uniqueID := args[0]
|
| 484 |
-
|
| 485 |
-
index, err := strconv.Atoi(args[1])
|
| 486 |
-
if err != nil {
|
| 487 |
-
update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Bad Request !", ShowAlert: true})
|
| 488 |
-
} else {
|
| 489 |
-
text := DB.GetAlert(uniqueID, index)
|
| 490 |
-
update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: text, ShowAlert: true, CacheTime: alertCacheDuration})
|
| 491 |
-
}
|
| 492 |
}
|
| 493 |
-
|
| 494 |
-
return nil
|
| 495 |
}
|
| 496 |
|
| 497 |
-
//
|
| 498 |
-
func
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
}
|
| 505 |
}
|
| 506 |
|
|
|
|
|
|
|
| 507 |
// CbSendFile handles inline button clicks to send a specific file.
|
|
|
|
| 508 |
//nolint:errcheck
|
| 509 |
func CbSendFile(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 510 |
update := ctx.CallbackQuery
|
| 511 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
return nil
|
| 513 |
}
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
|
|
|
| 518 |
return nil
|
| 519 |
}
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
msg, ok := update.Message.(*gotgbot.Message)
|
| 526 |
-
if !ok {
|
| 527 |
return nil
|
| 528 |
}
|
| 529 |
-
|
| 530 |
-
SendFileByID(fileID, bot, msg)
|
| 531 |
-
|
| 532 |
-
return nil
|
| 533 |
-
}
|
| 534 |
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
rowButtons []map[string]string
|
| 540 |
-
)
|
| 541 |
-
|
| 542 |
-
for _, i := range btn {
|
| 543 |
-
rowButtons = []map[string]string{}
|
| 544 |
-
|
| 545 |
-
for _, j := range i {
|
| 546 |
-
b := map[string]string{"Text": j.Text}
|
| 547 |
-
|
| 548 |
-
switch {
|
| 549 |
-
case j.CallbackData != "":
|
| 550 |
-
b["CallbackData"] = j.CallbackData
|
| 551 |
-
case j.Url != "":
|
| 552 |
-
b["Url"] = j.Url
|
| 553 |
-
default:
|
| 554 |
-
continue
|
| 555 |
-
}
|
| 556 |
-
|
| 557 |
-
rowButtons = append(rowButtons, b)
|
| 558 |
-
}
|
| 559 |
-
|
| 560 |
-
totalButtons = append(totalButtons, rowButtons)
|
| 561 |
}
|
| 562 |
|
| 563 |
-
|
|
|
|
|
|
|
| 564 |
}
|
| 565 |
|
| 566 |
-
|
| 567 |
-
// Convert a map back into button
|
| 568 |
-
var totalButtons [][]gotgbot.InlineKeyboardButton
|
| 569 |
-
|
| 570 |
-
for _, i := range data {
|
| 571 |
-
var rowButtons []gotgbot.InlineKeyboardButton
|
| 572 |
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
rowButtons = append(rowButtons, gotgbot.InlineKeyboardButton{Text: text, CallbackData: c})
|
| 580 |
-
}
|
| 581 |
-
}
|
| 582 |
-
|
| 583 |
-
totalButtons = append(totalButtons, rowButtons)
|
| 584 |
}
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
func ParseQuotes(text string) []string {
|
| 590 |
-
res := parseRegex.FindStringSubmatch(text)
|
| 591 |
-
if len(res) > 0 {
|
| 592 |
-
return []string{res[1], strings.Replace(text, "\""+res[1]+"\"", "", 1)} // very hacky workaround by adding quotes //TODO: update regex to include quotes
|
| 593 |
-
} else {
|
| 594 |
-
split := strings.SplitN(text, " ", 2)
|
| 595 |
-
return []string{split[0], strings.Replace(text, split[0], "", 1)}
|
| 596 |
}
|
|
|
|
| 597 |
}
|
| 598 |
|
| 599 |
-
|
| 600 |
-
var (
|
| 601 |
-
returnText = text
|
| 602 |
-
rowButtons []map[string]string
|
| 603 |
-
alert []string
|
| 604 |
-
)
|
| 605 |
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
}
|
| 618 |
-
|
| 619 |
-
returnText = strings.Replace(returnText, m[0], "", 1)
|
| 620 |
}
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
totalButtons = append(totalButtons, rowButtons)
|
| 624 |
}
|
| 625 |
-
|
| 626 |
-
rowButtons = []map[string]string{}
|
| 627 |
}
|
| 628 |
-
|
| 629 |
-
return strings.Trim(returnText, " "), totalButtons, alert
|
| 630 |
}
|
| 631 |
|
| 632 |
-
//
|
| 633 |
-
//
|
| 634 |
-
func
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
}
|
| 641 |
-
|
| 642 |
-
content := f.Content
|
| 643 |
-
// Strip usernames from the caption
|
| 644 |
-
content = usernameRegex.ReplaceAllString(content, "")
|
| 645 |
-
|
| 646 |
-
var (
|
| 647 |
-
buttons = mapToButton(markup)
|
| 648 |
-
ikm = gotgbot.InlineKeyboardMarkup{InlineKeyboard: buttons}
|
| 649 |
-
err error
|
| 650 |
-
m *gotgbot.Message
|
| 651 |
-
)
|
| 652 |
-
|
| 653 |
-
mediaType := f.MediaType
|
| 654 |
-
if mediaType != "" {
|
| 655 |
-
fileID := gotgbot.InputFileByID(f.FileID)
|
| 656 |
-
|
| 657 |
-
switch mediaType {
|
| 658 |
-
case "document":
|
| 659 |
-
m, err = bot.SendDocument(chatID, fileID, &gotgbot.SendDocumentOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 660 |
-
case "sticker":
|
| 661 |
-
m, err = bot.SendSticker(chatID, fileID, &gotgbot.SendStickerOpts{ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: ikm})
|
| 662 |
-
case "video":
|
| 663 |
-
m, err = bot.SendVideo(chatID, fileID, &gotgbot.SendVideoOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 664 |
-
case "photo":
|
| 665 |
-
m, err = bot.SendPhoto(chatID, fileID, &gotgbot.SendPhotoOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 666 |
-
case "audio":
|
| 667 |
-
m, err = bot.SendAudio(chatID, fileID, &gotgbot.SendAudioOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 668 |
-
case "animation":
|
| 669 |
-
m, err = bot.SendAnimation(chatID, fileID, &gotgbot.SendAnimationOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 670 |
-
default:
|
| 671 |
-
fmt.Println("Unknown media type " + mediaType)
|
| 672 |
-
}
|
| 673 |
-
} else {
|
| 674 |
-
m, err = update.Reply(bot, content, &gotgbot.SendMessageOpts{ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 675 |
-
}
|
| 676 |
-
|
| 677 |
-
if err != nil {
|
| 678 |
-
fmt.Println(err)
|
| 679 |
-
fmt.Println(f)
|
| 680 |
-
return
|
| 681 |
}
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
err := autodelete.InsertAutodel(autodelete.AutodelData{ChatID: chatID, MessageID: m.MessageId}, s.AutoDelete*60)
|
| 685 |
-
if err != nil {
|
| 686 |
-
fmt.Printf("sendfilter: %v\n", err)
|
| 687 |
-
}
|
| 688 |
}
|
| 689 |
-
|
|
|
|
|
|
|
|
|
|
| 690 |
|
| 691 |
-
//
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
func sendNoResultsSuggestion(bot *gotgbot.Bot, update *gotgbot.Message, query string, messageID int64) {
|
| 696 |
-
// Only trigger for messages that look like search queries (3+ chars, not commands)
|
| 697 |
-
query = strings.TrimSpace(query)
|
| 698 |
-
if len(query) < 3 || strings.HasPrefix(query, "/") {
|
| 699 |
-
return
|
| 700 |
}
|
| 701 |
-
// Limit to reasonable length
|
| 702 |
-
if len(strings.Fields(query)) > 8 {
|
| 703 |
-
return
|
| 704 |
-
}
|
| 705 |
-
|
| 706 |
-
googleQuery := "https://www.google.com/search?q=" + strings.ReplaceAll(query, " ", "+") + "+telegram+movie+bot"
|
| 707 |
-
|
| 708 |
-
text := fmt.Sprintf(
|
| 709 |
-
"❌ <b>No results found for:</b> <code>%s</code>\n\n"+
|
| 710 |
-
"💡 <i>Check your spelling and try again, or search on Google.</i>",
|
| 711 |
-
query,
|
| 712 |
-
)
|
| 713 |
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
|
| 718 |
-
{{Text: "🔍 Search on Google", Url: googleQuery}},
|
| 719 |
-
}},
|
| 720 |
-
ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID},
|
| 721 |
-
})
|
| 722 |
-
if err == nil && m != nil && s.AutoDelete > 0 {
|
| 723 |
-
autodelete.InsertAutodel(autodelete.AutodelData{ChatID: update.Chat.Id, MessageID: m.MessageId}, s.AutoDelete*60)
|
| 724 |
}
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
// CbPages handles the no-op "pages" callback (page number display button).
|
| 728 |
-
func CbPages(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 729 |
-
ctx.CallbackQuery.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{}) //nolint:errcheck
|
| 730 |
-
return nil
|
| 731 |
}
|
|
|
|
| 1 |
+
// Core auto-filter and search result logic.
|
| 2 |
+
// Search algorithm ported from VJ-FILTER-BOT (Python/Pyrogram) to Go.
|
| 3 |
+
// Credit: Bot code made by Claude
|
| 4 |
|
| 5 |
package plugins
|
| 6 |
|
|
|
|
| 8 |
"context"
|
| 9 |
"fmt"
|
| 10 |
"regexp"
|
|
|
|
| 11 |
"strings"
|
|
|
|
| 12 |
|
| 13 |
"github.com/Jisin0/Go-Filter-Bot/database"
|
|
|
|
| 14 |
"github.com/Jisin0/Go-Filter-Bot/utils/autodelete"
|
| 15 |
"github.com/Jisin0/Go-Filter-Bot/utils/customfilters"
|
|
|
|
|
|
|
|
|
|
| 16 |
"go.mongodb.org/mongo-driver/bson"
|
| 17 |
+
"go.mongodb.org/mongo-driver/mongo/options"
|
| 18 |
|
| 19 |
+
"github.com/PaulSonOfLars/gotgbot/v2"
|
| 20 |
+
"github.com/PaulSonOfLars/gotgbot/v2/ext"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
)
|
| 22 |
|
| 23 |
+
// DB is the shared database instance from customfilters package.
|
| 24 |
+
var DB = customfilters.DB
|
| 25 |
|
| 26 |
const (
|
| 27 |
+
globalNumber = 101
|
| 28 |
+
lenUniqueID = 15
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
)
|
| 30 |
|
| 31 |
+
// VJ-style search: removes noise words from query before searching
|
| 32 |
+
// Ported from auto_filter() in VJ-FILTER-BOT/plugins/pm_filter.py
|
| 33 |
+
var (
|
| 34 |
+
usernameRegex = regexp.MustCompile(`(?i)@\w+`)
|
| 35 |
+
noiseWords = regexp.MustCompile(`(?i)\b(please|pls|plz|send|movie|new|latest|bro|bruh|find|link|file|dubbed|full|helo|that|undo|kitti|thar|kittum|anyone|with subtitle[s]?)\b`)
|
| 36 |
)
|
| 37 |
|
| 38 |
+
// vjCleanQuery replicates VJ bot's search query cleaning logic.
|
| 39 |
+
// Removes noise words, normalises spacing — improves search hit ratio.
|
| 40 |
+
func vjCleanQuery(raw string) string {
|
| 41 |
+
q := strings.ToLower(raw)
|
| 42 |
+
q = noiseWords.ReplaceAllString(q, " ")
|
| 43 |
+
q = regexp.MustCompile(`[-:.]`).ReplaceAllString(q, " ")
|
| 44 |
+
q = regexp.MustCompile(`\s{2,}`).ReplaceAllString(q, " ")
|
| 45 |
+
return strings.TrimSpace(q)
|
| 46 |
}
|
| 47 |
|
| 48 |
+
// buildVJRegex builds a MongoDB regex pattern matching VJ-FILTER-BOT logic:
|
| 49 |
+
// - single word → word-boundary match
|
| 50 |
+
// - multi word → words connected by wildcard separators (space/dot/dash/+)
|
| 51 |
+
//
|
| 52 |
+
// Source: get_search_results() in VJ-FILTER-BOT/database/ia_filterdb.py
|
| 53 |
+
func buildVJRegex(query string) string {
|
| 54 |
+
query = strings.TrimSpace(query)
|
| 55 |
+
if query == "" {
|
| 56 |
+
return "."
|
|
|
|
|
|
|
| 57 |
}
|
| 58 |
+
if !strings.Contains(query, " ") {
|
| 59 |
+
// Single word: match at word boundary or file-name separator
|
| 60 |
+
return `(\b|[.\+\-_])` + regexp.QuoteMeta(query) + `(\b|[.\+\-_])`
|
| 61 |
}
|
| 62 |
+
// Multi-word: words connected by any separator (VJ exact logic)
|
| 63 |
+
parts := strings.Fields(query)
|
| 64 |
+
escaped := make([]string, len(parts))
|
| 65 |
+
for i, p := range parts {
|
| 66 |
+
escaped[i] = regexp.QuoteMeta(p)
|
|
|
|
| 67 |
}
|
| 68 |
+
return strings.Join(escaped, `.*[\s\.\+\-_]`)
|
|
|
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
+
// SearchVJ searches the file_name field using VJ-style regex — the primary hit driver.
|
| 72 |
+
// Falls back to text field for older manually-indexed docs.
|
| 73 |
+
func SearchVJ(chatID int64, query string, limit int64) []*database.Filter {
|
| 74 |
+
query = vjCleanQuery(query)
|
| 75 |
+
if query == "" {
|
| 76 |
+
return nil
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
}
|
|
|
|
|
|
|
| 78 |
|
| 79 |
+
pattern := buildVJRegex(query)
|
| 80 |
+
mongoFilter := bson.D{
|
| 81 |
+
{Key: "group_id", Value: chatID},
|
| 82 |
+
{Key: "$or", Value: bson.A{
|
| 83 |
+
bson.D{{Key: "file_name", Value: bson.M{"$regex": pattern, "$options": "i"}}},
|
| 84 |
+
bson.D{{Key: "text", Value: bson.M{"$regex": pattern, "$options": "i"}}},
|
| 85 |
+
}},
|
| 86 |
+
// Exclude ghost reference docs
|
| 87 |
+
{Key: "file", Value: bson.M{"$exists": true, "$ne": ""}},
|
| 88 |
}
|
| 89 |
|
| 90 |
+
opts := options.Find().
|
| 91 |
+
SetSort(bson.D{{Key: "$natural", Value: -1}}). // newest first — matches VJ sort
|
| 92 |
+
SetLimit(limit)
|
| 93 |
|
| 94 |
+
cur, err := DB.Mcol.Find(context.Background(), mongoFilter, opts)
|
| 95 |
+
if err != nil {
|
| 96 |
+
fmt.Printf("SearchVJ: %v\n", err)
|
| 97 |
+
return nil
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
}
|
| 99 |
+
defer cur.Close(context.Background())
|
| 100 |
|
| 101 |
+
var results []*database.Filter
|
| 102 |
+
for cur.Next(context.Background()) {
|
| 103 |
+
var f database.Filter
|
| 104 |
+
if err := cur.Decode(&f); err == nil {
|
| 105 |
+
results = append(results, &f)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
}
|
| 107 |
}
|
| 108 |
+
return results
|
| 109 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
+
// CountVJ returns total matching documents for pagination.
|
| 112 |
+
func CountVJ(chatID int64, query string) int64 {
|
| 113 |
+
query = vjCleanQuery(query)
|
| 114 |
+
if query == "" {
|
| 115 |
+
return 0
|
| 116 |
+
}
|
| 117 |
+
pattern := buildVJRegex(query)
|
| 118 |
+
mongoFilter := bson.D{
|
| 119 |
+
{Key: "group_id", Value: chatID},
|
| 120 |
+
{Key: "$or", Value: bson.A{
|
| 121 |
+
bson.D{{Key: "file_name", Value: bson.M{"$regex": pattern, "$options": "i"}}},
|
| 122 |
+
bson.D{{Key: "text", Value: bson.M{"$regex": pattern, "$options": "i"}}},
|
| 123 |
+
}},
|
| 124 |
+
{Key: "file", Value: bson.M{"$exists": true, "$ne": ""}},
|
| 125 |
}
|
| 126 |
+
count, _ := DB.Mcol.CountDocuments(context.Background(), mongoFilter)
|
| 127 |
+
return count
|
| 128 |
}
|
| 129 |
|
| 130 |
+
// ── Auto filter (group message handler) ──────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
+
// Filter is the main group message handler — the "give_filter" equivalent.
|
| 133 |
+
//
|
| 134 |
+
//nolint:errcheck
|
| 135 |
+
func Filter(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 136 |
+
msg := ctx.Message
|
| 137 |
+
if msg == nil {
|
| 138 |
return nil
|
| 139 |
}
|
| 140 |
|
| 141 |
+
chatID := msg.Chat.Id
|
| 142 |
+
messageID := msg.MessageId
|
| 143 |
+
query := strings.TrimSpace(msg.Text + msg.Caption)
|
| 144 |
|
| 145 |
+
// Ignore commands and very short queries
|
| 146 |
+
if strings.HasPrefix(query, "/") || strings.HasPrefix(query, "#") || len(query) < 2 {
|
| 147 |
+
return nil
|
| 148 |
}
|
| 149 |
+
// Ignore very long messages (not a movie search)
|
| 150 |
+
if len(query) > 100 {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
return nil
|
| 152 |
}
|
| 153 |
|
| 154 |
+
s := database.GetBotSettings()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
|
| 156 |
+
// Check force sub
|
| 157 |
+
if len(s.ForceSubChats) > 0 {
|
| 158 |
+
if !CheckForceSub(bot, ctx) {
|
| 159 |
+
return nil
|
| 160 |
}
|
| 161 |
}
|
| 162 |
|
| 163 |
+
// Send "Searching For..." indicator — VJ style
|
| 164 |
+
searching, _ := msg.Reply(bot,
|
| 165 |
+
fmt.Sprintf("<b><i>Searching For %s 🔍</i></b>", query),
|
| 166 |
+
&gotgbot.SendMessageOpts{
|
| 167 |
+
ParseMode: gotgbot.ParseModeHTML,
|
| 168 |
+
ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID, AllowSendingWithoutReply: true},
|
| 169 |
+
})
|
| 170 |
|
| 171 |
+
maxResults := int64(10)
|
| 172 |
+
results := SearchVJ(globalNumber, query, maxResults)
|
|
|
|
|
|
|
|
|
|
| 173 |
|
| 174 |
+
if len(results) == 0 {
|
| 175 |
+
// No results — show Google search suggestion
|
| 176 |
+
if searching != nil {
|
| 177 |
+
googleQuery := strings.ReplaceAll(query, " ", "+") + "+telegram+movie"
|
| 178 |
+
searching.EditText(bot,
|
| 179 |
+
fmt.Sprintf("❌ <b>No results found for:</b> <code>%s</code>\n\n💡 <i>Check spelling and try again.</i>", query),
|
| 180 |
+
&gotgbot.EditMessageTextOpts{
|
| 181 |
+
ParseMode: gotgbot.ParseModeHTML,
|
| 182 |
+
ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
|
| 183 |
+
{{Text: "🔍 Search on Google", Url: "https://www.google.com/search?q=" + googleQuery}},
|
| 184 |
+
}},
|
| 185 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
return nil
|
|
|
|
|
|
|
| 188 |
}
|
| 189 |
|
| 190 |
+
total := CountVJ(globalNumber, query)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
+
// Dispatch to VJ-style paginated results
|
| 193 |
+
if searching != nil {
|
| 194 |
+
searching.Delete(bot, nil) //nolint:errcheck
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
}
|
| 196 |
+
sendVJResults(results, bot, msg, query, chatID, messageID)
|
| 197 |
return nil
|
| 198 |
}
|
| 199 |
|
| 200 |
+
// ── File sender ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
+
// sendFilter sends a single filter result to the chat.
|
| 203 |
+
//
|
| 204 |
+
//nolint:errcheck
|
| 205 |
+
func sendFilter(f *database.Filter, bot *gotgbot.Bot, update *gotgbot.Message, chatID, messageID int64) {
|
| 206 |
+
if f == nil {
|
| 207 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
}
|
| 209 |
|
| 210 |
+
// Skip ghost reference docs
|
| 211 |
+
mediaType := f.MediaType
|
| 212 |
+
if mediaType == "reference" || (mediaType != "" && f.FileID == "") {
|
| 213 |
+
fmt.Printf("sendFilter: skipping ghost doc id=%s type=%s\n", f.ID, mediaType)
|
| 214 |
+
return
|
|
|
|
|
|
|
|
|
|
| 215 |
}
|
| 216 |
|
| 217 |
+
markup := f.Markup
|
| 218 |
+
s := database.GetBotSettings()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
|
| 220 |
+
content := f.Content
|
| 221 |
+
content = usernameRegex.ReplaceAllString(content, "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
|
| 223 |
+
// Build caption: use FileName if Content is empty
|
| 224 |
+
if content == "" && f.FileName != "" {
|
| 225 |
+
content = "<b>" + f.FileName + "</b>"
|
| 226 |
+
if f.FileSize > 0 {
|
| 227 |
+
content += fmt.Sprintf("\n📦 Size: %s", getFileSize(f.FileSize))
|
| 228 |
}
|
| 229 |
}
|
| 230 |
|
| 231 |
+
var (
|
| 232 |
+
buttons = mapToButton(markup)
|
| 233 |
+
ikm = gotgbot.InlineKeyboardMarkup{InlineKeyboard: buttons}
|
| 234 |
+
err error
|
| 235 |
+
m *gotgbot.Message
|
| 236 |
+
)
|
| 237 |
|
| 238 |
+
replyParams := &gotgbot.ReplyParameters{MessageId: messageID, AllowSendingWithoutReply: true}
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
+
if mediaType != "" {
|
| 241 |
+
fileID := gotgbot.InputFileByID(f.FileID)
|
| 242 |
+
switch mediaType {
|
| 243 |
+
case "document":
|
| 244 |
+
m, err = bot.SendDocument(chatID, fileID, &gotgbot.SendDocumentOpts{Caption: content, ReplyParameters: replyParams, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 245 |
+
case "video":
|
| 246 |
+
m, err = bot.SendVideo(chatID, fileID, &gotgbot.SendVideoOpts{Caption: content, ReplyParameters: replyParams, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 247 |
+
case "audio":
|
| 248 |
+
m, err = bot.SendAudio(chatID, fileID, &gotgbot.SendAudioOpts{Caption: content, ReplyParameters: replyParams, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 249 |
+
case "photo":
|
| 250 |
+
m, err = bot.SendPhoto(chatID, fileID, &gotgbot.SendPhotoOpts{Caption: content, ReplyParameters: replyParams, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 251 |
+
case "animation":
|
| 252 |
+
m, err = bot.SendAnimation(chatID, fileID, &gotgbot.SendAnimationOpts{Caption: content, ReplyParameters: replyParams, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 253 |
+
case "sticker":
|
| 254 |
+
m, err = bot.SendSticker(chatID, fileID, &gotgbot.SendStickerOpts{ReplyParameters: replyParams, ReplyMarkup: ikm})
|
| 255 |
+
default:
|
| 256 |
+
// Try as document for unknown media types
|
| 257 |
+
m, err = bot.SendDocument(chatID, fileID, &gotgbot.SendDocumentOpts{Caption: content, ReplyParameters: replyParams, ReplyMarkup: ikm, ParseMode: gotgbot.ParseModeHTML})
|
| 258 |
}
|
| 259 |
+
} else {
|
| 260 |
+
m, err = update.Reply(bot, content, &gotgbot.SendMessageOpts{
|
| 261 |
+
ReplyParameters: replyParams,
|
| 262 |
+
ReplyMarkup: ikm,
|
| 263 |
+
ParseMode: gotgbot.ParseModeHTML,
|
| 264 |
+
})
|
| 265 |
}
|
| 266 |
|
|
|
|
|
|
|
|
|
|
| 267 |
if err != nil {
|
| 268 |
+
fmt.Printf("sendFilter err: %v\n", err)
|
| 269 |
+
return
|
| 270 |
}
|
| 271 |
|
| 272 |
+
if m != nil && s.AutoDelete > 0 {
|
| 273 |
+
autodelete.InsertAutodel(autodelete.AutodelData{ChatID: chatID, MessageID: m.MessageId}, s.AutoDelete*60)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
}
|
|
|
|
|
|
|
| 275 |
}
|
| 276 |
|
| 277 |
+
// getFileSize returns human-readable file size.
|
| 278 |
+
func getFileSize(size int64) string {
|
| 279 |
+
switch {
|
| 280 |
+
case size >= 1024*1024*1024:
|
| 281 |
+
return fmt.Sprintf("%.1f GB", float64(size)/(1024*1024*1024))
|
| 282 |
+
case size >= 1024*1024:
|
| 283 |
+
return fmt.Sprintf("%.1f MB", float64(size)/(1024*1024))
|
| 284 |
+
case size >= 1024:
|
| 285 |
+
return fmt.Sprintf("%.1f KB", float64(size)/1024)
|
| 286 |
+
default:
|
| 287 |
+
return fmt.Sprintf("%d B", size)
|
| 288 |
}
|
| 289 |
}
|
| 290 |
|
| 291 |
+
// ── CbSendFile — inline button file delivery ─────────────────────────────────
|
| 292 |
+
|
| 293 |
// CbSendFile handles inline button clicks to send a specific file.
|
| 294 |
+
//
|
| 295 |
//nolint:errcheck
|
| 296 |
func CbSendFile(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 297 |
update := ctx.CallbackQuery
|
| 298 |
+
data := update.Data
|
| 299 |
+
|
| 300 |
+
var fileDocID string
|
| 301 |
+
if strings.HasPrefix(data, "file(") && strings.HasSuffix(data, ")") {
|
| 302 |
+
fileDocID = data[5 : len(data)-1]
|
| 303 |
+
} else {
|
| 304 |
+
update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Invalid file data", ShowAlert: true})
|
| 305 |
return nil
|
| 306 |
}
|
| 307 |
+
|
| 308 |
+
// Lookup by _id
|
| 309 |
+
res := DB.Mcol.FindOne(context.TODO(), bson.D{{Key: "_id", Value: fileDocID}})
|
| 310 |
+
if res.Err() != nil {
|
| 311 |
+
update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "❌ File not found. It may have been deleted.", ShowAlert: true})
|
| 312 |
return nil
|
| 313 |
}
|
| 314 |
+
|
| 315 |
+
var f database.Filter
|
| 316 |
+
if err := res.Decode(&f); err != nil {
|
| 317 |
+
update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "❌ Error loading file.", ShowAlert: true})
|
|
|
|
|
|
|
|
|
|
| 318 |
return nil
|
| 319 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
|
| 321 |
+
msg, ok := update.Message.(*gotgbot.Message)
|
| 322 |
+
if !ok {
|
| 323 |
+
update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{})
|
| 324 |
+
return nil
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
}
|
| 326 |
|
| 327 |
+
update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{})
|
| 328 |
+
sendFilter(&f, bot, msg, msg.Chat.Id, msg.MessageId)
|
| 329 |
+
return nil
|
| 330 |
}
|
| 331 |
|
| 332 |
+
// ── SendFileByID — start=file_xxx deep link handler ──────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
|
| 334 |
+
// SendFileByID finds a filter by its _id and sends it.
|
| 335 |
+
func SendFileByID(fileDocID string, bot *gotgbot.Bot, update *gotgbot.Message) {
|
| 336 |
+
res := DB.Mcol.FindOne(context.TODO(), bson.D{{Key: "_id", Value: fileDocID}})
|
| 337 |
+
if res.Err() != nil {
|
| 338 |
+
update.Reply(bot, "❌ File not found. It may have been deleted or the link is invalid.", &gotgbot.SendMessageOpts{})
|
| 339 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
}
|
| 341 |
+
var f database.Filter
|
| 342 |
+
if err := res.Decode(&f); err != nil {
|
| 343 |
+
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
}
|
| 345 |
+
sendFilter(&f, bot, update, update.Chat.Id, update.MessageId)
|
| 346 |
}
|
| 347 |
|
| 348 |
+
// ── No-op callbacks ────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
|
| 350 |
+
// CbPages handles the no-op "pages" callback (page number display button).
|
| 351 |
+
func CbPages(bot *gotgbot.Bot, ctx *ext.Context) error {
|
| 352 |
+
ctx.CallbackQuery.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{}) //nolint:errcheck
|
| 353 |
+
return nil
|
| 354 |
+
}
|
| 355 |
|
| 356 |
+
// ── Helpers used by vjsearch.go ───────────────────────────────────────────────
|
| 357 |
+
|
| 358 |
+
// mapToButton converts [][]map[string]string markup to gotgbot keyboard rows.
|
| 359 |
+
func mapToButton(markup [][]map[string]string) [][]gotgbot.InlineKeyboardButton {
|
| 360 |
+
var rows [][]gotgbot.InlineKeyboardButton
|
| 361 |
+
for _, row := range markup {
|
| 362 |
+
var buttons []gotgbot.InlineKeyboardButton
|
| 363 |
+
for _, btn := range row {
|
| 364 |
+
text := btn["name"]
|
| 365 |
+
if url, ok := btn["url"]; ok && url != "" {
|
| 366 |
+
buttons = append(buttons, gotgbot.InlineKeyboardButton{Text: text, Url: url})
|
| 367 |
+
} else if cb, ok := btn["data"]; ok && cb != "" {
|
| 368 |
+
buttons = append(buttons, gotgbot.InlineKeyboardButton{Text: text, CallbackData: cb})
|
| 369 |
}
|
|
|
|
|
|
|
| 370 |
}
|
| 371 |
+
if len(buttons) > 0 {
|
| 372 |
+
rows = append(rows, buttons)
|
|
|
|
| 373 |
}
|
|
|
|
|
|
|
| 374 |
}
|
| 375 |
+
return rows
|
|
|
|
| 376 |
}
|
| 377 |
|
| 378 |
+
// fileDisplayName returns the best display name for a filter, VJ-style:
|
| 379 |
+
// strips @username, URLs, shows [size] prefix like VJ does.
|
| 380 |
+
func fileDisplayName(f *database.Filter) string {
|
| 381 |
+
name := f.FileName
|
| 382 |
+
if name == "" {
|
| 383 |
+
name = strings.SplitN(f.Content, "\n", 2)[0]
|
| 384 |
+
name = usernameRegex.ReplaceAllString(name, "")
|
| 385 |
+
name = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(name, "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
}
|
| 387 |
+
if name == "" {
|
| 388 |
+
name = f.Text
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
}
|
| 390 |
+
// Strip @usernames and URLs from display name — VJ filter
|
| 391 |
+
name = usernameRegex.ReplaceAllString(name, "")
|
| 392 |
+
name = regexp.MustCompile(`(?i)https?://\S+|t\.me/\S+|www\.\S+`).ReplaceAllString(name, "")
|
| 393 |
+
name = strings.TrimSpace(name)
|
| 394 |
|
| 395 |
+
// Add size prefix like VJ: "[800MB] Movie Name"
|
| 396 |
+
prefix := ""
|
| 397 |
+
if f.FileSize > 0 {
|
| 398 |
+
prefix = "[" + getFileSize(f.FileSize) + "] "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
|
| 401 |
+
full := prefix + name
|
| 402 |
+
if len(full) > 58 {
|
| 403 |
+
full = full[:55] + "..."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
}
|
| 405 |
+
return full
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
}
|
plugins/index.go
CHANGED
|
@@ -106,17 +106,11 @@ func AutoIndex(bot *gotgbot.Bot, ctx *ext.Context) error {
|
|
| 106 |
return nil
|
| 107 |
}
|
| 108 |
|
| 109 |
-
// Skip if an identical global filter already exists
|
| 110 |
-
if _, exists := DB.GetMfilter(globalNumber, keyword); exists {
|
| 111 |
-
return nil
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
caption := ""
|
| 115 |
if msg.Caption != "" {
|
| 116 |
caption = msg.OriginalCaptionHTML()
|
| 117 |
}
|
| 118 |
|
| 119 |
-
// Extract file size and mime type
|
| 120 |
var fileSize int64
|
| 121 |
var mimeType string
|
| 122 |
switch {
|
|
@@ -132,10 +126,10 @@ func AutoIndex(bot *gotgbot.Bot, ctx *ext.Context) error {
|
|
| 132 |
}
|
| 133 |
|
| 134 |
f := &database.Filter{
|
| 135 |
-
|
| 136 |
ChatID: globalNumber,
|
| 137 |
Text: keyword,
|
| 138 |
-
FileName: rawName,
|
| 139 |
Content: caption,
|
| 140 |
FileID: fileID,
|
| 141 |
MediaType: mediaType,
|
|
@@ -144,9 +138,14 @@ func AutoIndex(bot *gotgbot.Bot, ctx *ext.Context) error {
|
|
| 144 |
Length: len(keyword),
|
| 145 |
}
|
| 146 |
|
| 147 |
-
DB.SaveMfilterByFileID(f)
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
return nil
|
| 151 |
}
|
| 152 |
|
|
@@ -207,11 +206,6 @@ func IndexNow(bot *gotgbot.Bot, ctx *ext.Context) error {
|
|
| 207 |
return nil
|
| 208 |
}
|
| 209 |
|
| 210 |
-
if _, exists := DB.GetMfilter(globalNumber, keyword); exists {
|
| 211 |
-
msg.Reply(bot, fmt.Sprintf("⚠️ A global filter for <code>%s</code> already exists.", keyword), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
|
| 212 |
-
return nil
|
| 213 |
-
}
|
| 214 |
-
|
| 215 |
caption := ""
|
| 216 |
if target.Caption != "" {
|
| 217 |
caption = target.OriginalCaptionHTML()
|
|
@@ -239,7 +233,6 @@ func IndexNow(bot *gotgbot.Bot, ctx *ext.Context) error {
|
|
| 239 |
}
|
| 240 |
|
| 241 |
f := &database.Filter{
|
| 242 |
-
ID: fileID, // Use file_id as _id for deduplication
|
| 243 |
ChatID: globalNumber,
|
| 244 |
Text: keyword,
|
| 245 |
FileName: origName,
|
|
@@ -251,10 +244,17 @@ func IndexNow(bot *gotgbot.Bot, ctx *ext.Context) error {
|
|
| 251 |
Length: len(keyword),
|
| 252 |
}
|
| 253 |
|
| 254 |
-
DB.SaveMfilterByFileID(f)
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
msg.Reply(bot,
|
| 257 |
-
fmt.Sprintf("✅ Indexed
|
| 258 |
&gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML},
|
| 259 |
)
|
| 260 |
return nil
|
|
|
|
| 106 |
return nil
|
| 107 |
}
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
caption := ""
|
| 110 |
if msg.Caption != "" {
|
| 111 |
caption = msg.OriginalCaptionHTML()
|
| 112 |
}
|
| 113 |
|
|
|
|
| 114 |
var fileSize int64
|
| 115 |
var mimeType string
|
| 116 |
switch {
|
|
|
|
| 126 |
}
|
| 127 |
|
| 128 |
f := &database.Filter{
|
| 129 |
+
// ID is generated by SaveMfilterByFileID (short, safe for Telegram deep links)
|
| 130 |
ChatID: globalNumber,
|
| 131 |
Text: keyword,
|
| 132 |
+
FileName: rawName,
|
| 133 |
Content: caption,
|
| 134 |
FileID: fileID,
|
| 135 |
MediaType: mediaType,
|
|
|
|
| 138 |
Length: len(keyword),
|
| 139 |
}
|
| 140 |
|
| 141 |
+
inserted, err := DB.SaveMfilterByFileID(f)
|
| 142 |
+
if err != nil {
|
| 143 |
+
fmt.Printf("auto-index: save error: %v\n", err)
|
| 144 |
+
} else if inserted {
|
| 145 |
+
fmt.Printf("auto-index: ✅ indexed %q from channel %d\n", rawName, msg.Chat.Id)
|
| 146 |
+
} else {
|
| 147 |
+
fmt.Printf("auto-index: ⏭ skipped duplicate %q\n", rawName)
|
| 148 |
+
}
|
| 149 |
return nil
|
| 150 |
}
|
| 151 |
|
|
|
|
| 206 |
return nil
|
| 207 |
}
|
| 208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
caption := ""
|
| 210 |
if target.Caption != "" {
|
| 211 |
caption = target.OriginalCaptionHTML()
|
|
|
|
| 233 |
}
|
| 234 |
|
| 235 |
f := &database.Filter{
|
|
|
|
| 236 |
ChatID: globalNumber,
|
| 237 |
Text: keyword,
|
| 238 |
FileName: origName,
|
|
|
|
| 244 |
Length: len(keyword),
|
| 245 |
}
|
| 246 |
|
| 247 |
+
inserted, err := DB.SaveMfilterByFileID(f)
|
| 248 |
+
if err != nil {
|
| 249 |
+
msg.Reply(bot, fmt.Sprintf("❌ Save error: %v", err), &gotgbot.SendMessageOpts{})
|
| 250 |
+
return nil
|
| 251 |
+
}
|
| 252 |
+
if !inserted {
|
| 253 |
+
msg.Reply(bot, fmt.Sprintf("⚠️ <code>%s</code> already indexed (duplicate file).", origName), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
|
| 254 |
+
return nil
|
| 255 |
+
}
|
| 256 |
msg.Reply(bot,
|
| 257 |
+
fmt.Sprintf("✅ Indexed: <code>%s</code>\nKeyword: <code>%s</code>", origName, keyword),
|
| 258 |
&gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML},
|
| 259 |
)
|
| 260 |
return nil
|