Browse Source

拉黑与危险用户

pull/7/head
hole-thu 4 years ago
parent
commit
ea3bf7a3b1
  1. 46
      hole.py
  2. 27
      utils.py

46
hole.py

@ -10,7 +10,7 @@ from sqlalchemy.sql.expression import func
from mastodon import Mastodon from mastodon import Mastodon
from models import db, User, Post, Comment, Attention, TagRecord, Syslog from models import db, User, Post, Comment, Attention, TagRecord, Syslog
from utils import get_current_username, map_post, map_comment, map_syslog, check_attention, hash_name, look, get_num, tmp_token, is_admin, check_can_del, rds, RDS_KEY_POLL_OPTS, RDS_KEY_POLL_VOTES, gen_poll_dict, name_with_tmp_limit from utils import get_current_username, map_post, map_comment, map_syslog, check_attention, hash_name, look, get_num, tmp_token, is_admin, check_can_del, rds, RDS_KEY_POLL_OPTS, RDS_KEY_POLL_VOTES, gen_poll_dict, name_with_tmp_limit, RDS_KEY_BLOCK_SET, RDS_KEY_BLOCKED_COUNT, RDS_KEY_DANGEROUS_USERS
app = Flask(__name__) app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hole.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hole.db'
@ -39,6 +39,10 @@ limiter = Limiter(
) )
PER_PAGE = 50 PER_PAGE = 50
DANGEROUS_USER_THRESHOLD = 10
# 重置后旧的被拉黑次数可以丢弃了,但其他仍需要保留
rds.delete(RDS_KEY_BLOCKED_COUNT)
class APIError(Exception): class APIError(Exception):
@ -558,5 +562,45 @@ def add_vote():
} }
@app.route('/_api/v1/block', methods=['POST'])
@limiter.limit("15 / hour; 1 / 2 second")
def block_user_by_target():
username = get_current_username()
target_type = request.form.get('type')
target_id = request.form.get('id', type=int)
if username.startswith('tmp_'):
raise APIError('临时用户无法拉黑')
if target_type == 'post':
target = Post.query.get_or_404(target_id)
elif target_type == 'comment':
target = Comment.query.get_or_404(target_id)
else:
raise APIError('无效的type')
if hash_name(username) == target.name_hash:
raise APIError('不可拉黑自己')
if is_admin(username):
rds.sadd(RDS_KEY_DANGEROUS_USERS, target.name_hash)
curr_cnt = rds.hget(RDS_KEY_BLOCKED_COUNT, target.name_hash)
else:
if rds.sismember(RDS_KEY_BLOCK_SET % username, target.name_hash):
raise APIError('已经拉黑了')
rds.sadd(RDS_KEY_BLOCK_SET % username, target.name_hash)
curr_cnt = rds.hincrby(RDS_KEY_BLOCKED_COUNT, target.name_hash, 1)
if curr_cnt >= DANGEROUS_USER_THRESHOLD:
rds.sadd(RDS_KEY_DANGEROUS_USERS, target.name_hash)
return {
'code': 0,
'data': {
'curr': curr_cnt,
'threshold': DANGEROUS_USER_THRESHOLD
}
}
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True) app.run(debug=True)

27
utils.py

@ -10,6 +10,10 @@ from config import RDS_CONFIG, ADMINS, ENABLE_TMP
RDS_KEY_POLL_OPTS = 'hole_thu:poll_opts:%s' RDS_KEY_POLL_OPTS = 'hole_thu:poll_opts:%s'
RDS_KEY_POLL_VOTES = 'hole_thu:poll_votes:%s:%s' RDS_KEY_POLL_VOTES = 'hole_thu:poll_votes:%s:%s'
RDS_KEY_BLOCK_SET = 'hole_thu:block_list:%s' # key的参数是name而非namehash,为了方便清理和持续拉黑。拉黑名单不那么敏感,应该可以接受后台实名。value是namehash。
RDS_KEY_BLOCKED_COUNT = 'hole_thu:blocked_count' # namehash -> 被拉黑次数
RDS_KEY_DANGEROUS_USERS = 'hole_thu:dangerous_users'
rds = redis.Redis(**RDS_CONFIG) rds = redis.Redis(**RDS_CONFIG)
@ -52,11 +56,14 @@ def hash_name(name):
def map_post(p, name, mc=50): def map_post(p, name, mc=50):
blocked = is_blocked(p.name_hash, name)
# TODO: 如果未来量大还是sql里not in一下
r = { r = {
'blocked': blocked,
'pid': p.id, 'pid': p.id,
'likenum': p.likenum, 'likenum': p.likenum,
'cw': p.cw, 'cw': p.cw,
'text': p.content, 'text': '' if blocked else p.content,
'timestamp': p.timestamp, 'timestamp': p.timestamp,
'type': p.post_type, 'type': p.post_type,
'url': p.file_url, 'url': p.file_url,
@ -65,7 +72,7 @@ def map_post(p, name, mc=50):
'attention': check_attention(name, p.id), 'attention': check_attention(name, p.id),
'can_del': check_can_del(name, p.name_hash), 'can_del': check_can_del(name, p.name_hash),
'allow_search': bool(p.search_text), 'allow_search': bool(p.search_text),
'poll': gen_poll_dict(p.id, name) 'poll': None if blocked else gen_poll_dict(p.id, name)
} }
if is_admin(name): if is_admin(name):
r['hot_score'] = p.hot_score r['hot_score'] = p.hot_score
@ -98,6 +105,19 @@ def name_with_tmp_limit(name: str) -> str:
'tmp_') else name 'tmp_') else name
def is_blocked(target_name_hash, name):
if rds.sismember(RDS_KEY_BLOCK_SET % name, target_name_hash):
return True
if rds.sismember(
RDS_KEY_DANGEROUS_USERS, target_name_hash
) and not (
is_admin(name) or rds.sismember(
RDS_KEY_DANGEROUS_USERS, hash_name(name))
):
return True
return False
def map_comment(p, name): def map_comment(p, name):
names = {p.name_hash: 0} names = {p.name_hash: 0}
@ -108,10 +128,11 @@ def map_comment(p, name):
return names[nh] return names[nh]
return [{ return [{
'blocked': (blocked := is_blocked(c.name_hash, name)),
'cid': c.id, 'cid': c.id,
'name_id': gen_name_id(c.name_hash), 'name_id': gen_name_id(c.name_hash),
'pid': p.id, 'pid': p.id,
'text': c.content, 'text': '' if blocked else c.content,
'timestamp': c.timestamp, 'timestamp': c.timestamp,
'can_del': check_can_del(name, c.name_hash) 'can_del': check_can_del(name, c.name_hash)
} for c in p.comments if not (c.deleted and gen_name_id(c.name_hash) >= 0) } for c in p.comments if not (c.deleted and gen_name_id(c.name_hash) >= 0)

Loading…
Cancel
Save