1
1
"""FilterBot class"""
2
2
from __future__ import annotations
3
-
4
3
from .message_events import MessageEvents
5
4
import time
5
+ from collections import deque
6
6
7
7
class HardFilter :
8
-
8
+
9
9
def __init__ (self , filter_name = "UntitledFilter" , * , equals = None , contains = None , author_name = None , project_id = None , profile = None , case_sensitive = False ):
10
- self .equals = equals
11
- self .contains = contains
12
- self .author_name = author_name
13
- self .project_id = project_id
14
- self .profile = profile
15
- self .case_sensitive = case_sensitive
10
+ self .equals = equals
11
+ self .contains = contains
12
+ self .author_name = author_name
13
+ self .project_id = project_id
14
+ self .profile = profile
15
+ self .case_sensitive = case_sensitive
16
16
self .filter_name = filter_name
17
17
18
18
def apply (self , content , author_name , source_id ):
19
- if not self .case_sensitive :
20
- content = content .lower ()
19
+ text_to_check = content if self .case_sensitive else content .lower ()
21
20
if self .equals is not None :
22
- if self .case_sensitive :
23
- if self .equals == content :
24
- return True
25
- else :
26
- if self .equals .lower () == content :
27
- return True
21
+ comparison_equals = self .equals if self .case_sensitive else self .equals .lower ()
22
+ if text_to_check == comparison_equals :
23
+ return True
28
24
if self .contains is not None :
29
- if self .case_sensitive :
30
- if self .contains .lower () in content :
31
- return True
32
- else :
33
- if self .contains in content :
34
- return True
35
- if self .author_name == author_name :
25
+ comparison_contains = self .contains if self .case_sensitive else self .contains .lower ()
26
+ if comparison_contains in text_to_check :
27
+ return True
28
+ if self .author_name is not None and self .author_name == author_name :
36
29
return True
37
- if self .project_id == source_id or self .profile == source_id :
30
+ if (self .project_id is not None and self .project_id == source_id ) or \
31
+ (self .profile is not None and self .profile == source_id ):
38
32
return True
39
33
return False
40
34
@@ -45,20 +39,27 @@ def __init__(self, score:float, filter_name="UntitledFilter", *, equals=None, co
45
39
46
40
class SpamFilter (HardFilter ):
47
41
def __init__ (self , filter_name = "UntitledFilter" , * , equals = None , contains = None , author_name = None , project_id = None , profile = None , case_sensitive = False ):
48
- self .memory = []
49
42
super ().__init__ (filter_name , equals = equals , contains = contains , author_name = author_name , project_id = project_id , profile = profile , case_sensitive = case_sensitive )
43
+ self .memory = deque ()
44
+ self .retention_period = 300
50
45
51
46
def apply (self , content , author_name , source_id ):
52
- applies = super ().apply (content , author_name , source_id )
53
- if not applies :
47
+ if not super ().apply (content , author_name , source_id ):
54
48
return False
55
- self .memory .insert (0 , {"content" :content , "time" :time .time ()})
56
- print (content , self .memory )
57
- for comment in list (self .memory )[1 :]:
58
- if comment ["time" ] < time .time () - 300 :
59
- self .memory .remove (comment )
60
- if comment ["content" ].lower () == content .lower ():
49
+ current_time = time .time ()
50
+
51
+ # Prune old entries from memory
52
+ while self .memory and self .memory [- 1 ]["time" ] < current_time - self .retention_period :
53
+ self .memory .pop ()
54
+
55
+ content_lower = content .lower ()
56
+ # Check for duplicates
57
+ for comment in self .memory :
58
+ if comment ["content" ].lower () == content_lower :
61
59
return True
60
+
61
+ # Add new comment to memory
62
+ self .memory .appendleft ({"content" : content , "time" : current_time })
62
63
return False
63
64
64
65
class Filterbot (MessageEvents ):
@@ -75,10 +76,10 @@ def __init__(self, user, *, log_deletions=True):
75
76
self .update_interval = 2
76
77
77
78
def add_filter (self , filter_obj ):
78
- if isinstance (filter_obj , SoftFilter ):
79
- self .soft_filters .append (filter_obj )
80
- elif isinstance (filter_obj , SpamFilter ): # careful: SpamFilter is also HardFilter due to inheritence
79
+ if isinstance (filter_obj , SpamFilter ):
81
80
self .spam_filters .append (filter_obj )
81
+ elif isinstance (filter_obj , SoftFilter ):
82
+ self .soft_filters .append (filter_obj )
82
83
elif isinstance (filter_obj , HardFilter ):
83
84
self .hard_filters .append (filter_obj )
84
85
@@ -105,57 +106,58 @@ def add_genalpha_nonsense_filter(self):
105
106
self .add_filter (HardFilter ("(genalpha_nonsene_filter) 'fanum tax'" , contains = "fanum tax" ))
106
107
107
108
def on_message (self , message ):
108
- if message .type == "addcomment" :
109
- delete = False
110
- content = message .comment_fragment
111
-
112
- if message .comment_type == 0 : # project comment
113
- source_id = message .comment_obj_id
114
- if self .user ._session .connect_project (message .comment_obj_id ).author_name != self .user .username :
115
- return # no permission to delete
116
- if message .comment_type == 1 : # profile comment
117
- source_id = message .comment_obj_title
118
- if message .comment_obj_title != self .user .username :
119
- return # no permission to delete
120
- if message .comment_type == 2 : # studio comment
121
- return # studio comments aren't handled
122
-
123
- # Apply hard filters
124
- for hard_filter in self .hard_filters :
125
- if hard_filter .apply (content , message .actor_username , source_id ):
126
- delete = True
127
- if self .log_deletions :
128
- print (f"DETECTED: #{ message .comment_id } violates hard filter: { hard_filter .filter_name } " )
109
+ if message .type != "addcomment" :
110
+ return
111
+ source_id = None
112
+ content = message .comment_fragment
113
+ if message .comment_type == 0 : # project comment
114
+ source_id = message .comment_obj_id
115
+ if self .user ._session .connect_project (message .comment_obj_id ).author_name != self .user .username :
116
+ return # no permission to delete comments that aren't on our own project
117
+ elif message .comment_type == 1 : # profile comment
118
+ source_id = message .comment_obj_title
119
+ if source_id != self .user .username :
120
+ return # no permission to delete messages that are not on our profile
121
+ elif message .comment_type == 2 : # studio comment
122
+ return # studio comments aren't handled
123
+ else :
124
+ return
125
+ delete = False
126
+ reason = ""
127
+
128
+ # Apply hard filters
129
+ for hard_filter in self .hard_filters :
130
+ if hard_filter .apply (content , message .actor_username , source_id ):
131
+ delete = True
132
+ reason = f"hard filter: { hard_filter .filter_name } "
133
+ break
134
+
135
+ # Apply spam filters
136
+ if not delete :
137
+ for spam_filter in self .spam_filters :
138
+ if spam_filter .apply (content , message .actor_username , source_id ):
139
+ delete = True
140
+ reason = f"spam filter: { spam_filter .filter_name } "
129
141
break
130
142
131
- # Apply spam filters
132
- if delete is False :
133
- for spam_filter in self .spam_filters :
134
- if spam_filter .apply (content , message .actor_username , source_id ):
135
- delete = True
136
- if self .log_deletions :
137
- print (f"DETECTED: #{ message .comment_id } violates spam filter: { spam_filter .filter_name } " )
138
- break
139
-
140
- # Apply soft filters
141
- if delete is False :
142
- score = 0
143
- violated_filers = []
144
- for soft_filter in self .soft_filters :
145
- if soft_filter .apply (content , message .actor_username , source_id ):
146
- score += soft_filter .score
147
- violated_filers .append (soft_filter .name )
148
- if score >= 1 :
149
- print (f"DETECTED: #{ message .comment_id } violates too many soft filters: { violated_filers } " )
150
- delete = True
151
-
152
- if delete is True :
153
- try :
154
- message .target ().delete ()
155
- if self .log_deletions :
156
- print (f"DELETED: #{ message .comment_id } by f{ message .actor_username } : '{ content } '" )
157
- except Exception as e :
158
- if self .log_deletions :
159
- print (f"DELETION FAILED: #{ message .comment_id } by f{ message .actor_username } : '{ content } '" )
160
-
161
-
143
+ # Apply soft filters
144
+ if not delete :
145
+ score = 0
146
+ violated_filters = []
147
+ for soft_filter in self .soft_filters :
148
+ if soft_filter .apply (content , message .actor_username , source_id ):
149
+ score += soft_filter .score
150
+ violated_filters .append (soft_filter .filter_name )
151
+ if score >= 1 :
152
+ delete = True
153
+ reason = f"too many soft filters: { violated_filters } "
154
+ if delete :
155
+ if self .log_deletions :
156
+ print (f"DETECTED: #{ message .comment_id } violates { reason } " )
157
+ try :
158
+ resp = message .target ().delete ()
159
+ if self .log_deletions :
160
+ print (f"DELETED: #{ message .comment_id } by { message .actor_username !r} : '{ content } ' with message { resp .content !r} & headers { resp .headers !r} " )
161
+ except Exception as e :
162
+ if self .log_deletions :
163
+ print (f"DELETION FAILED: #{ message .comment_id } by { message .actor_username !r} : '{ content } '; exception: { e } " )
0 commit comments