Detect chat flooding by counting lines
Reject comments by line count. Ratelimit users by number of lines sent in chat.
このコミットが含まれているのは:
コミット
542d6c9ae5
|
@ -66,8 +66,10 @@ def create_app(config_file):
|
|||
'CHAT_NAME_MIN_CONTRAST': config['chat']['min_name_contrast'],
|
||||
'CHAT_BACKGROUND_COLOUR': color_to_colour(config['chat']['background_color']),
|
||||
'CHAT_LEGACY_TRIPCODE_ALGORITHM': config['chat']['legacy_tripcode_algorithm'],
|
||||
'FLOOD_DURATION': config['flood']['duration'],
|
||||
'FLOOD_THRESHOLD': config['flood']['threshold'],
|
||||
'FLOOD_MESSAGE_DURATION': config['flood']['messages']['duration'],
|
||||
'FLOOD_MESSAGE_THRESHOLD': config['flood']['messages']['threshold'],
|
||||
'FLOOD_LINE_DURATION': config['flood']['lines']['duration'],
|
||||
'FLOOD_LINE_THRESHOLD': config['flood']['lines']['threshold'],
|
||||
'CAPTCHA_LIFETIME': config['captcha']['lifetime'],
|
||||
'CAPTCHA_FONTS': config['captcha']['fonts'],
|
||||
'CAPTCHA_ALPHABET': config['captcha']['alphabet'],
|
||||
|
|
|
@ -8,7 +8,7 @@ from quart import current_app, escape
|
|||
|
||||
from anonstream.broadcast import broadcast, broadcast_users_update
|
||||
from anonstream.helpers.chat import generate_nonce_hash, get_scrollback
|
||||
from anonstream.utils.chat import get_message_for_websocket
|
||||
from anonstream.utils.chat import get_message_for_websocket, get_approx_linespan
|
||||
|
||||
CONFIG = current_app.config
|
||||
MESSAGES_BY_ID = current_app.messages_by_id
|
||||
|
@ -33,6 +33,26 @@ def add_chat_message(user, nonce, comment, ignore_empty=False):
|
|||
if ignore_empty and len(comment) == 0:
|
||||
return False
|
||||
|
||||
timestamp_ms = time.time_ns() // 1_000_000
|
||||
timestamp = timestamp_ms // 1000
|
||||
|
||||
# Check user
|
||||
while user['linespan']:
|
||||
linespan_timestamp, _ = user['linespan'][0]
|
||||
if timestamp - linespan_timestamp >= CONFIG['FLOOD_LINE_DURATION']:
|
||||
user['linespan'].popleft()
|
||||
else:
|
||||
break
|
||||
total_recent_linespan = sum(map(
|
||||
lambda linespan_tuple: linespan_tuple[1],
|
||||
user['linespan'],
|
||||
))
|
||||
if total_recent_linespan > CONFIG['FLOOD_LINE_THRESHOLD']:
|
||||
raise Rejected(
|
||||
f'Chat overuse in the last '
|
||||
f'{CONFIG["FLOOD_LINE_DURATION"]:.0f} seconds'
|
||||
)
|
||||
|
||||
# Check message
|
||||
message_id = generate_nonce_hash(nonce)
|
||||
if message_id in MESSAGES_BY_ID:
|
||||
|
@ -41,10 +61,18 @@ def add_chat_message(user, nonce, comment, ignore_empty=False):
|
|||
raise Rejected('Message was empty')
|
||||
if len(comment) > 512:
|
||||
raise Rejected('Message exceeded 512 chars')
|
||||
if comment.count('\n') + 1 > 12:
|
||||
raise Rejected('Message exceeded 12 lines')
|
||||
|
||||
linespan = get_approx_linespan(comment)
|
||||
if linespan > 12:
|
||||
raise Rejected('Message would span too many lines')
|
||||
|
||||
# Record linespan
|
||||
linespan_tuple = (timestamp, linespan)
|
||||
user['linespan'].append(linespan_tuple)
|
||||
|
||||
# Create and add message
|
||||
timestamp_ms = time.time_ns() // 1_000_000
|
||||
timestamp = timestamp_ms // 1000
|
||||
try:
|
||||
last_message = next(reversed(MESSAGES))
|
||||
except StopIteration:
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import hashlib
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
from collections import deque, OrderedDict
|
||||
from math import inf
|
||||
|
||||
from quart import current_app
|
||||
|
@ -45,6 +45,7 @@ def generate_user(timestamp, token, broadcaster, presence):
|
|||
'watching': -inf,
|
||||
},
|
||||
'presence': presence,
|
||||
'linespan': deque(),
|
||||
}
|
||||
|
||||
def get_default_name(user):
|
||||
|
|
|
@ -152,12 +152,12 @@ def deverify(timestamp, user):
|
|||
n_user_messages = 0
|
||||
for message in reversed(MESSAGES):
|
||||
message_sent_ago = timestamp - message['timestamp']
|
||||
if message_sent_ago >= CONFIG['FLOOD_DURATION']:
|
||||
if message_sent_ago >= CONFIG['FLOOD_MESSAGE_DURATION']:
|
||||
break
|
||||
elif message['token'] == user['token']:
|
||||
n_user_messages += 1
|
||||
|
||||
if n_user_messages >= CONFIG['FLOOD_THRESHOLD']:
|
||||
if n_user_messages >= CONFIG['FLOOD_MESSAGE_THRESHOLD']:
|
||||
user['verified'] = False
|
||||
|
||||
def _update_presence(timestamp, user):
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import base64
|
||||
import hashlib
|
||||
import math
|
||||
import secrets
|
||||
|
||||
class NonceReuse(Exception):
|
||||
|
@ -18,3 +19,10 @@ def get_message_for_websocket(user, message):
|
|||
**{key: message[key] for key in message_keys},
|
||||
**{key: user[key] for key in user_keys},
|
||||
}
|
||||
|
||||
def get_approx_linespan(text):
|
||||
def height(line):
|
||||
return math.ceil(len(line) / 48)
|
||||
linespan = sum(map(height, text.splitlines()))
|
||||
linespan = linespan if linespan > 0 else 1
|
||||
return linespan
|
||||
|
|
|
@ -49,10 +49,14 @@ min_name_contrast = 3.0
|
|||
background_color = "#232327"
|
||||
legacy_tripcode_algorithm = false
|
||||
|
||||
[flood]
|
||||
[flood.messages]
|
||||
duration = 20.0
|
||||
threshold = 4
|
||||
|
||||
[flood.lines]
|
||||
duration = 20.0
|
||||
threshold = 20
|
||||
|
||||
[thresholds]
|
||||
user_notwatching = 8.0
|
||||
user_tentative = 20.0
|
||||
|
|
読み込み中…
新しいイシューから参照