├── .env.sample
├── .github
├── dependabot.yml
└── workflows
│ └── go.yml
├── .gitignore
├── .golangci.yml
├── Dockerfile
├── LICENSE
├── Procfile
├── README.md
├── api
└── bot.go
├── app.json
├── database
└── database.go
├── go.mod
├── go.sum
├── heroku.yml
├── logo.jpg
├── main.go
├── okteto-pipeline.yml
├── okteto-stack.yml
├── plugins
├── basics.go
├── broadcast.go
├── connect.go
├── dispatcher.go
├── filter.go
├── gfilter.go
└── newchat.go
├── public
└── index.html
├── render.yaml
├── utils
├── autodelete
│ └── autodelete.go
├── config
│ ├── config.go
│ └── environment.go
├── customfilters
│ └── customfilters.go
├── format.go
└── listen.go
└── vercel.json
/.env.sample:
--------------------------------------------------------------------------------
1 | BOT_TOKEN = 123456789:AAGfDDgY318Ut5L5iYFt4LlFWqrkVq30iMD
2 | MONGODB_URI = mongodb+srv://yourusername:password@cluster0.tgahosao.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0
3 | ADMINS = 1234567890 1093541873
4 | MULTI_FILTER = false
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: gomod
4 | directory: /
5 | schedule:
6 | interval: daily
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on: [push,pull_request]
4 |
5 | jobs:
6 |
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 |
12 | - name: Set up Go
13 | uses: actions/setup-go@v4
14 | with:
15 | go-version: '1.22.1'
16 |
17 | - name: Build
18 | run: go build -v ./...
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
17 | # Go workspace file
18 | go.work
19 | go.work.sum
20 |
21 | # Vscode workspace configs
22 | /.vscode
23 |
24 | # Local environment configs
25 | .env
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters-settings:
2 | errcheck:
3 | check-type-assertions: true
4 | revive:
5 | rules:
6 | - name: unused-parameter
7 | severity: warning
8 | disabled: true
9 | dupl:
10 | threshold: 100
11 | goconst:
12 | min-len: 2
13 | min-occurrences: 3
14 | gocritic:
15 | enabled-tags:
16 | - diagnostic
17 | - experimental
18 | - opinionated
19 | - performance
20 | - style
21 | govet:
22 | shadow: true
23 | nolintlint:
24 | require-explanation: false
25 | require-specific: true
26 | prealloc:
27 | range-loops: false
28 | mnd:
29 | ignored-numbers:
30 | - '2'
31 |
32 | linters:
33 | disable-all: true
34 | enable:
35 | - bodyclose
36 | - copyloopvar
37 | - deadcode
38 | # - depguard (absolute trash)
39 | - dogsled
40 | - dupl
41 | # - errcheck (TODO: waay too much now)
42 | - exportloopref
43 | - exhaustive
44 | - goconst
45 | - gocritic
46 | - gofmt
47 | - goimports
48 | # - gomnd (deprecated)
49 | - mnd
50 | - gocyclo
51 | - gosec
52 | - gosimple
53 | - govet
54 | - ineffassign
55 | - misspell
56 | - nolintlint
57 | - nakedret
58 | - prealloc
59 | - predeclared
60 | - revive
61 | - staticcheck
62 | - structcheck
63 | - stylecheck
64 | - thelper
65 | - tparallel
66 | - typecheck
67 | - unconvert
68 | - unparam
69 | - varcheck
70 | - whitespace
71 | - wsl
72 |
73 | issues:
74 | exclude-rules:
75 | # Disable linters that are annoying in tests.
76 | - path: _test\.go
77 | linters:
78 | - gocritic
79 | - gocyclo
80 | - errcheck
81 | - dupl
82 | - gosec
83 | - funlen
84 | - goconst
85 | - gocognit
86 | - scopelint
87 | - lll
88 |
89 | run:
90 | issues-exit-code: 1
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.18-bullseye
2 |
3 | RUN mkdir /App
4 | WORKDIR /App
5 | RUN cd /App
6 | COPY . .
7 | RUN go build .
8 |
9 | CMD ["./Go-Filter-Bot"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
38 | Follow these instructions to deploy this repo to vercel
39 | Deploy To Vercel
37 |
40 |
46 |
51 |
52 |
53 |
54 |
55 |
60 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 |
73 |
75 | Make sure to have the following options set : 76 | 77 | Environment 78 |
Go79 | 80 | Build Command 81 |
go build .82 | 83 | Start Command 84 |
./Go-Filter-Bot85 | 86 | Advanced >> Health Check Path 87 |
/88 | 89 |
94 |
95 |
96 |
97 |
98 |
100 | You must set the Run command to : 101 |
./bin/Go-Filter-Bot102 | 103 |
107 |
108 |
109 |
110 |
111 |
116 |
117 |
118 |
119 |
120 |
125 | You must have the latest version of go installed first 126 |
127 | git clone https://github.com/Jisin0/Go-Filter-Bot 128 | cd Go-Filter-Bot 129 | go build . 130 | ./Go-Filter-Bot 131 |132 | 133 |
%v
111 | ├ ▸ Filters : %v
112 | ╰ ▸ Groups : %v
113 | `
114 |
115 | //nolint:errcheck // meh
116 | func (db *Database) Stats() string {
117 | users, _ := db.Ucol.CountDocuments(context.TODO(), bson.M{})
118 | chats, _ := db.Col.CountDocuments(context.TODO(), bson.M{})
119 | mfilters, _ := db.Mcol.CountDocuments(context.TODO(), bson.M{})
120 |
121 | return fmt.Sprintf(statText, users, mfilters, chats)
122 | }
123 |
124 | func (db *Database) GetConnection(userID int64) (int64, bool) {
125 | if c, ok := connectionCache[userID]; ok {
126 | if c == 0 {
127 | ok = false
128 | }
129 |
130 | return c, ok
131 | }
132 |
133 | res := db.Ucol.FindOne(context.TODO(), bson.D{{Key: "_id", Value: userID}})
134 | if res.Err() != nil { // this shouldn't happen. the user should be saved in the db.
135 | connectionCache[userID] = 0
136 | return 0, false
137 | }
138 |
139 | var doc User
140 |
141 | res.Decode(&doc)
142 |
143 | connectionCache[userID] = doc.ConnectedChat
144 |
145 | if doc.ConnectedChat != 0 {
146 | return doc.ConnectedChat, true
147 | }
148 |
149 | return doc.ConnectedChat, false
150 | }
151 |
152 | func (db *Database) ConnectUser(userID, chatID int64) {
153 | var tf = true
154 |
155 | _, err := db.Ucol.UpdateOne(context.TODO(), bson.D{{Key: "_id", Value: userID}}, bson.D{{Key: "$set", Value: bson.D{{Key: "connected", Value: chatID}}}}, &options.UpdateOptions{Upsert: &tf})
156 | if err != nil {
157 | fmt.Printf("db.connectuser: %v\n", err)
158 | }
159 |
160 | // clear any cache.
161 | delete(connectionCache, userID)
162 | }
163 |
164 | func (db *Database) SaveMfilter(data *Filter) {
165 | _, err := db.Mcol.InsertOne(context.TODO(), data)
166 | if err != nil {
167 | fmt.Printf("db.savemfilter: %v\n", err)
168 | }
169 | }
170 |
171 | func (db *Database) DeleteConnection(userID int64) {
172 | _, err := db.Ucol.UpdateOne(context.TODO(), bson.D{{Key: "_id", Value: userID}}, bson.D{{Key: "$unset", Value: "connected"}})
173 | if err != nil {
174 | fmt.Printf("db.connectuser: %v\n", err)
175 | }
176 |
177 | // clear any cache.
178 | delete(connectionCache, userID)
179 | }
180 |
181 | // Returns all manual filters for a chat.
182 | func (db *Database) GetMfilters(chatID int64) (*mongo.Cursor, error) {
183 | return db.Mcol.Aggregate(
184 | context.TODO(),
185 | []bson.D{
186 | {{Key: "$match", Value: bson.D{{Key: "group_id", Value: chatID}}}},
187 | {{Key: "$sort", Value: bson.D{{Key: "length", Value: -1}}}},
188 | },
189 | )
190 | }
191 |
192 | // SearchMfilterClassic uses the traditional way of fetching mfilters by fetching all mfilters of a chat and doing regex queries individually.
193 | // This method is more resource intensive but could be faster for large scale bots with several hundred/thousand groups.
194 | func (db *Database) SearchMfilterClassic(chatID int64, input string) (results []*Filter) {
195 | res, e := db.GetMfilters(chatID)
196 | if e != nil {
197 | fmt.Printf("db.searchmfiltersclassic: %v\n", e)
198 | return results
199 | }
200 |
201 | for res.Next(context.TODO()) {
202 | var f Filter
203 |
204 | err := res.Decode(&f)
205 | if err != nil {
206 | fmt.Printf("db.searchmfilterclassic: %v\n", err)
207 | continue
208 | }
209 |
210 | text := `(?i)( |^|[^\w])` + f.Text + `( |$|[^\w])`
211 |
212 | pattern := regexp.MustCompile(text)
213 |
214 | m := pattern.FindStringSubmatch(input)
215 | if len(m) > 0 {
216 | results = append(results, &f)
217 | }
218 | }
219 |
220 | return results
221 | }
222 |
223 | // SearchMfilterNew does a regex query on the database shifting some load to mongodb.
224 | func (db *Database) SearchMfilterNew(chatID int64, fields []string, multiFilter bool) (results []*Filter) {
225 | pattern := "(?i).*\\b(" + strings.Join(fields, "|") + ")\\b.*"
226 | filter := bson.D{
227 | {Key: "group_id", Value: chatID},
228 | {Key: "text", Value: bson.M{"$regex": pattern}},
229 | }
230 |
231 | if !multiFilter {
232 | res := db.Mcol.FindOne(context.Background(), filter)
233 | switch res.Err() {
234 | case mongo.ErrNoDocuments:
235 | return results
236 | case nil:
237 | var f Filter
238 |
239 | if err := res.Decode(&f); err != nil {
240 | fmt.Printf("db.searchmfilternew: %v\n", err)
241 | return results
242 | }
243 |
244 | return append(results, &f)
245 | }
246 | }
247 |
248 | res, err := db.Mcol.Find(context.Background(), filter, options.Find().SetSort(bson.D{{Key: "length", Value: -1}}))
249 | if err != nil {
250 | fmt.Printf("db.searchmfilternew: %v\n", err)
251 | return results
252 | }
253 |
254 | for res.Next(context.Background()) {
255 | var r Filter
256 |
257 | err := res.Decode(&r)
258 | if err != nil {
259 | fmt.Printf("db.searchmfilternew: %v\n", err)
260 | continue
261 | }
262 |
263 | results = append(results, &r)
264 | }
265 |
266 | return results
267 | }
268 |
269 | func (db *Database) GetMfilter(chatID int64, key string) (bson.M, bool) {
270 | res := db.Mcol.FindOne(context.TODO(), bson.D{{Key: "group_id", Value: chatID}, {Key: "text", Value: key}})
271 |
272 | var b bson.M
273 |
274 | if res.Err() != nil {
275 | return b, false
276 | } else {
277 | err := res.Decode(&b)
278 | if err != nil {
279 | fmt.Printf("db.getmfilter: %v\n", err)
280 | return b, false
281 | }
282 |
283 | return b, true
284 | }
285 | }
286 |
287 | func (db *Database) StringMfilter(chatID int64) string {
288 | r, err := db.GetMfilters(chatID)
289 | if err != nil {
290 | return fmt.Sprintf("failed to get filters: %v", err)
291 | }
292 |
293 | var text string
294 |
295 | for r.Next(context.TODO()) {
296 | var d bson.M
297 |
298 | r.Decode(&d)
299 | text += fmt.Sprintf("\n• %v
", d["text"])
300 | }
301 |
302 | return text
303 | }
304 |
305 | func (db *Database) StopGfilter(chatID int64, key string) {
306 | var t = true
307 |
308 | _, err := db.Col.UpdateOne(context.TODO(), bson.D{{Key: "_id", Value: chatID}}, bson.D{{Key: "$append", Value: bson.D{{Key: "stopped", Value: key}}}}, &options.UpdateOptions{Upsert: &t})
309 | if err != nil {
310 | fmt.Println(err)
311 | }
312 |
313 | go db.RecacheSettings(chatID)
314 | }
315 |
316 | func (db *Database) DeleteMfilter(chatID int64, key string) {
317 | _, err := db.Mcol.DeleteOne(context.TODO(), bson.D{{Key: "group_id", Value: chatID}, {Key: "text", Value: key}})
318 | if err != nil {
319 | fmt.Printf("db.deletemfilter: %v\n", err)
320 | }
321 | }
322 |
323 | func (db *Database) SetChatSetting(chatID int64, key string, value any) {
324 | go db.SetDefaultSettings(chatID)
325 |
326 | _, err := db.Col.UpdateOne(context.TODO(), bson.D{{Key: "_id", Value: chatID}}, bson.D{{Key: "$set", Value: bson.D{{Key: key, Value: value}}}})
327 | if err != nil {
328 | fmt.Printf("db.connectuser: %v\n", err)
329 | return
330 | }
331 |
332 | go db.RecacheSettings(chatID)
333 | }
334 |
335 | func (db *Database) SetDefaultSettings(chatID int64) {
336 | r := db.Col.FindOne(context.TODO(), bson.D{{Key: "_id", Value: chatID}})
337 | if r.Err() == mongo.ErrNoDocuments {
338 | db.Col.InsertOne(context.TODO(), defaultSettings(chatID))
339 | }
340 | }
341 |
342 | func defaultSettings(chatID int64) bson.D {
343 | return bson.D{
344 | {Key: "_id", Value: chatID},
345 | }
346 | }
347 |
348 | // A Function To Update Cached Settings With Latest From DB
349 | func (db *Database) RecacheSettings(chatID int64) {
350 | res := db.Col.FindOne(context.TODO(), bson.D{{Key: "_id", Value: chatID}})
351 | if res.Err() == mongo.ErrNoDocuments {
352 | cachedSettings[chatID] = &defaultChatSettings
353 | } else {
354 | var r ChatSettings
355 |
356 | err := res.Decode(&r)
357 | if err != nil {
358 | fmt.Printf("db.recachesettings: %v\n", err)
359 | return
360 | }
361 |
362 | cachedSettings[chatID] = &r
363 | }
364 | }
365 |
366 | func (db *Database) GetCachedSetting(chatID int64) *ChatSettings {
367 | s, e := cachedSettings[chatID]
368 | if !e {
369 | go db.RecacheSettings(chatID)
370 | return &defaultChatSettings
371 | } else {
372 | return s
373 | }
374 | }
375 |
376 | func (db *Database) StartGfilter(chatID int64, key string) {
377 | keys := db.GetCachedSetting(chatID).Stopped
378 | for i, k := range keys {
379 | if k == key {
380 | keys[i] = keys[len(keys)-1]
381 | keys = keys[:len(keys)-1]
382 | }
383 | }
384 |
385 | _, err := db.Col.UpdateOne(context.TODO(), bson.D{{Key: "_id", Value: chatID}}, bson.D{{Key: "$set", Value: bson.D{{Key: "stopped", Value: keys}}}})
386 | if err != nil {
387 | fmt.Printf("db.startgfilter: %v\n", err)
388 | }
389 |
390 | go db.RecacheSettings(chatID)
391 | }
392 |
393 | func (db *Database) GetAlert(uniqueID string, index int) string {
394 | defaultString := "404: Content Not Found !"
395 | res := db.Mcol.FindOne(context.TODO(), bson.D{{Key: "_id", Value: uniqueID}})
396 |
397 | var f Filter
398 |
399 | res.Decode(&f)
400 |
401 | if len(f.Alerts) < index+1 {
402 | return defaultString
403 | } else {
404 | return f.Alerts[index]
405 | }
406 | }
407 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/Jisin0/Go-Filter-Bot
2 |
3 | // +heroku goVersion go1.18
4 | go 1.18
5 |
6 | require (
7 | github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.29
8 | github.com/jmoiron/sqlx v1.4.0
9 | github.com/joho/godotenv v1.5.1
10 | github.com/mattn/go-sqlite3 v1.14.22
11 | go.mongodb.org/mongo-driver v1.16.1
12 | )
13 |
14 | require (
15 | github.com/golang/snappy v0.0.4 // indirect
16 | github.com/klauspost/compress v1.13.6 // indirect
17 | github.com/montanaflynn/stats v0.7.1 // indirect
18 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect
19 | github.com/xdg-go/scram v1.1.2 // indirect
20 | github.com/xdg-go/stringprep v1.0.4 // indirect
21 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
22 | golang.org/x/crypto v0.22.0 // indirect
23 | golang.org/x/sync v0.7.0 // indirect
24 | golang.org/x/text v0.14.0 // indirect
25 | )
26 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
3 | github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.29 h1:5/K8zgmoKnsegt6h9XvFIJAGxbHVWOEwSpjdjaySf6A=
4 | github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.29/go.mod h1:kL1v4iIjlalwm3gCYGvF4NLa3hs+aKEfRkNJvj4aoDU=
5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6 | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
7 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
8 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
9 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
10 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
11 | github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
12 | github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
13 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
14 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
15 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
16 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
17 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
18 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
19 | github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
20 | github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
21 | github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
22 | github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
23 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
24 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
25 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
26 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
27 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
28 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
29 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
30 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
31 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
32 | go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8=
33 | go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
34 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
35 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
36 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
37 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
38 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
39 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
40 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
41 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
42 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
43 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
44 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
45 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
46 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
47 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
48 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
49 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
50 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
51 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
52 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
53 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
54 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
55 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
56 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
57 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
58 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
59 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
60 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
61 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
62 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
63 |
--------------------------------------------------------------------------------
/heroku.yml:
--------------------------------------------------------------------------------
1 | web : ./bin/Go-Filter-Bot
--------------------------------------------------------------------------------
/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jisin0/Go-Filter-Bot/66cef229cd2463f92c76a0c51c68effa62fd0da3/logo.jpg
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "runtime/debug"
7 | "time"
8 |
9 | "github.com/Jisin0/Go-Filter-Bot/plugins"
10 | "github.com/Jisin0/Go-Filter-Bot/utils/autodelete"
11 | "github.com/Jisin0/Go-Filter-Bot/utils/config"
12 | "github.com/PaulSonOfLars/gotgbot/v2"
13 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
14 | )
15 |
16 | func main() {
17 | defer func() {
18 | if r := recover(); r != nil {
19 | // Print reason for panic + stack for some sort of helpful log output
20 | fmt.Println(r)
21 | fmt.Println(string(debug.Stack()))
22 | }
23 | }()
24 |
25 | // Run a useless http server to get a healthy build on koyeb
26 | go func() {
27 | http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
28 | fmt.Fprintf(w, "Waku Waku")
29 | })
30 |
31 | http.ListenAndServe(":"+config.Port, nil)
32 | }()
33 |
34 | if config.BotToken == "" {
35 | panic("Exiting Because No BOT_TOKEN Provided :(")
36 | }
37 |
38 | // Create bot from environment value.
39 | b, err := gotgbot.NewBot(config.BotToken, &gotgbot.BotOpts{
40 | BotClient: &gotgbot.BaseBotClient{
41 | Client: http.Client{},
42 | DefaultRequestOpts: &gotgbot.RequestOpts{
43 | Timeout: gotgbot.DefaultTimeout, // Customise the default request timeout here
44 | APIURL: gotgbot.DefaultAPIURL, // As well as the Default API URL here (in case of using local bot API servers)
45 | },
46 | },
47 | })
48 | if err != nil {
49 | panic("failed to create new bot: " + err.Error())
50 | }
51 |
52 | // To make sure no other instance of the bot is running
53 | _, err = b.GetUpdates(&gotgbot.GetUpdatesOpts{})
54 | if err != nil {
55 | fmt.Println("waiting 10s because : " + err.Error())
56 | time.Sleep(10 * time.Second)
57 | }
58 |
59 | updater := ext.NewUpdater(plugins.Dispatcher, &ext.UpdaterOpts{})
60 |
61 | // Start receiving updates.
62 | err = updater.StartPolling(b, &ext.PollingOpts{
63 | DropPendingUpdates: true,
64 | GetUpdatesOpts: &gotgbot.GetUpdatesOpts{
65 | AllowedUpdates: []string{"message", "callback_query", "channel_post", "inline_query", "chosen_inline_result", "chat_member", "my_chat_member"},
66 | },
67 | })
68 | if err != nil {
69 | panic("failed to start polling: " + err.Error())
70 | }
71 |
72 | fmt.Printf("@%s Started !\n", b.User.Username)
73 |
74 | if plugins.AutoDelete > 0 {
75 | go autodelete.RunAutodel(b)
76 | }
77 |
78 | // Idle, to keep updates coming in, and avoid bot stopping.
79 | updater.Idle()
80 | }
81 |
--------------------------------------------------------------------------------
/okteto-pipeline.yml:
--------------------------------------------------------------------------------
1 | deploy:
2 | - okteto stack deploy --build -f okteto-stack.yml
--------------------------------------------------------------------------------
/okteto-stack.yml:
--------------------------------------------------------------------------------
1 | services:
2 | drone-vc-p:
3 | build: .
4 | environment:
5 | BOT_TOKEN: $BOT_TOKEN
6 | ADMINS: $ADMINS
7 | MONGODB_URI: $MONGODB_URI
8 | PORT: $PORT
9 | AUTO_DELETE: $AUTO_DELETE
10 | MULTI_FILTER: $MULTI_FILTER
11 | resources:
12 | cpu: 1000m
13 | memory: 3Gi
--------------------------------------------------------------------------------
/plugins/basics.go:
--------------------------------------------------------------------------------
1 | // (c) Jisin0
2 |
3 | package plugins
4 |
5 | import (
6 | "fmt"
7 | "regexp"
8 |
9 | "github.com/Jisin0/Go-Filter-Bot/utils/config"
10 | "github.com/PaulSonOfLars/gotgbot/v2"
11 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
12 | )
13 |
14 | var cbEditPattern *regexp.Regexp = regexp.MustCompile(`edit\((.+)\)`)
15 |
16 | var AutoDelete = config.AutoDelete * 60 // time in seconds after which a message should be automantically deleted.
17 |
18 | func Start(bot *gotgbot.Bot, update *ext.Context) error {
19 | go DB.AddUser(update.EffectiveMessage.From.Id)
20 |
21 | _, err := bot.SendMessage(
22 | update.Message.Chat.Id,
23 | fmt.Sprintf(config.TEXT["START"], update.Message.From.FirstName, bot.FirstName),
24 | &gotgbot.SendMessageOpts{
25 | ParseMode: gotgbot.ParseModeHTML,
26 | ReplyMarkup: gotgbot.InlineKeyboardMarkup{
27 | InlineKeyboard: config.BUTTONS["START"],
28 | },
29 | ReplyParameters: &gotgbot.ReplyParameters{
30 | AllowSendingWithoutReply: true,
31 | },
32 | })
33 | if err != nil {
34 | fmt.Printf("start: %v\n", err)
35 | }
36 |
37 | return nil
38 | }
39 |
40 | func Stats(bot *gotgbot.Bot, update *ext.Context) error {
41 | markup := gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{{{Text: "Refresh 🔄", CallbackData: "stats"}}}}
42 |
43 | _, err := update.EffectiveMessage.Reply(bot, DB.Stats(), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: markup})
44 | if err != nil {
45 | fmt.Printf("stats: %v\n", err)
46 | }
47 |
48 | return nil
49 | }
50 |
51 | func GetID(bot *gotgbot.Bot, ctx *ext.Context) error {
52 | var (
53 | text string
54 | update = ctx.Message
55 | )
56 |
57 | if update.ReplyToMessage != nil {
58 | text += fmt.Sprintf("\nReplied to user : %v
", update.ReplyToMessage.From.Id)
59 |
60 | if f := update.ReplyToMessage.ForwardOrigin; f.GetDate() != 0 {
61 | switch m := f.MergeMessageOrigin(); {
62 | case m.Chat != nil:
63 | text += fmt.Sprintf("\nForwarded from : %v
", m.Chat.Id)
64 | case m.SenderChat != nil:
65 | text += fmt.Sprintf("\nForwarded from : %v
", m.SenderChat.Id)
66 | case m.SenderUser != nil:
67 | text += fmt.Sprintf("\nForwarded from : %v
", m.SenderUser.Id)
68 | }
69 | }
70 | }
71 |
72 | text += fmt.Sprintf("\nUser id : %v
", update.From.Id)
73 |
74 | if update.Chat.Type != gotgbot.ChatTypePrivate {
75 | text += fmt.Sprintf("\nChat id : %v
", update.Chat.Id)
76 | }
77 |
78 | _, err := update.Reply(bot, text, &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyParameters: &gotgbot.ReplyParameters{MessageId: update.MessageId}})
79 | if err != nil {
80 | fmt.Printf("getid: %v\n", err)
81 | }
82 |
83 | return nil
84 | }
85 |
86 | func CbStats(bot *gotgbot.Bot, update *ext.Context) error {
87 | _, _, err := update.CallbackQuery.Message.EditText(bot, DB.Stats(), &gotgbot.EditMessageTextOpts{
88 | ChatId: update.CallbackQuery.Message.GetChat().Id,
89 | MessageId: update.CallbackQuery.Message.GetMessageId(),
90 | ParseMode: gotgbot.ParseModeHTML,
91 | ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: config.BUTTONS["STATS"]},
92 | })
93 |
94 | if err != nil {
95 | fmt.Printf("cbstats: %v\n", err)
96 | }
97 |
98 | return nil
99 | }
100 |
101 | func FilterHandler(b *gotgbot.Bot, ctx *ext.Context) error {
102 | // What R U Lookking At Its Just a Pro Function ;)
103 | go MFilter(b, ctx)
104 | go GFilter(b, ctx)
105 |
106 | return nil
107 | }
108 |
109 | // Function to handle edit() callbacks from the Start, About and Help menus
110 | func CbEdit(bot *gotgbot.Bot, update *ext.Context) error {
111 | key := cbEditPattern.FindStringSubmatch(update.CallbackQuery.Data)[1]
112 |
113 | markup, ok := config.BUTTONS[key]
114 | if !ok {
115 | markup = [][]gotgbot.InlineKeyboardButton{{{Text: "⤝ Bᴀᴄᴋ", CallbackData: "edit(HELP)"}}}
116 | }
117 |
118 | options := gotgbot.EditMessageTextOpts{
119 | ChatId: update.CallbackQuery.Message.GetChat().Id,
120 | MessageId: update.CallbackQuery.Message.GetMessageId(),
121 | ParseMode: gotgbot.ParseModeHTML,
122 | LinkPreviewOptions: &gotgbot.LinkPreviewOptions{
123 | IsDisabled: true,
124 | },
125 | ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: markup},
126 | }
127 |
128 | var text string
129 |
130 | if key == "START" {
131 | text = fmt.Sprintf(config.TEXT["START"], update.CallbackQuery.From.FirstName, bot.FirstName)
132 | } else {
133 | text = config.TEXT[key]
134 | }
135 |
136 | _, _, err := update.CallbackQuery.Message.EditText(bot,
137 | text,
138 | &options,
139 | )
140 | if err != nil {
141 | fmt.Printf("cbedit: %v\n", err)
142 | }
143 |
144 | return nil
145 | }
146 |
147 | func About(b *gotgbot.Bot, update *ext.Context) error {
148 | _, err := update.Message.Reply(b, config.TEXT["ABOUT"], &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: config.BUTTONS["ABOUT"]}})
149 | if err != nil {
150 | fmt.Printf("about: %v\n", err)
151 | }
152 |
153 | return nil
154 | }
155 |
156 | func Help(b *gotgbot.Bot, update *ext.Context) error {
157 | _, err := update.Message.Reply(b, config.TEXT["HELP"], &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: config.BUTTONS["HELP"]}})
158 | if err != nil {
159 | fmt.Printf("help: %v\n", err)
160 | }
161 |
162 | return nil
163 | }
164 |
--------------------------------------------------------------------------------
/plugins/broadcast.go:
--------------------------------------------------------------------------------
1 | // (c) Jisin0
2 |
3 | package plugins
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | "strings"
9 |
10 | "github.com/Jisin0/Go-Filter-Bot/utils"
11 | "github.com/PaulSonOfLars/gotgbot/v2"
12 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
13 | "go.mongodb.org/mongo-driver/bson"
14 | )
15 |
16 | func Broadcast(bot *gotgbot.Bot, ctx *ext.Context) error {
17 | // Function to handle /broadcast command
18 | if !utils.IsAdmin(ctx.EffectiveUser.Id) {
19 | ctx.Message.Reply(bot, "Only bot admins can use this command !", &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyParameters: &gotgbot.ReplyParameters{MessageId: ctx.Message.MessageId}})
20 | return nil
21 | }
22 |
23 | var (
24 | update = ctx.Message
25 | filter = bson.D{}
26 | )
27 |
28 | if strings.HasPrefix(update.Text, "/concast") {
29 | filter = bson.D{{Key: "connected", Value: bson.D{{Key: "$exists", Value: true}}}}
30 | }
31 |
32 | cursor, err := DB.Ucol.Find(context.TODO(), filter)
33 | if err != nil {
34 | fmt.Printf("broadcast.find: %v\n", err)
35 | return nil
36 | }
37 |
38 | if update.ReplyToMessage == nil {
39 | update.Reply(bot, "Please reply this command to the message you would like to broadcast !", &gotgbot.SendMessageOpts{})
40 | return nil
41 | }
42 |
43 | msg := update.ReplyToMessage
44 |
45 | var (
46 | isText = false
47 | isMedia = false
48 | caption string
49 | markup = &gotgbot.InlineKeyboardMarkup{}
50 | total int
51 | sent int
52 | failed int
53 | id int64
54 | )
55 |
56 | if msg.Text != "" {
57 | isText = true
58 | } else {
59 | isMedia = true
60 | caption = msg.OriginalCaptionHTML()
61 | }
62 |
63 | if msg.ReplyMarkup != nil {
64 | markup = &gotgbot.InlineKeyboardMarkup{InlineKeyboard: msg.ReplyMarkup.InlineKeyboard}
65 | }
66 |
67 | stat, err := update.Reply(bot, "Starting broadcast ...
", &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
68 | if err != nil {
69 | fmt.Printf("broadcast.startmsg: %v\n", err)
70 | return nil
71 | }
72 |
73 | // TODO: create User type and remove this bson.M trash.
74 | for cursor.Next(context.TODO()) {
75 | var doc bson.M
76 |
77 | cursor.Decode(&doc)
78 |
79 | // Just in case, to prevent unwanted crashes
80 | rawID, ok := doc["_id"]
81 | if !ok {
82 | continue
83 | }
84 |
85 | if _, ok := rawID.(int32); ok {
86 | id = int64(rawID.(int32))
87 | } else {
88 | id = rawID.(int64)
89 | }
90 |
91 | if isText {
92 | _, err := msg.Copy(
93 | bot,
94 | id,
95 | &gotgbot.CopyMessageOpts{ReplyMarkup: markup},
96 | )
97 | if err != nil {
98 | fmt.Printf("broadcast.sendmsg: %v\n", err)
99 |
100 | failed += 1
101 | } else {
102 | sent += 1
103 | }
104 | } else if isMedia {
105 | _, err := msg.Copy(
106 | bot,
107 | id,
108 | &gotgbot.CopyMessageOpts{Caption: &caption, ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: markup},
109 | )
110 | if err != nil {
111 | failed += 1
112 | } else {
113 | sent += 1
114 | }
115 | }
116 |
117 | total += 1
118 |
119 | // Update stat message every 20 requests to prevent floodwaits
120 | if total%20 == 0 {
121 | _, _, er := stat.EditText(
122 | bot,
123 | fmt.Sprintf(`
124 | Live Broadcast Stats :
125 |
126 | Success : %v
127 | Failed : %v
128 | Total : %v
129 | `, sent, failed, total),
130 | &gotgbot.EditMessageTextOpts{ParseMode: gotgbot.ParseModeHTML},
131 | )
132 | if er != nil {
133 | fmt.Printf("broadcast.progress.edit: %v\n", err)
134 | }
135 | }
136 | }
137 |
138 | _, _, err = stat.EditText(
139 | bot,
140 | fmt.Sprintf(`
141 | Broadcast Completed :
142 |
143 | Success : %v
144 | Failed : %v
145 | Total : %v
146 | `, sent, failed, total),
147 | &gotgbot.EditMessageTextOpts{ParseMode: gotgbot.ParseModeHTML},
148 | )
149 | if err != nil {
150 | fmt.Printf("broadcast.progress.complete: %v\n", err)
151 | }
152 |
153 | return nil
154 | }
155 |
--------------------------------------------------------------------------------
/plugins/connect.go:
--------------------------------------------------------------------------------
1 | package plugins
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/Jisin0/Go-Filter-Bot/utils"
10 | "github.com/PaulSonOfLars/gotgbot/v2"
11 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
12 | )
13 |
14 | var cbConnectRegex *regexp.Regexp = regexp.MustCompile(`cbconnect\((.+)\)`)
15 |
16 | func Connect(bot *gotgbot.Bot, update *ext.Context) error {
17 | // Check for any existing connections first
18 | _, k := DB.GetConnection(update.Message.From.Id)
19 | if k {
20 | _, err := update.Message.Reply(
21 | bot,
22 | "Looks Like You Are Already Connected To A Chat, Please /disconnect From It To Connect To Another One :)",
23 | &gotgbot.SendMessageOpts{},
24 | )
25 | if err != nil {
26 | fmt.Println(err)
27 | }
28 |
29 | return nil
30 | } else {
31 | if update.Message.Chat.Type == gotgbot.ChatTypePrivate {
32 | // Require a chat id if the command was used in a private chat
33 | var (
34 | args = strings.Split(update.Message.Text, " ")
35 | chatRaw string
36 | )
37 |
38 | if len(args) < 2 {
39 | m := utils.Ask(bot, "Please send the id of the chat you would like to connect to : ", update.EffectiveChat, update.EffectiveUser)
40 | if m == nil {
41 | return nil
42 | }
43 |
44 | chatRaw = m.Text
45 | } else {
46 | chatRaw = args[1]
47 | }
48 |
49 | chatID, e := strconv.ParseInt(chatRaw, 0, 64)
50 | if e != nil {
51 | // If converion of raw chat_id to an int64 fails i.e it isnt a number
52 | _, err := update.Message.Reply(
53 | bot,
54 | "That Doesnt Seem Like A Valid ChatId A ChatId Looks Something Like -100xxxxxxxxxx :(",
55 | &gotgbot.SendMessageOpts{},
56 | )
57 | if err != nil {
58 | fmt.Println(err)
59 | }
60 |
61 | return nil
62 | } else {
63 | // Verify and connect
64 | admins, err := bot.GetChatAdministrators(chatID, &gotgbot.GetChatAdministratorsOpts{})
65 | if err != nil {
66 | _, err := update.Message.Reply(
67 | bot,
68 | fmt.Sprintf("Sorry Looks Like I Couldnt Find That Chat With Id %v
. Make Sure I'm Admin There With Full Permissions :(", chatID),
69 | &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML},
70 | )
71 | if err != nil {
72 | fmt.Println(err)
73 | }
74 |
75 | return nil
76 | }
77 |
78 | for _, admin := range admins {
79 | if update.Message.From.Id == admin.GetUser().Id {
80 | DB.ConnectUser(update.Message.From.Id, chatID)
81 | _, err := update.Message.Reply(
82 | bot,
83 | "Awesome I've Successfully Connected You To Your Group !",
84 | &gotgbot.SendMessageOpts{},
85 | )
86 |
87 | if err != nil {
88 | fmt.Println(err)
89 | }
90 |
91 | return nil
92 | }
93 | }
94 |
95 | _, err = update.Message.Reply(
96 | bot,
97 | "You Cant Connect To A Chat Where You're Not Admin :)",
98 | &gotgbot.SendMessageOpts{},
99 | )
100 | if err != nil {
101 | fmt.Println(err)
102 | }
103 |
104 | return nil
105 | }
106 | } else if update.Message.Chat.Type == gotgbot.ChatTypeSupergroup || update.Message.Chat.Type == gotgbot.ChatTypeGroup {
107 | // For groups or supergroups just connect
108 | if update.Message.From.Id == 0 {
109 | // Connect using button in case user is anonymous
110 | _, err := update.Message.Reply(
111 | bot,
112 | "It Looks Like You Are Anonymous Click The Button Below To Connect :(",
113 | &gotgbot.SendMessageOpts{
114 | ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{{{Text: "Connect Me", CallbackData: "cbconnect(con)"}}}},
115 | },
116 | )
117 | if err != nil {
118 | fmt.Println(err)
119 | }
120 |
121 | return nil
122 | } else {
123 | // Verification stuff
124 | admins, err := bot.GetChatAdministrators(update.Message.Chat.Id, &gotgbot.GetChatAdministratorsOpts{})
125 | if err != nil {
126 | _, err := update.Message.Reply(
127 | bot,
128 | "Sorry I Couldn't access the admins list of this chat!\nPlease make sure I'm an admin here.",
129 | &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML},
130 | )
131 | if err != nil {
132 | fmt.Println(err)
133 | }
134 |
135 | return nil
136 | }
137 |
138 | for _, admin := range admins {
139 | if update.Message.From.Id == admin.GetUser().Id {
140 | DB.ConnectUser(update.Message.From.Id, update.Message.Chat.Id)
141 | _, err := update.Message.Reply(
142 | bot,
143 | "Awesome I've Successfully Connected You To This Group !",
144 | &gotgbot.SendMessageOpts{},
145 | )
146 |
147 | if err != nil {
148 | fmt.Println(err)
149 | }
150 |
151 | return nil
152 | }
153 | }
154 |
155 | _, err = update.Message.Reply(
156 | bot,
157 | "Ok Mr. Non-Admin :)",
158 | &gotgbot.SendMessageOpts{},
159 | )
160 | if err != nil {
161 | fmt.Println(err)
162 | }
163 | }
164 | }
165 | }
166 |
167 | return nil
168 | }
169 |
170 | // CbConnecthandles callback from connect button.
171 | //
172 | //nolint:errcheck // Ignore alert errors
173 | func CbConnect(bot *gotgbot.Bot, ctx *ext.Context) error {
174 | update := ctx.CallbackQuery
175 |
176 | rMatch := cbConnectRegex.FindStringSubmatch(update.Data)
177 | if len(rMatch) < 2 {
178 | fmt.Printf("cbconnect: bad callback data %s", update.Data)
179 | return nil
180 | }
181 |
182 | action := rMatch[1]
183 | if action == "con" {
184 | admins, _ := bot.GetChatAdministrators(update.Message.GetChat().Id, &gotgbot.GetChatAdministratorsOpts{})
185 |
186 | for _, admin := range admins {
187 | if update.From.Id == admin.GetUser().Id {
188 | DB.ConnectUser(update.From.Id, update.Message.GetChat().Id)
189 |
190 | update.Answer(
191 | bot,
192 | &gotgbot.AnswerCallbackQueryOpts{Text: "Awesome I've Successfully Connected You To This Group !", ShowAlert: true},
193 | )
194 |
195 | update.Message.Delete(bot, &gotgbot.DeleteMessageOpts{})
196 |
197 | return nil
198 | }
199 | }
200 |
201 | update.Answer(
202 | bot,
203 | &gotgbot.AnswerCallbackQueryOpts{Text: "Ok Mr. Non-Admin :)", ShowAlert: true},
204 | )
205 | } else if action == "dis" {
206 | DB.DeleteConnection(update.From.Id)
207 |
208 | update.Answer(
209 | bot,
210 | &gotgbot.AnswerCallbackQueryOpts{ShowAlert: true, Text: "All Of Your Connections Were Cleared :)"},
211 | )
212 | }
213 |
214 | return nil
215 | }
216 |
217 | // Function to handle /diconnect command
218 | //
219 | //nolint:errcheck // no need
220 | func Disconnect(bot *gotgbot.Bot, update *ext.Context) error {
221 | if update.Message.From.Id == 0 {
222 | update.Message.Reply(
223 | bot,
224 | "Sorry Looks Like You Are Anonymous Use The Button Below To Prove Your Identity :)",
225 | &gotgbot.SendMessageOpts{ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{{{Text: "Disconnect Me", CallbackData: "cbconnect(dis)"}}}}},
226 | )
227 | } else {
228 | DB.DeleteConnection(update.Message.From.Id)
229 |
230 | update.Message.Reply(
231 | bot,
232 | "Any Existing Connections Were Cleared Successfully :)",
233 | &gotgbot.SendMessageOpts{},
234 | )
235 | }
236 |
237 | return nil
238 | }
239 |
--------------------------------------------------------------------------------
/plugins/dispatcher.go:
--------------------------------------------------------------------------------
1 | // (c) Jisin0
2 |
3 | package plugins
4 |
5 | import (
6 | "fmt"
7 |
8 | "github.com/Jisin0/Go-Filter-Bot/utils"
9 | "github.com/Jisin0/Go-Filter-Bot/utils/customfilters"
10 | "github.com/PaulSonOfLars/gotgbot/v2"
11 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
12 | "github.com/PaulSonOfLars/gotgbot/v2/ext/handlers"
13 | "github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters/callbackquery"
14 | "github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters/message"
15 | )
16 |
17 | const (
18 | filterHandlerGroup = 1 // handler group for filters
19 | basicCommandsGroup = 2 // handler group for basic commands
20 | commandHandlerGroup = 3 // handler group for other cammands
21 | callbackHandlerGroup = 4 // handler group for callbacks
22 | miscHandlerGroup = 5 // handler group for everything else
23 | )
24 |
25 | // Create updater and Dispatcher.
26 | var Dispatcher = ext.NewDispatcher(&ext.DispatcherOpts{
27 | // If an error is returned by a handler, log it and continue going.
28 | Error: func(b *gotgbot.Bot, ctx *ext.Context, err error) ext.DispatcherAction {
29 | fmt.Println("an error occurred while handling update:", err.Error())
30 | return ext.DispatcherActionNoop
31 | },
32 | MaxRoutines: ext.DefaultMaxRoutines,
33 | })
34 |
35 | func init() {
36 | Dispatcher.AddHandlerToGroup(handlers.NewMessage(customfilters.PrivateOrGroup, FilterHandler), filterHandlerGroup)
37 |
38 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("start", Start), basicCommandsGroup)
39 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("about", About), basicCommandsGroup)
40 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("help", Help), basicCommandsGroup)
41 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("stats", Stats), basicCommandsGroup)
42 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("id", GetID), basicCommandsGroup)
43 |
44 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("filter", NewFilter), commandHandlerGroup)
45 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("gfilter", NewFilter), commandHandlerGroup)
46 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("filters", AllMfilters), commandHandlerGroup)
47 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("stop", StopMfilter), commandHandlerGroup)
48 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("gstop", StopGfilter), commandHandlerGroup)
49 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("connect", Connect), commandHandlerGroup)
50 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("disconnect", Disconnect), commandHandlerGroup)
51 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("startglobal", StartGlobal), commandHandlerGroup)
52 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("gfilters", Gfilters), commandHandlerGroup)
53 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("broadcast", Broadcast), commandHandlerGroup)
54 | Dispatcher.AddHandlerToGroup(handlers.NewCommand("concast", Broadcast), commandHandlerGroup)
55 |
56 | Dispatcher.AddHandlerToGroup(handlers.NewCallback(callbackquery.Prefix("cbconnect("), CbConnect), callbackHandlerGroup)
57 | Dispatcher.AddHandlerToGroup(handlers.NewCallback(callbackquery.Prefix("stopf("), CbStop), callbackHandlerGroup)
58 | Dispatcher.AddHandlerToGroup(handlers.NewCallback(callbackquery.Prefix("edit("), CbEdit), callbackHandlerGroup)
59 | Dispatcher.AddHandlerToGroup(handlers.NewCallback(callbackquery.Prefix("alert("), CbAlert), callbackHandlerGroup)
60 | Dispatcher.AddHandlerToGroup(handlers.NewCallback(callbackquery.Equal("stats"), CbStats), callbackHandlerGroup)
61 |
62 | Dispatcher.AddHandlerToGroup(handlers.NewMessage(message.All, utils.RunListening), miscHandlerGroup)
63 | Dispatcher.AddHandlerToGroup(handlers.NewMyChatMember(nil, MyChatMember), miscHandlerGroup)
64 | }
65 |
--------------------------------------------------------------------------------
/plugins/filter.go:
--------------------------------------------------------------------------------
1 | // (c) Jisin0
2 |
3 | package plugins
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | "regexp"
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/Jisin0/Go-Filter-Bot/database"
13 | "github.com/Jisin0/Go-Filter-Bot/utils"
14 | "github.com/Jisin0/Go-Filter-Bot/utils/autodelete"
15 | "github.com/Jisin0/Go-Filter-Bot/utils/config"
16 | "github.com/Jisin0/Go-Filter-Bot/utils/customfilters"
17 | "github.com/PaulSonOfLars/gotgbot/v2"
18 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
19 | "go.mongodb.org/mongo-driver/bson"
20 | )
21 |
22 | // Regex expressions for callbacks and filtering
23 | var (
24 | buttonRegex *regexp.Regexp = regexp.MustCompile(`\[([^\[]+?)\]\((buttonurl|url|alert):(?:/{0,2})(.+?)\)`)
25 | parseRegex *regexp.Regexp = regexp.MustCompile(`^"([^"]+)"`)
26 | cbstopRegex *regexp.Regexp = regexp.MustCompile(`stopf\((.+)\)`)
27 | cbalertRegex *regexp.Regexp = regexp.MustCompile(`alert\((.+)\)`)
28 | )
29 |
30 | var DB *database.Database = database.NewDatabase()
31 |
32 | const (
33 | lenUniqueID = 15 // length of unique id string for filters
34 | globalNumber int64 = 101 // Number used as chat id for global filters. you could change it to anything you like but you will lose any existing gfilters
35 | maxKeyLength = 20 // maximum length for a keyword of a filter
36 | maxButttons = 5 // maximum number of buttons to scan for (should increase and test)
37 |
38 | filterSplitCount = 3 // number of subtrings into which input of /filter command should be split
39 | minButtonParseParams = 4 // Parse of a button should yield atleast this many values
40 |
41 | alertCacheDuration = 3000 // number of seconds an alert should be cache by the client
42 | )
43 |
44 | const (
45 | cbStopParamCount = 3 // number of parameters required for cbstop
46 | )
47 |
48 | // Manual filter function
49 | func MFilter(bot *gotgbot.Bot, ctx *ext.Context) error {
50 | var (
51 | chatID int64
52 | update = ctx.Message
53 | messageID = update.MessageId
54 | chatType = update.Chat.Type
55 | message = update.Text
56 | )
57 |
58 | if update.Caption != "" {
59 | message = update.Caption
60 | }
61 |
62 | switch chatType {
63 | case gotgbot.ChatTypePrivate:
64 | var ok bool
65 |
66 | chatID, ok = DB.GetConnection(update.Chat.Id)
67 | if !ok {
68 | return nil
69 | }
70 | case gotgbot.ChatTypeSupergroup, gotgbot.ChatTypeGroup:
71 | chatID = update.Chat.Id
72 | default:
73 | return nil
74 | }
75 |
76 | var results []*database.Filter
77 |
78 | fields := strings.Fields(message)
79 | if len(fields) <= 15 { // uses new method only if input has <=15 substrings
80 | results = DB.SearchMfilterNew(chatID, fields, config.MultiFilter)
81 | } else {
82 | results = DB.SearchMfilterClassic(chatID, message)
83 | }
84 |
85 | for _, f := range results {
86 | sendFilter(f, bot, update, chatID, messageID)
87 | }
88 |
89 | return nil
90 | }
91 |
92 | // Function to handle filter and gfilter commands
93 | //
94 | //nolint:errcheck // too many
95 | func NewFilter(bot *gotgbot.Bot, ctx *ext.Context) error {
96 | var (
97 | c int64
98 | update = ctx.Message
99 | )
100 |
101 | // I didnt wanna create a whole new function for gfilter so ...
102 | if strings.HasPrefix(update.Text, "/gfilter") {
103 | if !utils.IsAdmin(ctx.EffectiveUser.Id) {
104 | update.Reply(bot, "Thats an Admin-only Command :(", &gotgbot.SendMessageOpts{})
105 | return nil
106 | }
107 |
108 | c = globalNumber
109 | } else {
110 | // Verifying and getting connections for private chats
111 | var v bool
112 |
113 | c, v = customfilters.Verify(bot, ctx)
114 | if !v {
115 | return nil
116 | } else if c == 0 {
117 | c = ctx.Message.Chat.Id
118 | }
119 | }
120 |
121 | args := strings.SplitN(update.Text, " ", filterSplitCount)
122 | if len(args) < 2 && (update.ReplyToMessage == nil && len(args) < filterSplitCount) {
123 | update.Reply(
124 | bot,
125 | "Not Enough Parameters :(\n\nExample:- /filter hi hello
",
126 | &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML},
127 | )
128 |
129 | return nil
130 | }
131 |
132 | parse := ParseQuotes(strings.SplitN(update.Text, " ", 2)[1])
133 |
134 | key := strings.ToLower(parse[0])
135 |
136 | if len(key) > maxKeyLength {
137 | update.Reply(bot, fmt.Sprintf("Sorry The Length of the Key Can't be More than %d Characters !\nInput Key: %s
", maxKeyLength, key), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
138 | }
139 |
140 | e := DB.Mcol.FindOne(context.TODO(), bson.D{{Key: "group_id", Value: c}, {Key: "text", Value: key}})
141 | if e.Err() == nil {
142 | update.Reply(
143 | bot,
144 | fmt.Sprintf("It Looks Like Another Filter For %v
Has Already Been Saved In This Chat, If You Want To Stop It First Use The Button Below", key),
145 | &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
146 | {{Text: "Stop Filter", CallbackData: fmt.Sprintf("stopf(%v|%v|y)", key, "local")}},
147 | }}},
148 | )
149 |
150 | return nil
151 | }
152 |
153 | var (
154 | text = parse[1]
155 | button [][]map[string]string
156 | )
157 |
158 | if update.ReplyToMessage != nil {
159 | text += update.ReplyToMessage.OriginalHTML()
160 | text += update.ReplyToMessage.OriginalCaptionHTML()
161 |
162 | if update.ReplyToMessage.ReplyMarkup != nil {
163 | // Buttons are converted to maps to save to the database
164 | button = buttonToMap(ctx.Message.ReplyToMessage.ReplyMarkup.InlineKeyboard)
165 | }
166 | }
167 |
168 | uniqueID := utils.RandString(lenUniqueID)
169 |
170 | text, button, alert := parseButtons(text, uniqueID, button)
171 |
172 | // Finding media if any
173 | var (
174 | fileID string
175 | mediaType string
176 | )
177 |
178 | if msg := update.ReplyToMessage; msg != nil {
179 | switch {
180 | case msg.Document != nil:
181 | fileID = msg.Document.FileId
182 | mediaType = "document"
183 | case msg.Video != nil:
184 | fileID = msg.Video.FileId
185 | mediaType = "video"
186 | case msg.Audio != nil:
187 | fileID = msg.Audio.FileId
188 | mediaType = "audio"
189 | case msg.Sticker != nil:
190 | fileID = msg.Sticker.FileId
191 | mediaType = "sticker"
192 | case msg.Animation != nil:
193 | fileID = msg.Animation.FileId
194 | mediaType = "animation"
195 | case msg.Photo != nil:
196 | fileID = msg.Photo[len(msg.Photo)-1].FileId
197 | mediaType = "photo"
198 | }
199 | }
200 |
201 | f := &database.Filter{
202 | ID: uniqueID,
203 | ChatID: c,
204 | Text: key,
205 | Content: text,
206 | FileID: fileID,
207 | Markup: button,
208 | Alerts: alert,
209 | Length: len(key),
210 | MediaType: mediaType,
211 | }
212 |
213 | DB.SaveMfilter(f)
214 |
215 | _, err := update.Reply(
216 | bot,
217 | fmt.Sprintf("Successfully Saved A Manual Filter For %v
!", key),
218 | &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML},
219 | )
220 | if err != nil {
221 | fmt.Println(err)
222 | }
223 |
224 | return nil
225 | }
226 |
227 | func CbStop(bot *gotgbot.Bot, ctx *ext.Context) error {
228 | // Function to handle callbacks from confirm buttons when stopping a filter
229 | update := ctx.CallbackQuery
230 |
231 | c, v := customfilters.Verify(bot, ctx)
232 | if !v {
233 | return nil
234 | } else if c == 0 {
235 | c = update.Message.GetChat().Id
236 | }
237 |
238 | // Making sure the callback data is valid
239 | args := strings.Split(cbstopRegex.FindStringSubmatch(update.Data)[1], "|")
240 | if len(args) < cbStopParamCount {
241 | update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Bad Button :(", ShowAlert: true})
242 | return nil
243 | }
244 |
245 | var (
246 | key = args[0]
247 | ftype = args[1]
248 | erase = args[2]
249 | )
250 |
251 | if ftype == "local" {
252 | if erase == "y" {
253 | DB.DeleteMfilter(c, key)
254 | update.Message.EditText(bot, fmt.Sprintf("Manual Filter For %v
Was Deleted Successfully !", key), &gotgbot.EditMessageTextOpts{ParseMode: gotgbot.ParseModeHTML})
255 | } else if erase == "n" {
256 | // Unused till now, might use it later
257 | 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})
258 | }
259 | } else if ftype == "global" {
260 | DB.StopGfilter(c, key)
261 | update.Message.EditText(bot, fmt.Sprintf("Global Filter For %v
Has Been Stopped Successfully !", key), &gotgbot.EditMessageTextOpts{ParseMode: gotgbot.ParseModeHTML})
262 | }
263 |
264 | return nil
265 | }
266 |
267 | // Function to handle the stop command
268 | //
269 | //nolint:errcheck // too many
270 | func StopMfilter(bot *gotgbot.Bot, ctx *ext.Context) error {
271 | c, v := customfilters.Verify(bot, ctx)
272 | if !v {
273 | return nil
274 | } else if c == 0 {
275 | c = ctx.Message.Chat.Id
276 | }
277 |
278 | var (
279 | update = ctx.Message
280 | split = strings.SplitN(update.Text, " ", 2)
281 | key string
282 | )
283 |
284 | switch {
285 | case len(split) < 2 && update.Chat.Type == gotgbot.ChatTypePrivate:
286 | m := utils.Ask(bot, "Ok Now Send Me The Name OF The Filter You Would Like To Stop ...", ctx.EffectiveChat, ctx.EffectiveUser)
287 | if m == nil {
288 | return nil
289 | }
290 |
291 | key = m.Text
292 | case len(split) < 2:
293 | update.Reply(bot, "Whoops looks like you forgot to mention a filter to stop !", &gotgbot.SendMessageOpts{})
294 | default:
295 | key = split[1]
296 | }
297 |
298 | // Checking if theres a local/global filter for the key
299 | _, k := DB.GetMfilter(c, key)
300 | _, ok := DB.GetMfilter(globalNumber, key)
301 |
302 | // If there isnt local or global
303 | if !k && !ok {
304 | update.Reply(bot, fmt.Sprintf("I Couldnt Find Any Filter For %v
To Stop !", key), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
305 | return nil
306 | }
307 |
308 | // Both local and global
309 | switch {
310 | case k && ok:
311 | 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)}}}}
312 |
313 | _, 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 "+key+"
", &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: markup})
314 | if err != nil {
315 | fmt.Println(err)
316 | }
317 | case k:
318 | // Only local
319 | markup := gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{{{Text: "CONFIRM", CallbackData: fmt.Sprintf("stopf(%v|local|y)", key)}}}}
320 |
321 | _, err := update.Reply(bot, "Please Press The Button Below To Confirm Deletion Of Manual Filter For "+key+"
", &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: markup})
322 | if err != nil {
323 | fmt.Println(err)
324 | }
325 | case ok:
326 | // Only global
327 | markup := gotgbot.InlineKeyboardMarkup{InlineKeyboard: [][]gotgbot.InlineKeyboardButton{{{Text: "CONFIRM", CallbackData: fmt.Sprintf("stopf(%v|global|y)", key)}}}}
328 |
329 | _, err := update.Reply(bot, "Please Press The Button Below To Stop Global Filter For "+key+"
", &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML, ReplyMarkup: markup})
330 | if err != nil {
331 | fmt.Println(err)
332 | }
333 | }
334 |
335 | return nil
336 | }
337 |
338 | func AllMfilters(bot *gotgbot.Bot, ctx *ext.Context) error {
339 | // Function to handle the /filters command
340 | update := ctx.Message
341 | c := update.Chat.Id
342 |
343 | if update.Chat.Type == gotgbot.ChatTypePrivate {
344 | if i, k := DB.GetConnection(update.From.Id); k {
345 | c = i
346 | }
347 | }
348 |
349 | text := DB.StringMfilter(c)
350 |
351 | _, err := update.Reply(bot, "Lɪsᴛ ᴏғ ғɪʟᴛᴇʀs ғᴏʀ ᴛʜɪs ᴄʜᴀᴛ :\n"+text, &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
352 | if err != nil {
353 | fmt.Printf("allmfilters: %v\n", err)
354 | }
355 |
356 | return nil
357 | }
358 |
359 | // Function to handle callbacks from alert button in saved filters
360 | //
361 | //nolint:errcheck // unnecessary
362 | func CbAlert(bot *gotgbot.Bot, ctx *ext.Context) error {
363 | update := ctx.CallbackQuery
364 |
365 | args := strings.Split(cbalertRegex.FindStringSubmatch(update.Data)[1], "|")
366 | if len(args) < 2 {
367 | update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Bad Request !", ShowAlert: true})
368 | } else {
369 | uniqueID := args[0]
370 |
371 | index, err := strconv.Atoi(args[1])
372 | if err != nil {
373 | update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Bad Request !", ShowAlert: true})
374 | } else {
375 | text := DB.GetAlert(uniqueID, index)
376 | update.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: text, ShowAlert: true, CacheTime: alertCacheDuration})
377 | }
378 | }
379 |
380 | return nil
381 | }
382 |
383 | func buttonToMap(btn [][]gotgbot.InlineKeyboardButton) [][]map[string]string {
384 | // Convert a button into a map
385 | var (
386 | totalButtons [][]map[string]string
387 | rowButtons []map[string]string
388 | )
389 |
390 | for _, i := range btn {
391 | rowButtons = []map[string]string{}
392 |
393 | for _, j := range i {
394 | b := map[string]string{"Text": j.Text}
395 |
396 | switch {
397 | case j.CallbackData != "":
398 | b["CallbackData"] = j.CallbackData
399 | case j.Url != "":
400 | b["Url"] = j.Url
401 | default:
402 | continue
403 | }
404 |
405 | rowButtons = append(rowButtons, b)
406 | }
407 |
408 | totalButtons = append(totalButtons, rowButtons)
409 | }
410 |
411 | return totalButtons
412 | }
413 |
414 | func mapToButton(data [][]map[string]string) [][]gotgbot.InlineKeyboardButton {
415 | // Convert a map back into button
416 | var totalButtons [][]gotgbot.InlineKeyboardButton
417 |
418 | for _, i := range data {
419 | var rowButtons []gotgbot.InlineKeyboardButton
420 |
421 | for _, b := range i {
422 | text := b["Text"]
423 |
424 | if u, k := b["Url"]; k {
425 | rowButtons = append(rowButtons, gotgbot.InlineKeyboardButton{Text: text, Url: u})
426 | } else if c, k := b["CallbackData"]; k {
427 | rowButtons = append(rowButtons, gotgbot.InlineKeyboardButton{Text: text, CallbackData: c})
428 | }
429 | }
430 |
431 | totalButtons = append(totalButtons, rowButtons)
432 | }
433 |
434 | return totalButtons
435 | }
436 |
437 | func ParseQuotes(text string) []string {
438 | res := parseRegex.FindStringSubmatch(text)
439 | if len(res) > 0 {
440 | return []string{res[1], strings.Replace(text, "\""+res[1]+"\"", "", 1)} // very hacky workaround by adding quotes //TODO: update regex to include quotes
441 | } else {
442 | split := strings.SplitN(text, " ", 2)
443 | return []string{split[0], strings.Replace(text, split[0], "", 1)}
444 | }
445 | }
446 |
447 | func parseButtons(text, uniqueID string, totalButtons [][]map[string]string) (messageText string, buttons [][]map[string]string, alertText []string) {
448 | var (
449 | returnText = text
450 | rowButtons []map[string]string
451 | alert []string
452 | )
453 |
454 | for _, rows := range strings.Split(text, "\n") {
455 | for _, m := range buttonRegex.FindAllStringSubmatch(rows, maxButttons) {
456 | if len(m) < minButtonParseParams {
457 | continue
458 | }
459 |
460 | if m[2] == "url" || m[2] == "buttonurl" {
461 | rowButtons = append(rowButtons, map[string]string{"Text": m[1], "Url": m[3]})
462 | } else if m[2] == "alert" || m[2] == "buttonalert" {
463 | alert = append(alert, m[3])
464 | rowButtons = append(rowButtons, map[string]string{"Text": m[1], "CallbackData": fmt.Sprintf("alert(%v|%v)", uniqueID, len(alert)-1)})
465 | }
466 |
467 | returnText = strings.Replace(returnText, m[0], "", 1)
468 | }
469 |
470 | if len(rowButtons) > 0 {
471 | totalButtons = append(totalButtons, rowButtons)
472 | }
473 |
474 | rowButtons = []map[string]string{}
475 | }
476 |
477 | return strings.Trim(returnText, " "), totalButtons, alert
478 | }
479 |
480 | // sendFilter sends the data from filter f to the chatID.
481 | func sendFilter(f *database.Filter, bot *gotgbot.Bot, update *gotgbot.Message, chatID, messageID int64) {
482 | // Find buttons saved for the filter and convert it from map
483 | var (
484 | buttons = mapToButton(f.Markup)
485 | markup = gotgbot.InlineKeyboardMarkup{InlineKeyboard: buttons}
486 | content = f.Content
487 | err error
488 | m *gotgbot.Message
489 | )
490 |
491 | mediaType := f.MediaType
492 | if mediaType != "" {
493 | fileID := gotgbot.InputFileByID(f.FileID)
494 |
495 | switch mediaType {
496 | case "document":
497 | m, err = bot.SendDocument(chatID, fileID, &gotgbot.SendDocumentOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: markup, ParseMode: gotgbot.ParseModeHTML})
498 | case "sticker":
499 | m, err = bot.SendSticker(chatID, fileID, &gotgbot.SendStickerOpts{ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: markup})
500 | case "video":
501 | m, err = bot.SendVideo(chatID, fileID, &gotgbot.SendVideoOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: markup, ParseMode: gotgbot.ParseModeHTML})
502 | case "photo":
503 | m, err = bot.SendPhoto(chatID, fileID, &gotgbot.SendPhotoOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: markup, ParseMode: gotgbot.ParseModeHTML})
504 | case "audio":
505 | m, err = bot.SendAudio(chatID, fileID, &gotgbot.SendAudioOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: markup, ParseMode: gotgbot.ParseModeHTML})
506 | case "animation":
507 | m, err = bot.SendAnimation(chatID, fileID, &gotgbot.SendAnimationOpts{Caption: content, ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: markup, ParseMode: gotgbot.ParseModeHTML})
508 | default:
509 | fmt.Println("Unknown media type " + mediaType)
510 | }
511 | } else {
512 | m, err = update.Reply(bot, content, &gotgbot.SendMessageOpts{ReplyParameters: &gotgbot.ReplyParameters{MessageId: messageID}, ReplyMarkup: markup, ParseMode: gotgbot.ParseModeHTML})
513 | }
514 |
515 | if err != nil {
516 | fmt.Println(err)
517 | fmt.Println(f)
518 | return
519 | }
520 |
521 | if m != nil && AutoDelete > 0 {
522 | err := autodelete.InsertAutodel(autodelete.AutodelData{ChatID: chatID, MessageID: m.MessageId}, AutoDelete)
523 | if err != nil {
524 | fmt.Printf("sendfilter: %v\n", err)
525 | }
526 | }
527 | }
528 |
--------------------------------------------------------------------------------
/plugins/gfilter.go:
--------------------------------------------------------------------------------
1 | // (c) Jisin0
2 |
3 | package plugins
4 |
5 | import (
6 | "fmt"
7 | "strings"
8 |
9 | "github.com/Jisin0/Go-Filter-Bot/database"
10 | "github.com/Jisin0/Go-Filter-Bot/utils"
11 | "github.com/Jisin0/Go-Filter-Bot/utils/config"
12 | "github.com/Jisin0/Go-Filter-Bot/utils/customfilters"
13 | "github.com/PaulSonOfLars/gotgbot/v2"
14 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
15 | )
16 |
17 | func GFilter(bot *gotgbot.Bot, ctx *ext.Context) error {
18 | var (
19 | chatID int64
20 | update = ctx.Message
21 | messageID = update.MessageId
22 | )
23 |
24 | switch chatType := update.Chat.Type; chatType {
25 | case gotgbot.ChatTypePrivate:
26 | var ok bool
27 |
28 | chatID, ok = DB.GetConnection(update.From.Id)
29 | if !ok {
30 | return nil
31 | }
32 | case gotgbot.ChatTypeSupergroup, gotgbot.ChatTypeGroup:
33 | chatID = update.Chat.Id
34 | default:
35 | return nil
36 | }
37 |
38 | var message string
39 | switch {
40 | case update.Text != "":
41 | message = update.Text
42 | case update.Caption != "":
43 | message = update.Caption
44 | default:
45 | return nil
46 | }
47 |
48 | stopped := DB.GetCachedSetting(chatID).Stopped
49 |
50 | var results []*database.Filter
51 |
52 | fields := strings.Fields(message)
53 | if len(fields) <= 15 { // uses new method only if input has <=15 substrings
54 | results = DB.SearchMfilterNew(globalNumber, fields, config.MultiFilter)
55 | } else {
56 | results = DB.SearchMfilterClassic(globalNumber, message)
57 | }
58 |
59 | for _, f := range results {
60 | if utils.Contains(stopped, f.Text) {
61 | continue
62 | }
63 |
64 | sendFilter(f, bot, update, chatID, messageID)
65 | }
66 |
67 | return nil
68 | }
69 |
70 | // Function to handle the startglobal command
71 | //
72 | //nolint:errcheck // hmm
73 | func StartGlobal(bot *gotgbot.Bot, ctx *ext.Context) error {
74 | update := ctx.Message
75 |
76 | c, v := customfilters.Verify(bot, ctx)
77 | if !v {
78 | return nil
79 | }
80 |
81 | if c == 0 {
82 | c = ctx.Message.Chat.Id
83 | }
84 |
85 | split := strings.SplitN(update.Text, " ", 2)
86 | if split[1] == "" {
87 | update.Reply(bot, "Bad Usage No Keyword Provided :(", &gotgbot.SendMessageOpts{})
88 | } else {
89 | key := split[1]
90 |
91 | _, ok := DB.GetMfilter(globalNumber, key)
92 | if !ok {
93 | update.Reply(bot, fmt.Sprintf("No Global Filter For %v Was Found To Restart !", key), &gotgbot.SendMessageOpts{})
94 | } else {
95 | for _, k := range DB.GetCachedSetting(c).Stopped {
96 | if k == key {
97 | DB.StartGfilter(c, key)
98 | update.Reply(bot, fmt.Sprintf("Restarted Global Filter For %v Successfully !", key), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
99 |
100 | return nil
101 | }
102 | }
103 |
104 | update.Reply(bot, fmt.Sprintf("You Havent Stopped Any Global Filter For %v :(", key), &gotgbot.SendMessageOpts{})
105 | }
106 | }
107 |
108 | return nil
109 | }
110 |
111 | func Gfilters(bot *gotgbot.Bot, ctx *ext.Context) error {
112 | // Function to handle /gfilters function
113 | text := DB.StringMfilter(globalNumber)
114 |
115 | _, err := ctx.Message.Reply(bot, "Aʟʟ ғɪʟᴛᴇʀs sᴀᴠᴇᴅ ғᴏʀ ɢʟᴏʙᴀʟ ᴜsᴀɢᴇ :\n"+text, &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
116 | if err != nil {
117 | fmt.Printf("gfilters: %v\n", err)
118 | }
119 |
120 | return nil
121 | }
122 |
123 | // Function to handle the gstop command
124 | //
125 | //nolint:errcheck // too many
126 | func StopGfilter(bot *gotgbot.Bot, ctx *ext.Context) error {
127 | update := ctx.Message
128 |
129 | if !utils.IsAdmin(ctx.EffectiveUser.Id) {
130 | update.Reply(bot, "Only bot admins can use this command !", &gotgbot.SendMessageOpts{})
131 | return nil
132 | }
133 |
134 | var (
135 | split = strings.SplitN(update.Text, " ", 2)
136 | key string
137 | )
138 |
139 | switch {
140 | case len(split) < 2:
141 | m := utils.Ask(bot, "Ok Now Send Me The Name OF The Filter You Would Like To Stop ...", ctx.EffectiveChat, ctx.EffectiveUser)
142 | if m == nil {
143 | return nil
144 | }
145 |
146 | key = m.Text
147 | default:
148 | key = split[1]
149 | }
150 |
151 | // Checking if theres a global filter for the key
152 | _, ok := DB.GetMfilter(globalNumber, key)
153 |
154 | // If there isnt local or global
155 | if !ok {
156 | update.Reply(bot, fmt.Sprintf("I Couldnt Find Any Global Filter For %v
To Stop !", key), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
157 | return nil
158 | }
159 |
160 | DB.DeleteMfilter(globalNumber, key)
161 | update.Reply(bot, fmt.Sprintf("Global Filter For %v Was Stopped Successfully !", key), &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
162 |
163 | return nil
164 | }
165 |
--------------------------------------------------------------------------------
/plugins/newchat.go:
--------------------------------------------------------------------------------
1 | package plugins
2 |
3 | import (
4 | "github.com/Jisin0/Go-Filter-Bot/utils/autodelete"
5 | "github.com/PaulSonOfLars/gotgbot/v2"
6 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
7 | )
8 |
9 | // MyChatMember handles the MyChatMember event and saves the chat id in db.
10 | func MyChatMember(bot *gotgbot.Bot, ctx *ext.Context) error {
11 | if ctx.MyChatMember == nil || ctx.MyChatMember.NewChatMember.GetUser().Id != bot.Id {
12 | // new chat member not present or isnt the bot
13 | return nil
14 | }
15 |
16 | if chatType := ctx.MyChatMember.Chat.Type; chatType != gotgbot.ChatTypeSupergroup && chatType != gotgbot.ChatTypeGroup {
17 | return nil
18 | }
19 |
20 | DB.SetDefaultSettings(ctx.MyChatMember.Chat.Id)
21 |
22 | text := `👋ᴛʜᴀɴᴋ ʏᴏᴜ ғᴏʀ ᴀᴅᴅɪɴɢ ᴍᴇ ᴛᴏ ʏᴏᴜʀ ɢʀᴏᴜᴘ.
23 | I ᴄᴀɴ'ᴛ ᴡᴀɪᴛ ᴛᴏ sᴛᴀʀᴛ ʜᴇʟᴘɪɴɢ ʏᴏᴜ ᴏᴜᴛ, ᴍᴀᴋᴇ sᴜʀᴇ ʏᴏᴜ'ᴠᴇ ᴍᴀᴅᴇ ᴍᴇ ᴀɴ ᴀᴅᴍɪɴ ғɪʀsᴛ !`
24 |
25 | m, err := bot.SendMessage(ctx.MyChatMember.Chat.Id, text, &gotgbot.SendMessageOpts{ParseMode: "HTML"})
26 | if err == nil {
27 | autodelete.InsertAutodel(autodelete.AutodelData{ChatID: m.Chat.Id, MessageID: m.MessageId}, 240) // autodel in 2 mins
28 | }
29 |
30 | return nil
31 | }
32 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | /filter "keyword" text
or
29 | Rᴇᴘʟʏ ᴛᴏ ᴀ ᴍᴇssᴀɢᴇ ->/filter "keyword"
30 | Usᴀɢᴇ
31 | /filter "hi" hello
32 | [hello
] -> Reply -> /filter hi
33 |
34 | Sᴛᴏᴘ ғɪʟᴛᴇʀ :
35 |
36 | Fᴏʀᴍᴀᴛ
37 | /stop "keyword"
38 | Usᴀɢᴇ
39 | /stop "hi"
40 |
41 | Vɪᴇᴡ ғɪʟᴛᴇʀs :
42 | /filters
43 | `,
44 |
45 | "GF": `
46 | Gʟᴏʙᴀʟ ғɪʟᴛᴇʀs ᴀʀᴇ ᴍᴀɴᴜᴀʟ ғɪʟᴛᴇʀs sᴀᴠᴇᴅ ʙʏ ʙᴏᴛ ᴀᴅᴍɪɴs ᴛʜᴀᴛ ᴡᴏʀᴋ ɪɴ ᴀʟʟ ᴄʜᴀᴛs. Tʜᴇʏ ᴘʀᴏᴠɪᴅᴇ ʟᴀᴛᴇsᴛ ᴍᴏᴠɪᴇs ɪɴ ᴀ ᴇᴀsʏ ᴛᴏ ᴜsᴇ ғᴏʀᴍᴀᴛ.
47 |
48 | Sᴛᴏᴘ ғɪʟᴛᴇʀ :
49 |
50 | Fᴏʀᴍᴀᴛ
51 | /stop "keyword"
52 | Usᴀɢᴇ
53 | /stop "et"
54 |
55 | Vɪᴇᴡ ғɪʟᴛᴇʀs :
56 | /gfilters
57 | `,
58 | "CONNECT": `
59 | Cᴏɴɴᴇᴄᴛɪᴏɴs ᴀʟʟᴏᴡ ʏᴏᴜ ᴛᴏ ᴍᴀɴᴀɢᴇ ʏᴏᴜʀ ɢʀᴏᴜᴘ ʜᴇʀᴇ ɪɴ ᴘᴍ ɪɴsᴛᴇᴀᴅ ᴏғ sᴇɴᴅɪɴɢ ᴛʜᴏsᴇ ᴄᴏᴍᴍᴀɴᴅs ᴘᴜʙʟɪᴄʟʏ ɪɴ ᴛʜᴇ ɢʀᴏᴜᴘ ⠘⁾
60 |
61 | Cᴏɴɴᴇᴄᴛ :
62 | -> Fɪʀsᴛ ɢᴇᴛ ʏᴏᴜʀ ɢʀᴏᴜᴘ's ɪᴅ ʙʏ sᴇɴᴅɪɴɢ /id ɪɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ
63 | -> /connect [group_id]
64 |
65 | Dɪsᴄᴏɴɴᴇᴄᴛ :
66 | /disconnect
67 | `,
68 |
69 | "BROADCAST": `
70 | The broadcast feature allows bot admins to broadcast a message to all of the bot's users.
71 |
72 | Broadcasts are of two types :
73 | ~ Full Broadcast - Broadcast to all of the bot users /broadcast
74 | ~ Concast - Broadcast to only users who are connected to a chat /concast
75 | `,
76 |
77 | "HELP": `
78 | To know how to use my modules use the buttons below to get all my commands with usage examples !
79 | `,
80 |
81 | "BTN": `
82 | Here's a format of how you can add buttons to filters.
83 | Buttons are split into different rows by using a new line.
84 |
85 | URL Button
86 | [Button Text](url:https://example.com)
87 |
88 | Alert Button
89 | [Button Text](alert:This is an alert)
90 | `,
91 | }
92 |
93 | var BUTTONS map[string][][]gotgbot.InlineKeyboardButton = map[string][][]gotgbot.InlineKeyboardButton{
94 | "START": {
95 | {
96 | {Text: "About", CallbackData: "edit(ABOUT)"},
97 | {Text: "Help", CallbackData: "edit(HELP)"},
98 | },
99 | },
100 | "ABOUT": {
101 | {
102 | {Text: "Home", CallbackData: "edit(START)"},
103 | {Text: "Stats", CallbackData: "stats"},
104 | }, {
105 | {Text: "Source 🔗", Url: "https://github.com/Jisin0/Go-Filter-Bot"},
106 | },
107 | },
108 | "STATS": {
109 | {
110 | {Text: "⬅ Back", CallbackData: "edit(ABOUT)"},
111 | {Text: "Refresh 🔄", CallbackData: "stats"},
112 | },
113 | },
114 | "HELP": {
115 | {{Text: "Fɪʟᴛᴇʀ", CallbackData: "edit(MF)"},
116 | {Text: "Gʟᴏʙᴀʟ", CallbackData: "edit(GF)"},
117 | }, {
118 | {Text: "Cᴏɴɴᴇᴄᴛ", CallbackData: "edit(CONNECT)"}, {Text: "Broadcast", CallbackData: "edit(BROADCAST)"},
119 | },
120 | {{Text: "Bᴀᴄᴋ ➔", CallbackData: "edit(START)"}},
121 | },
122 | "MF": {{
123 | {Text: "ʙᴜᴛᴛᴏɴs", CallbackData: "edit(BTN)"},
124 | {Text: "bᴀᴄᴋ ➔", CallbackData: "edit(HELP)"},
125 | }},
126 | "BTN": {{{Text: "bᴀᴄᴋ ➔", CallbackData: "edit(MF)"}}},
127 | }
128 |
--------------------------------------------------------------------------------
/utils/config/environment.go:
--------------------------------------------------------------------------------
1 | // (c) Jisin0
2 |
3 | package config
4 |
5 | import (
6 | "fmt"
7 | "os"
8 | "strconv"
9 | "strings"
10 |
11 | "github.com/joho/godotenv"
12 | )
13 |
14 | const (
15 | stringTrue = "true"
16 | defaultBatchLimit = 50
17 | defaultPort = "10000"
18 | )
19 |
20 | var (
21 | BotToken string // bot token obtained from @botfather
22 | MongodbURI string // mongodb uri
23 | Admins []int64 // admins list
24 | MultiFilter bool // indicates wether multiple results should be given for a single message
25 | AutoDelete int64 // minutes after which msgs should be auto deleted
26 | Port string // port on which the webserver should run
27 | )
28 |
29 | func init() {
30 | err := godotenv.Load()
31 | if err == nil {
32 | fmt.Println("configs loaded from .env file")
33 | }
34 |
35 | BotToken = os.Getenv("BOT_TOKEN")
36 | Admins = int64ListEnviron("ADMINS")
37 | MongodbURI = os.Getenv("MONGODB_URI")
38 | MultiFilter = strings.ToLower(os.Getenv("MULTI_FILTER")) == stringTrue
39 | AutoDelete = int64Environ("AUTO_DELETE")
40 | Port = stringEnviron("PORT", defaultPort)
41 | }
42 |
43 | // int64Environ gets a environment variable as an int64.
44 | func int64Environ(envVar string, defaultVal ...int64) int64 {
45 | s := os.Getenv(envVar)
46 | if s == "" {
47 | if len(defaultVal) > 0 {
48 | return defaultVal[0]
49 | }
50 |
51 | return 0
52 | }
53 |
54 | i, err := strconv.ParseInt(s, 10, 64)
55 | if err != nil {
56 | fmt.Printf("config.int64environ: failed to parse %s: %v\n", envVar, err)
57 | }
58 |
59 | return i
60 | }
61 |
62 | // stringEnviron gets a environment variable.
63 | func stringEnviron(envVar string, defaultVal ...string) string {
64 | s := os.Getenv(envVar)
65 | if s == "" {
66 | if len(defaultVal) > 0 {
67 | return defaultVal[0]
68 | }
69 |
70 | return ""
71 | }
72 |
73 | return s
74 | }
75 |
76 | // int64ListEnviron returns a slice of int64 for an environment variable.
77 | func int64ListEnviron(envVar string) (result []int64) {
78 | s := os.Getenv(envVar)
79 | if s == "" {
80 | return result
81 | }
82 |
83 | for _, str := range strings.Split(s, " ") {
84 | i, err := strconv.ParseInt(str, 10, 64)
85 | if err != nil {
86 | fmt.Printf("config.int64listenviron: failed to parse %s: %v\n", envVar, err)
87 | }
88 |
89 | result = append(result, i)
90 | }
91 |
92 | return result
93 | }
94 |
--------------------------------------------------------------------------------
/utils/customfilters/customfilters.go:
--------------------------------------------------------------------------------
1 | // (c) Jisin0
2 |
3 | package customfilters
4 |
5 | import (
6 | "fmt"
7 |
8 | "github.com/Jisin0/Go-Filter-Bot/database"
9 | "github.com/PaulSonOfLars/gotgbot/v2"
10 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
11 | "github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters"
12 | )
13 |
14 | var DB *database.Database = database.NewDatabase()
15 | var CachedAdmins map[int64][]int64 = map[int64][]int64{}
16 |
17 | func Listen(m *gotgbot.Message) filters.Message {
18 | return func(msg *gotgbot.Message) bool {
19 | return m.From.Id == msg.From.Id && m.MessageId < msg.MessageId && m.Chat.Id == msg.Chat.Id && msg.Text != " "
20 | }
21 | }
22 |
23 | func PrivateOrGroup(msg *gotgbot.Message) bool {
24 | // A Function To Filter Group & SuperGroup Message
25 | return msg.Chat.Type == gotgbot.ChatTypeSupergroup || msg.Chat.Type == gotgbot.ChatTypeGroup || msg.Chat.Type == gotgbot.ChatTypePrivate
26 | }
27 |
28 | func Chats(chatID []int64) filters.Message {
29 | return func(msg *gotgbot.Message) bool {
30 | for _, c := range chatID {
31 | if c == msg.Chat.Id {
32 | return true
33 | }
34 | }
35 |
36 | return false
37 | }
38 | }
39 |
40 | //nolint:errcheck // too lazy
41 | func Verify(bot *gotgbot.Bot, ctx *ext.Context) (int64, bool) {
42 | var (
43 | userID int64
44 | msg gotgbot.MaybeInaccessibleMessage
45 | )
46 |
47 | if ctx.CallbackQuery != nil {
48 | msg = ctx.CallbackQuery.Message
49 | userID = ctx.CallbackQuery.From.Id
50 | } else {
51 | msg = ctx.Message
52 | userID = msg.(*gotgbot.Message).From.Id
53 | }
54 |
55 | chatType := msg.GetChat().Type
56 | chatID := msg.GetChat().Id
57 |
58 | var c int64
59 |
60 | switch chatType {
61 | case gotgbot.ChatTypeSupergroup, gotgbot.ChatTypeGroup:
62 | if userID == 0 {
63 | bot.SendMessage(
64 | chatID,
65 | "Sorry It Looks Like You Are Anonymous Please Connect From Pm And Use Me Or Turn Off Anonymous :(",
66 | &gotgbot.SendMessageOpts{
67 | ReplyParameters: &gotgbot.ReplyParameters{
68 | MessageId: msg.GetMessageId(),
69 | },
70 | },
71 | )
72 |
73 | return c, false
74 | }
75 |
76 | cachedAdmins, ok := CachedAdmins[chatID]
77 | if !ok {
78 | admins, e := msg.GetChat().GetAdministrators(bot, &gotgbot.GetChatAdministratorsOpts{})
79 | if e != nil {
80 | return c, false
81 | }
82 |
83 | var newAdmins []int64
84 |
85 | for _, admin := range admins {
86 | newAdmins = append(newAdmins, admin.GetUser().Id)
87 | }
88 |
89 | CachedAdmins[chatID] = newAdmins
90 |
91 | for _, admin := range newAdmins {
92 | if userID == admin {
93 | return c, true
94 | }
95 | }
96 |
97 | if ctx.CallbackQuery == nil {
98 | bot.SendMessage(
99 | chatID,
100 | "Who dis non-admin telling me what to do !",
101 | &gotgbot.SendMessageOpts{
102 | ReplyParameters: &gotgbot.ReplyParameters{
103 | MessageId: msg.GetMessageId(),
104 | },
105 | },
106 | )
107 | } else {
108 | ctx.CallbackQuery.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Who dis non-admin telling me what to do !", ShowAlert: true})
109 | }
110 |
111 | return c, false
112 | } else {
113 | for _, admin := range cachedAdmins {
114 | if userID == admin {
115 | return c, true
116 | }
117 | }
118 |
119 | if ctx.CallbackQuery == nil {
120 | bot.SendMessage(
121 | chatID,
122 | "Hey You're Not An Admin, If You Are A New Admin Use The /updateadmins Command To Update Current List !",
123 | &gotgbot.SendMessageOpts{
124 | ReplyParameters: &gotgbot.ReplyParameters{
125 | MessageId: msg.GetMessageId(),
126 | },
127 | },
128 | )
129 | } else {
130 | ctx.CallbackQuery.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{Text: "Who dis non-admin telling me what to do !", ShowAlert: true})
131 | }
132 |
133 | return c, false
134 | }
135 | case gotgbot.ChatTypePrivate:
136 | c, ok := DB.GetConnection(userID)
137 | if !ok {
138 | bot.SendMessage(
139 | chatID,
140 | "Please connect to a chat first to use this operation !",
141 | &gotgbot.SendMessageOpts{
142 | ReplyParameters: &gotgbot.ReplyParameters{
143 | MessageId: msg.GetMessageId(),
144 | },
145 | },
146 | )
147 |
148 | return c, false
149 | }
150 |
151 | return c, true
152 | default:
153 | fmt.Println("Unknown ChatType ", chatType)
154 | return c, false
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/utils/format.go:
--------------------------------------------------------------------------------
1 | // (c) Jisin0
2 |
3 | package utils
4 |
5 | import (
6 | "math/rand"
7 | "time"
8 |
9 | "github.com/Jisin0/Go-Filter-Bot/utils/config"
10 | )
11 |
12 | // Character set to generate a random string
13 | const charset = "abcdefghijklmnopqrstuvwxyz" +
14 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
15 |
16 | func RandString(length int) string {
17 | var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
18 |
19 | b := make([]byte, length)
20 | for i := range b {
21 | b[i] = charset[seededRand.Intn(len(charset))]
22 | }
23 |
24 | return string(b)
25 | }
26 |
27 | // IsAdmin returns wether the user is a global admin(from env).
28 | func IsAdmin(u int64) bool {
29 | for _, n := range config.Admins {
30 | if u == n {
31 | return true
32 | }
33 | }
34 |
35 | return false
36 | }
37 |
38 | // Checks if a string slice contains an item.
39 | func Contains(l []string, v string) bool {
40 | for _, i := range l {
41 | if i == v {
42 | return true
43 | }
44 | }
45 |
46 | return false
47 | }
48 |
--------------------------------------------------------------------------------
/utils/listen.go:
--------------------------------------------------------------------------------
1 | // (c) Jisin0
2 |
3 | package utils
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | "time"
9 |
10 | "github.com/PaulSonOfLars/gotgbot/v2"
11 | "github.com/PaulSonOfLars/gotgbot/v2/ext"
12 | "github.com/PaulSonOfLars/gotgbot/v2/ext/handlers"
13 | "github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters"
14 | )
15 |
16 | var Listening []Listeners
17 |
18 | const (
19 | fiveMinutes = time.Minute * 5
20 | )
21 |
22 | type Listeners struct {
23 | // Filter which checks if message is a match
24 | Filter filters.Message
25 | // Function to be called if filter matches
26 | Callback handlers.Response
27 | // Unix timestamp at which the handler was added (Timeout after 300s)
28 | AddedTime int64
29 | // Channel to which message is passed
30 | Channel chan *gotgbot.Message
31 | }
32 |
33 | // Checks if update matches any saved listen filters
34 | func RunListening(bot *gotgbot.Bot, update *ext.Context) error {
35 | for i, u := range Listening {
36 | if !u.Filter(update.Message) {
37 | continue
38 | }
39 |
40 | // Delete handler from slice
41 | Listening[i] = Listening[len(Listening)-1] // Copy last element to index i.
42 | Listening[len(Listening)-1] = Listeners{} // Erase last element (write zero value).
43 | Listening = Listening[:len(Listening)-1] // Completely Remove That Last Value
44 |
45 | // Send message to channel
46 | u.Channel <- update.Message
47 | }
48 |
49 | if update.Message.ForwardOrigin != nil && update.Message.ForwardOrigin.MergeMessageOrigin().Chat != nil && update.Message.Chat.Type == gotgbot.ChatTypePrivate {
50 | text := fmt.Sprintf("This Message Was Forwarded From : %v
", update.Message.ForwardOrigin.MergeMessageOrigin().Chat.Id)
51 | update.Message.Reply(bot, text, &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML}) //nolint:errcheck // not a core feature
52 | }
53 |
54 | return nil
55 | }
56 |
57 | var ListenTimeout error
58 |
59 | // Listens for a message that matches the given filter.
60 | //
61 | // ctx - Context with ideally a deadline set
62 | // filter - The filter that the message must match (use customfilters package for prebuilts)
63 | //
64 | // Returns either the message if it was received or a ListenTimeout error
65 | func ListenMessage(ctx context.Context, filter filters.Message) (*gotgbot.Message, error) {
66 | // Make a channel
67 | c := make(chan *gotgbot.Message, 1)
68 |
69 | // Add details to listening slice
70 | Listening = append(Listening, Listeners{Filter: filter, Channel: c})
71 |
72 | // Listen for either a message or timeout
73 | select {
74 | case <-ctx.Done():
75 | return nil, ListenTimeout
76 | case m := <-c:
77 | return m, nil
78 | }
79 | }
80 |
81 | // Custom filter used when requesting a user or chat.
82 | //
83 | // chat - chat the message was sent to.
84 | // user - user who is expected to reply.
85 | // msgID - id of the last message in the chat
86 | func listenRequestFilter(chat *gotgbot.Chat, user *gotgbot.User, msgID int64) filters.Message {
87 | return func(msg *gotgbot.Message) bool {
88 | return user.Id == msg.From.Id && msgID < msg.MessageId && chat.Id == msg.Chat.Id
89 | }
90 | }
91 |
92 | // Sends a text message to the given chat and listens for a reply.
93 | //
94 | // bot - The bot that handles the conversation.
95 | // text - Content of the request message to be sent.
96 | // chat - The chat in which the conversion takes place
97 | // user - The user expected to answer
98 | func Ask(bot *gotgbot.Bot, text string, chat *gotgbot.Chat, user *gotgbot.User) *gotgbot.Message {
99 | // initial msg which's id is later used as the pinpoint of the converation's start
100 | firstM, err := bot.SendMessage(chat.Id, text, &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
101 | if err != nil {
102 | fmt.Println("error while asking ", err)
103 | return nil
104 | }
105 |
106 | // create context with 5 min timeout
107 | ctx, cancel := context.WithTimeout(context.Background(), fiveMinutes)
108 | defer cancel()
109 |
110 | // listen for matching messages
111 | msg, err := ListenMessage(ctx, listenRequestFilter(chat, user, firstM.MessageId))
112 | if err != nil {
113 | bot.SendMessage(chat.Id, "Request timed out ❗", &gotgbot.SendMessageOpts{ParseMode: gotgbot.ParseModeHTML})
114 | return nil
115 | }
116 |
117 | return msg
118 | }
119 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "build": {
3 | "env": {
4 | "GO_BUILD_FLAGS": "-ldflags '-s -w'"
5 | }
6 | },
7 |
8 | "routes": [
9 | {
10 | "src": "/bot/.*",
11 | "dest": "/api/bot.go"
12 | },
13 | {
14 | "src": "/",
15 | "dest": "/index.html"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------