• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Civil / tg-simple-regex-antispam / 13070263183

31 Jan 2025 09:45AM UTC coverage: 0.0%. Remained the same
13070263183

Pull #5

github

Civil
Refactor and improve bot

 * Fixed small bug in forward action
 * package statefulFilters renamed to chains
 * generic badgerdb interface
 * add filter that counts repeated messages
 * add filter that checks if message is a story
 * add filter that checks if message was marked as spam
Pull Request #5: some improvements

0 of 1422 new or added lines in 20 files covered. (0.0%)

21 existing lines in 3 files now uncovered.

0 of 3756 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/filters/chains/report/rule.go
1
package report
2

3
import (
4
        "bytes"
5
        "errors"
6
        "fmt"
7
        "strings"
8
        "sync"
9
        "time"
10

11
        "github.com/dgraph-io/badger/v4"
12
        "github.com/mymmrac/telego"
13
        "go.uber.org/zap"
14
        "google.golang.org/protobuf/proto"
15
        "google.golang.org/protobuf/types/known/timestamppb"
16

17
        actions "github.com/Civil/tg-simple-regex-antispam/actions/interfaces"
18
        "github.com/Civil/tg-simple-regex-antispam/bannedDB"
19
        "github.com/Civil/tg-simple-regex-antispam/filters/interfaces"
20
        "github.com/Civil/tg-simple-regex-antispam/filters/types/checkNeventsState"
21
        "github.com/Civil/tg-simple-regex-antispam/filters/types/scoringResult"
22
        badgerHelper "github.com/Civil/tg-simple-regex-antispam/helper/badger"
23
        "github.com/Civil/tg-simple-regex-antispam/helper/badger/badgerOpts"
24
        config2 "github.com/Civil/tg-simple-regex-antispam/helper/config"
25
        "github.com/Civil/tg-simple-regex-antispam/helper/tg"
26
)
27

28
var (
29
        ErrStateDirEmpty = errors.New("state_dir cannot be empty")
30
        ErrNIsZero       = errors.New("n cannot be equal to 0")
31
)
32

33
type Filter struct {
34
        sync.RWMutex
35
        chainName string
36
        logger    *zap.Logger
37

38
        stateDir string
39

40
        filteringRules []interfaces.FilteringRule
41
        actions        []actions.Action
42

43
        db  *badger.DB
44
        bot *telego.Bot
45

46
        isFinal         bool
47
        removeReportMsg bool
48

49
        vacations map[string]time.Time
50

51
        tg.TGHaveAdminCommands
52
}
53

54
func New(logger *zap.Logger, chainName string, _ bannedDB.BanDB, bot *telego.Bot, config map[string]any,
55
        filteringRules []interfaces.FilteringRule, actions []actions.Action,
56
) (interfaces.StatefulFilter, error) {
×
57
        var stateDir string
×
58
        var err error
×
59
        stateDir, err = config2.GetOptionString(config, "state_dir")
×
60
        if err != nil {
×
61
                return nil, err
×
62
        }
×
63
        if stateDir == "" {
×
64
                return nil, ErrStateDirEmpty
×
65
        }
×
66

67
        isFinal, err := config2.GetOptionBoolWithDefault(config, "isFinal", false)
×
68
        if err != nil {
×
69
                return nil, err
×
70
        }
×
71

72
        removeReportMsg, err := config2.GetOptionBoolWithDefault(config, "removeReportMsg", true)
×
73
        if err != nil {
×
74
                return nil, err
×
75
        }
×
76

77
        badgerDB, err := badger.Open(badgerOpts.GetBadgerOptions(logger, chainName+"_DB", stateDir))
×
78
        if err != nil {
×
79
                return nil, err
×
80
        }
×
81

82
        f := &Filter{
×
83
                logger: logger.With(
×
84
                        zap.String("filter", chainName),
×
85
                        zap.String("filter_type", "report"),
×
86
                ),
×
87
                chainName:       chainName,
×
88
                stateDir:        stateDir,
×
89
                db:              badgerDB,
×
90
                bot:             bot,
×
91
                isFinal:         isFinal,
×
92
                filteringRules:  filteringRules,
×
93
                removeReportMsg: removeReportMsg,
×
94
                actions:         actions,
×
NEW
95
                vacations:       make(map[string]time.Time),
×
NEW
96
                TGHaveAdminCommands: tg.TGHaveAdminCommands{
×
NEW
97
                        Handlers: make(map[string]tg.AdminCMDHandlerFunc),
×
NEW
98
                },
×
99
        }
×
NEW
100

×
NEW
101
        f.TGHaveAdminCommands.Handlers["vacation"] = f.vacationCmd
×
NEW
102
        go f.cleanVacationState()
×
103
        return f, nil
×
104
}
105

106
func Help() string {
×
107
        return "report requires `stateFile` parameter"
×
108
}
×
109

NEW
110
func (r *Filter) cleanVacationState() {
×
NEW
111
        for {
×
NEW
112
                for admin, vacationEndDate := range r.vacations {
×
NEW
113
                        if time.Now().After(vacationEndDate) {
×
NEW
114
                                _ = r.removeVacation(admin)
×
NEW
115
                        }
×
116
                }
NEW
117
                time.Sleep(1 * time.Minute)
×
118
        }
119
}
120

NEW
121
func (r *Filter) vacationCmd(logger *zap.Logger, bot *telego.Bot, message *telego.Message, tokens []string) error {
×
NEW
122
        r.logger.Debug("vacation command", zap.Strings("tokens", tokens))
×
NEW
123
        if len(tokens) == 0 || tokens[0] == "list" {
×
NEW
124
                return r.listVacations(logger, bot, message, tokens)
×
NEW
125
        }
×
NEW
126
        switch tokens[0] {
×
NEW
127
        case "add":
×
NEW
128
                return r.addVacationCmd(logger, bot, message, tokens[1:])
×
NEW
129
        case "remove":
×
NEW
130
                return r.removeVacationCmd(logger, bot, message, tokens[1:])
×
NEW
131
        default:
×
NEW
132
                return fmt.Errorf("unknown subcommand: %v", tokens[0])
×
133
        }
134
}
135

NEW
136
func (r *Filter) listVacations(logger *zap.Logger, bot *telego.Bot, message *telego.Message, tokens []string) error {
×
NEW
137
        r.logger.Debug("list vacations command", zap.Strings("tokens", tokens))
×
NEW
138
        buf := bytes.NewBuffer([]byte{})
×
NEW
139
        buf.WriteString("Admins on vacation:\n")
×
NEW
140
        r.logger.Debug("taking lock")
×
NEW
141
        r.RLock()
×
NEW
142
        r.logger.Debug("lock taken")
×
NEW
143
        if len(r.vacations) == 0 {
×
NEW
144
                buf.WriteString("No admins on vacation\n")
×
NEW
145
        } else {
×
NEW
146
                for admin, vacationEndDate := range r.vacations {
×
NEW
147
                        buf.WriteString(fmt.Sprintf("%v is on vacation until %v\n", admin, vacationEndDate))
×
NEW
148
                }
×
149
        }
NEW
150
        r.RUnlock()
×
NEW
151
        r.logger.Debug("lock released")
×
NEW
152

×
NEW
153
        r.logger.Debug("sending message", zap.String("message", buf.String()))
×
NEW
154
        err := tg.SendMessage(bot, message.Chat.ChatID(), &message.MessageID, buf.String())
×
NEW
155
        if err != nil {
×
NEW
156
                r.logger.Error("failed to send message", zap.Error(err))
×
NEW
157
        }
×
NEW
158
        return err
×
159
}
160

NEW
161
func (r *Filter) addVacationCmd(logger *zap.Logger, bot *telego.Bot, message *telego.Message, tokens []string) error {
×
NEW
162
        if len(tokens) == 0 {
×
NEW
163
                return fmt.Errorf("no admin username specified")
×
NEW
164
        }
×
NEW
165
        if len(tokens) == 1 {
×
NEW
166
                return fmt.Errorf("no vacation duration provided")
×
NEW
167
        }
×
NEW
168
        admin := tokens[0]
×
NEW
169
        vacationDuration, err := time.ParseDuration(tokens[1])
×
NEW
170
        if err != nil {
×
NEW
171
                return fmt.Errorf("failed to parse duration for admin %v: %v", admin, err)
×
NEW
172
        }
×
NEW
173
        r.Lock()
×
NEW
174
        r.vacations[admin] = time.Now().Add(vacationDuration)
×
NEW
175
        r.Unlock()
×
NEW
176
        err = tg.SendMessage(bot, message.Chat.ChatID(), &message.MessageID, fmt.Sprintf("Admin %v is on vacation until %v", admin, r.vacations[admin]))
×
NEW
177
        if err != nil {
×
NEW
178
                r.logger.Error("failed to send message", zap.Error(err))
×
NEW
179
        }
×
NEW
180
        return err
×
181
}
182

183
func (r *Filter) removeVacationCmd(logger *zap.Logger, bot *telego.Bot, message *telego.Message,
NEW
184
        tokens []string) error {
×
NEW
185
        if len(tokens) == 0 {
×
NEW
186
                return fmt.Errorf("no admin username specified")
×
NEW
187
        }
×
NEW
188
        admin := tokens[0]
×
NEW
189
        err := r.removeVacation(admin)
×
NEW
190
        if err != nil {
×
NEW
191
                return err
×
NEW
192
        }
×
NEW
193
        err = tg.SendMessage(bot, message.Chat.ChatID(), &message.MessageID,
×
NEW
194
                fmt.Sprintf("Admin %v is no longer on vacation", admin))
×
NEW
195
        if err != nil {
×
NEW
196
                r.logger.Error("failed to send message", zap.Error(err))
×
NEW
197
        }
×
NEW
198
        return err
×
199
}
200

NEW
201
func (r *Filter) removeVacation(admin string) error {
×
NEW
202
        r.Lock()
×
NEW
203
        defer r.Unlock()
×
NEW
204
        _, ok := r.vacations[admin]
×
NEW
205
        if !ok {
×
NEW
206
                return fmt.Errorf("admin %v is not on vacation", admin)
×
NEW
207
        }
×
NEW
208
        delete(r.vacations, admin)
×
NEW
209
        return nil
×
210
}
211

212
func (r *Filter) setState(userID int64, s *checkNeventsState.State) error {
×
213
        b, err := proto.Marshal(s)
×
214
        if err != nil {
×
215
                return err
×
216
        }
×
217
        return r.db.Update(
×
218
                func(txn *badger.Txn) error {
×
219
                        return txn.Set(badgerHelper.UserIDToKey(userID), b)
×
220
                })
×
221
}
222

223
func (r *Filter) getState(userID int64) (*checkNeventsState.State, error) {
×
224
        var s checkNeventsState.State
×
225
        err := r.db.View(
×
226
                func(txn *badger.Txn) error {
×
227
                        item, err := txn.Get(badgerHelper.UserIDToKey(userID))
×
228
                        if err != nil {
×
229
                                return err
×
230
                        }
×
231
                        return item.Value(func(val []byte) error {
×
232
                                return proto.Unmarshal(val, &s)
×
233
                        })
×
234
                })
235
        if err != nil {
×
236
                return nil, err
×
237
        }
×
238
        return &s, nil
×
239
}
240

241
func (r *Filter) RemoveState(userID int64) error {
×
242
        return r.db.Update(
×
243
                func(txn *badger.Txn) error {
×
244
                        return txn.Delete(badgerHelper.UserIDToKey(userID))
×
245
                })
×
246
}
247

248
func (r *Filter) Score(bot *telego.Bot, msg *telego.Message) *scoringResult.ScoringResult {
×
249
        score := &scoringResult.ScoringResult{}
×
250
        if !strings.HasPrefix(msg.Text, "/report") {
×
251
                r.logger.Debug("message does not start with /report")
×
252
                return score
×
253
        }
×
254
        if msg.ReplyToMessage == nil {
×
255
                r.logger.Debug("message does not have a reply")
×
256
                err := tg.SendMessage(r.bot, msg.Chat.ChatID(), &msg.MessageID, "Report must be a reply to a message")
×
257
                if err != nil {
×
258
                        r.logger.Error("failed to send message", zap.Error(err))
×
259
                }
×
260
                return score
×
261
        }
262

NEW
263
        r.logger.Debug("got a message that start with /report",
×
264
                zap.String("message_text", msg.Text),
×
265
                zap.String("from_user", msg.From.Username),
×
266
        )
×
267

×
268
        reportedMsg := msg.ReplyToMessage
×
269
        stateKey := int64(reportedMsg.MessageID)
×
270
        actualState, err := r.getState(stateKey)
×
NEW
271
        if err != nil || actualState == nil || len(actualState.GetMessageIds()) == 0 {
×
272
                r.logger.Debug("failed to get state, creating a clean one",
×
273
                        zap.Int("messageID", reportedMsg.MessageID),
×
274
                        zap.Error(err),
×
275
                )
×
NEW
276
                actualState = checkNeventsState.State_builder{
×
NEW
277
                        Verified:   proto.Bool(false),
×
278
                        LastUpdate: timestamppb.Now(),
×
NEW
279
                }.Build()
×
NEW
280
                messageIds := map[int64]bool{stateKey: true}
×
NEW
281
                actualState.SetMessageIds(messageIds)
×
282
        }
×
283

284
        // We already reported that message/user
NEW
285
        if actualState.GetVerified() {
×
286
                r.logger.Debug("message/user already reported")
×
287
                err = tg.SendMessage(r.bot, msg.Chat.ChatID(), &msg.MessageID, "Message/user was already reported")
×
288
                if err != nil {
×
289
                        r.logger.Error("failed to send message", zap.Error(err))
×
290
                }
×
291

292
                if r.removeReportMsg {
×
293
                        err = tg.DeleteMessage(r.bot, msg)
×
294
                        if err != nil {
×
295
                                r.logger.Error("failed to delete message", zap.Error(err))
×
296
                        }
×
297
                }
298
                score.Score = -1
×
299
                score.Reason = "already reported"
×
300
                return score
×
301
        }
302
        if r.removeReportMsg {
×
303
                err = tg.DeleteMessage(r.bot, msg)
×
304
                if err != nil {
×
305
                        r.logger.Error("failed to delete message", zap.Error(err))
×
306
                }
×
307
        }
308

309
        r.logger.Debug("applying actions...")
×
310
        score.Score = 100
×
311
        score.Reason = "reported command"
×
312
        for _, action := range r.actions {
×
313
                r.logger.Debug("trying to apply action",
×
NEW
314
                        zap.Any("message_ids", actualState.GetMessageIds()),
×
315
                        zap.Any("action", action),
×
316
                )
×
NEW
317
                err = action.ApplyToMessage(r, score, reportedMsg, r.vacations)
×
318
                if err != nil {
×
319
                        r.logger.Error("failed to apply action", zap.Any("action", action), zap.Error(err))
×
320
                        return score
×
321
                }
×
322
        }
323

NEW
324
        actualState.SetVerified(true)
×
325
        err = r.setState(stateKey, actualState)
×
326
        if err != nil {
×
327
                r.logger.Error("failed to set new state",
×
328
                        zap.Any("new_state", actualState),
×
329
                        zap.Error(err),
×
330
                )
×
331
        }
×
332

333
        return score
×
334
}
335

336
func (r *Filter) IsStateful() bool {
×
337
        return true
×
338
}
×
339

340
func (r *Filter) GetName() string {
×
341
        return "report"
×
342
}
×
343

344
func (r *Filter) GetFilterName() string {
×
345
        return r.chainName
×
346
}
×
347

348
func (r *Filter) IsFinal() bool {
×
349
        return r.isFinal
×
350
}
×
351

352
func (r *Filter) Close() error {
×
353
        return r.db.Close()
×
354
}
×
355

356
func (r *Filter) SaveState() error {
×
357
        return nil
×
358
}
×
359

360
func (r *Filter) LoadState() error {
×
361
        return nil
×
362
}
×
363

364
func (r *Filter) TGAdminPrefix() string {
×
NEW
365
        return r.chainName
×
366
}
×
367

368
func (r *Filter) UnbanUser(_ int64) error {
×
369
        return nil
×
370
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc