mirror of
https://github.com/tgbot-collection/YYeTsBot.git
synced 2025-11-25 03:15:05 +08:00
refactor code
This commit is contained in:
484
yyetsweb/databases/comment.py
Normal file
484
yyetsweb/databases/comment.py
Normal file
@@ -0,0 +1,484 @@
|
||||
#!/usr/bin/env python3
|
||||
# coding: utf-8
|
||||
import contextlib
|
||||
import os
|
||||
import re
|
||||
from http import HTTPStatus
|
||||
|
||||
import pymongo
|
||||
from bson import ObjectId
|
||||
|
||||
from common.utils import check_spam, send_mail, ts_date
|
||||
from databases.base import Mongo
|
||||
from databases.other import Captcha, SpamProcess
|
||||
|
||||
|
||||
class Comment(Mongo):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.inner_page = 1
|
||||
self.inner_size = 5
|
||||
self.projection = {"ip": False, "parent_id": False}
|
||||
|
||||
@staticmethod
|
||||
def convert_objectid(data):
|
||||
# change _id to id, remove _id
|
||||
for item in data:
|
||||
item["id"] = str(item["_id"])
|
||||
item.pop("_id")
|
||||
for child in item.get("children", []):
|
||||
with contextlib.suppress(Exception):
|
||||
child["id"] = str(child["_id"])
|
||||
child.pop("_id")
|
||||
|
||||
def find_children(self, parent_data):
|
||||
for item in parent_data:
|
||||
children_ids = item.get("children", [])
|
||||
condition = {
|
||||
"_id": {"$in": children_ids},
|
||||
"deleted_at": {"$exists": False},
|
||||
"type": "child",
|
||||
}
|
||||
children_count = self.db["comment"].count_documents(condition)
|
||||
children_data = (
|
||||
self.db["comment"]
|
||||
.find(condition, self.projection)
|
||||
.sort("_id", pymongo.DESCENDING)
|
||||
.limit(self.inner_size)
|
||||
.skip((self.inner_page - 1) * self.inner_size)
|
||||
)
|
||||
children_data = list(children_data)
|
||||
self.get_user_group(children_data)
|
||||
self.add_reactions(children_data)
|
||||
|
||||
item["children"] = []
|
||||
if children_data:
|
||||
item["children"].extend(children_data)
|
||||
item["childrenCount"] = children_count
|
||||
else:
|
||||
item["childrenCount"] = 0
|
||||
|
||||
def get_user_group(self, data):
|
||||
whitelist = os.getenv("whitelist", "").split(",")
|
||||
for comment in data:
|
||||
username = comment["username"]
|
||||
user = self.db["users"].find_one({"username": username}) or {}
|
||||
group = user.get("group", ["user"])
|
||||
comment["group"] = group
|
||||
comment["hasAvatar"] = bool(user.get("avatar"))
|
||||
if username in whitelist:
|
||||
comment["group"].append("publisher")
|
||||
|
||||
def add_reactions(self, data):
|
||||
for comment in data:
|
||||
cid = comment.get("id") or comment.get("_id")
|
||||
cid = str(cid)
|
||||
reactions = (
|
||||
self.db["reactions"].find_one(
|
||||
{"comment_id": cid}, projection={"_id": False, "comment_id": False}
|
||||
)
|
||||
or {}
|
||||
)
|
||||
for verb, users in reactions.items():
|
||||
if users:
|
||||
comment.setdefault("reactions", []).append(
|
||||
{"verb": verb, "users": users}
|
||||
)
|
||||
|
||||
def get_comment(self, resource_id: int, page: int, size: int, **kwargs) -> dict:
|
||||
self.inner_page = kwargs.get("inner_page", 1)
|
||||
self.inner_size = kwargs.get("inner_size", 5)
|
||||
comment_id = kwargs.get("comment_id")
|
||||
|
||||
condition = {
|
||||
"resource_id": resource_id,
|
||||
"deleted_at": {"$exists": False},
|
||||
"type": {"$ne": "child"},
|
||||
}
|
||||
if comment_id:
|
||||
# 搜索某个评论id的结果
|
||||
condition = {
|
||||
"deleted_at": {"$exists": False},
|
||||
"$or": [
|
||||
# 如果是子评论id,搜索子评论,会将整个父评论带出
|
||||
{"children": {"$in": [ObjectId(comment_id)]}},
|
||||
# 如果是父评论id,搜索父评论,并且排除子评论的记录
|
||||
{"_id": ObjectId(comment_id), "type": {"$ne": "child"}},
|
||||
],
|
||||
}
|
||||
|
||||
count = self.db["comment"].count_documents(condition)
|
||||
data = (
|
||||
self.db["comment"]
|
||||
.find(condition, self.projection)
|
||||
.sort("_id", pymongo.DESCENDING)
|
||||
.limit(size)
|
||||
.skip((page - 1) * size)
|
||||
)
|
||||
data = list(data)
|
||||
self.find_children(data)
|
||||
self.convert_objectid(data)
|
||||
self.get_user_group(data)
|
||||
self.add_reactions(data)
|
||||
|
||||
return {"data": data, "count": count, "resource_id": resource_id}
|
||||
|
||||
def add_comment(
|
||||
self,
|
||||
captcha: str,
|
||||
captcha_id: int,
|
||||
content: str,
|
||||
resource_id: int,
|
||||
ip: str,
|
||||
username: str,
|
||||
browser: str,
|
||||
parent_comment_id=None,
|
||||
) -> dict:
|
||||
user_data = self.db["users"].find_one({"username": username})
|
||||
# old user is allowed to comment without verification
|
||||
if (
|
||||
not self.is_old_user(username)
|
||||
and user_data.get("email", {}).get("verified", False) is False
|
||||
):
|
||||
return {
|
||||
"status_code": HTTPStatus.TEMPORARY_REDIRECT,
|
||||
"message": "你需要验证邮箱才能评论,请到个人中心进行验证",
|
||||
}
|
||||
returned = {"status_code": 0, "message": ""}
|
||||
# check if this user is blocked
|
||||
reason = self.is_user_blocked(username)
|
||||
if reason:
|
||||
return {"status_code": HTTPStatus.FORBIDDEN, "message": reason}
|
||||
if check_spam(ip, browser, username, content) != 0:
|
||||
document = {
|
||||
"username": username,
|
||||
"ip": ip,
|
||||
"date": ts_date(),
|
||||
"browser": browser,
|
||||
"content": content,
|
||||
"resource_id": resource_id,
|
||||
}
|
||||
inserted_id = self.db["spam"].insert_one(document).inserted_id
|
||||
document["_id"] = str(inserted_id)
|
||||
SpamProcess.request_approval(document)
|
||||
return {
|
||||
"status_code": HTTPStatus.FORBIDDEN,
|
||||
"message": f"possible spam, reference id: {inserted_id}",
|
||||
}
|
||||
|
||||
user_group = user_data.get("group", [])
|
||||
if not user_group:
|
||||
# admin don't have to verify code
|
||||
verify_result = Captcha().verify_code(captcha, captcha_id)
|
||||
if os.getenv("PYTHON_DEV"):
|
||||
pass
|
||||
elif not verify_result["status"]:
|
||||
returned["status_code"] = HTTPStatus.BAD_REQUEST
|
||||
returned["message"] = verify_result["message"]
|
||||
return returned
|
||||
|
||||
exists = self.db["yyets"].find_one({"data.info.id": resource_id})
|
||||
if not exists:
|
||||
returned["status_code"] = HTTPStatus.NOT_FOUND
|
||||
returned["message"] = "资源不存在"
|
||||
return returned
|
||||
|
||||
if parent_comment_id:
|
||||
exists = self.db["comment"].find_one({"_id": ObjectId(parent_comment_id)})
|
||||
if not exists:
|
||||
returned["status_code"] = HTTPStatus.NOT_FOUND
|
||||
returned["message"] = "评论不存在"
|
||||
return returned
|
||||
|
||||
basic_comment = {
|
||||
"username": username,
|
||||
"ip": ip,
|
||||
"date": ts_date(),
|
||||
"browser": browser,
|
||||
"content": content,
|
||||
"resource_id": resource_id,
|
||||
}
|
||||
if parent_comment_id is None:
|
||||
basic_comment["type"] = "parent"
|
||||
else:
|
||||
basic_comment["type"] = "child"
|
||||
# 无论什么评论,都要插入一个新的document
|
||||
inserted_id: str = self.db["comment"].insert_one(basic_comment).inserted_id
|
||||
|
||||
if parent_comment_id is not None:
|
||||
# 对父评论的子评论,需要给父评论加children id
|
||||
self.db["comment"].find_one_and_update(
|
||||
{"_id": ObjectId(parent_comment_id)},
|
||||
{"$push": {"children": inserted_id}},
|
||||
)
|
||||
self.db["comment"].find_one_and_update(
|
||||
{"_id": ObjectId(inserted_id)},
|
||||
{"$set": {"parent_id": ObjectId(parent_comment_id)}},
|
||||
)
|
||||
returned["status_code"] = HTTPStatus.CREATED
|
||||
returned["message"] = "评论成功"
|
||||
|
||||
# notification
|
||||
if parent_comment_id:
|
||||
# find username
|
||||
|
||||
self.db["notification"].find_one_and_update(
|
||||
{"username": exists["username"]},
|
||||
{"$push": {"unread": inserted_id}},
|
||||
upsert=True,
|
||||
)
|
||||
# send email
|
||||
parent_comment = self.db["comment"].find_one(
|
||||
{"_id": ObjectId(parent_comment_id)}
|
||||
)
|
||||
if resource_id == 233:
|
||||
link = f"https://yyets.dmesg.app/discuss#{parent_comment_id}"
|
||||
else:
|
||||
link = f"https://yyets.dmesg.app/resource?id={resource_id}#{parent_comment_id}"
|
||||
user_info = self.db["users"].find_one(
|
||||
{"username": parent_comment["username"], "email.verified": True}
|
||||
)
|
||||
if user_info:
|
||||
subject = "[人人影视下载分享站] 你的评论有了新的回复"
|
||||
pt_content = content.split("</reply>")[-1]
|
||||
text = (
|
||||
f"你的评论 {parent_comment['content']} 有了新的回复:<br>{pt_content}"
|
||||
f"<br>你可以<a href='{link}'>点此链接</a>查看<br><br>请勿回复此邮件"
|
||||
)
|
||||
context = {"username": username, "text": text}
|
||||
send_mail(user_info["email"]["address"], subject, context)
|
||||
return returned
|
||||
|
||||
def delete_comment(self, comment_id):
|
||||
current_time = ts_date()
|
||||
count = (
|
||||
self.db["comment"]
|
||||
.update_one(
|
||||
{"_id": ObjectId(comment_id), "deleted_at": {"$exists": False}},
|
||||
{"$set": {"deleted_at": current_time}},
|
||||
)
|
||||
.modified_count
|
||||
)
|
||||
# 找到子评论,全部标记删除
|
||||
parent_data = self.db["comment"].find_one({"_id": ObjectId(comment_id)})
|
||||
if parent_data:
|
||||
child_ids = parent_data.get("children", [])
|
||||
else:
|
||||
child_ids = []
|
||||
count += (
|
||||
self.db["comment"]
|
||||
.update_many(
|
||||
{"_id": {"$in": child_ids}, "deleted_at": {"$exists": False}},
|
||||
{"$set": {"deleted_at": current_time}},
|
||||
)
|
||||
.modified_count
|
||||
)
|
||||
|
||||
returned = {"status_code": 0, "message": "", "count": -1}
|
||||
if count == 0:
|
||||
returned["status_code"] = HTTPStatus.NOT_FOUND
|
||||
returned["count"] = 0
|
||||
else:
|
||||
returned["status_code"] = HTTPStatus.OK
|
||||
returned["count"] = count
|
||||
|
||||
return returned
|
||||
|
||||
|
||||
class CommentReaction(Mongo):
|
||||
def react_comment(self, username, data):
|
||||
# {"comment_id":"da23","😊":["user1","user2"]}
|
||||
comment_id = data["comment_id"]
|
||||
verb = data["verb"]
|
||||
method = data["method"]
|
||||
if not self.db["comment"].find_one({"_id": ObjectId(comment_id)}):
|
||||
return {
|
||||
"status": False,
|
||||
"message": "Where is your comments?",
|
||||
"status_code": HTTPStatus.NOT_FOUND,
|
||||
}
|
||||
|
||||
if method == "POST":
|
||||
self.db["reactions"].update_one(
|
||||
{"comment_id": comment_id}, {"$addToSet": {verb: username}}, upsert=True
|
||||
)
|
||||
code = HTTPStatus.CREATED
|
||||
elif method == "DELETE":
|
||||
self.db["reactions"].update_one(
|
||||
{"comment_id": comment_id}, {"$pull": {verb: username}}
|
||||
)
|
||||
code = HTTPStatus.ACCEPTED
|
||||
else:
|
||||
code = HTTPStatus.BAD_REQUEST
|
||||
return {"status": True, "message": "success", "status_code": code}
|
||||
|
||||
|
||||
class CommentChild(Comment, Mongo):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.page = 1
|
||||
self.size = 5
|
||||
self.projection = {"ip": False, "parent_id": False}
|
||||
|
||||
def get_comment(self, parent_id: str, page: int, size: int) -> dict:
|
||||
condition = {
|
||||
"parent_id": ObjectId(parent_id),
|
||||
"deleted_at": {"$exists": False},
|
||||
"type": "child",
|
||||
}
|
||||
|
||||
count = self.db["comment"].count_documents(condition)
|
||||
data = (
|
||||
self.db["comment"]
|
||||
.find(condition, self.projection)
|
||||
.sort("_id", pymongo.DESCENDING)
|
||||
.limit(size)
|
||||
.skip((page - 1) * size)
|
||||
)
|
||||
data = list(data)
|
||||
self.convert_objectid(data)
|
||||
self.get_user_group(data)
|
||||
return {
|
||||
"data": data,
|
||||
"count": count,
|
||||
}
|
||||
|
||||
|
||||
class CommentNewest(Comment, Mongo):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.page = 1
|
||||
self.size = 5
|
||||
self.projection = {"ip": False, "parent_id": False, "children": False}
|
||||
self.condition: "dict" = {"deleted_at": {"$exists": False}}
|
||||
|
||||
def get_comment(self, page: int, size: int, keyword="") -> dict:
|
||||
# ID,时间,用户名,用户组,资源名,资源id
|
||||
count = self.db["comment"].count_documents(self.condition)
|
||||
data = (
|
||||
self.db["comment"]
|
||||
.find(self.condition, self.projection)
|
||||
.sort("_id", pymongo.DESCENDING)
|
||||
.limit(size)
|
||||
.skip((page - 1) * size)
|
||||
)
|
||||
data = list(data)
|
||||
self.convert_objectid(data)
|
||||
self.get_user_group(data)
|
||||
self.extra_info(data)
|
||||
return {
|
||||
"data": data,
|
||||
"count": count,
|
||||
}
|
||||
|
||||
def extra_info(self, data):
|
||||
for i in data:
|
||||
resource_id = i.get("resource_id", 233)
|
||||
res = self.db["yyets"].find_one({"data.info.id": resource_id})
|
||||
if res:
|
||||
i["cnname"] = res["data"]["info"]["cnname"]
|
||||
|
||||
|
||||
class CommentSearch(CommentNewest):
|
||||
def get_comment(self, page: int, size: int, keyword="") -> dict:
|
||||
self.projection.pop("children")
|
||||
self.condition.update(content={"$regex": f".*{keyword}.*", "$options": "i"})
|
||||
data = list(
|
||||
self.db["comment"]
|
||||
.find(self.condition, self.projection)
|
||||
.sort("_id", pymongo.DESCENDING)
|
||||
.limit(size)
|
||||
.skip((page - 1) * size)
|
||||
)
|
||||
self.convert_objectid(data)
|
||||
self.get_user_group(data)
|
||||
self.extra_info(data)
|
||||
self.fill_children(data)
|
||||
# final step - remove children
|
||||
for i in data:
|
||||
i.pop("children", None)
|
||||
return {
|
||||
"data": data,
|
||||
}
|
||||
|
||||
def fill_children(self, data):
|
||||
for item in data:
|
||||
child_id: "list" = item.get("children", [])
|
||||
children = list(
|
||||
self.db["comment"]
|
||||
.find({"_id": {"$in": child_id}}, self.projection)
|
||||
.sort("_id", pymongo.DESCENDING)
|
||||
)
|
||||
self.convert_objectid(children)
|
||||
self.get_user_group(children)
|
||||
self.extra_info(children)
|
||||
data.extend(children)
|
||||
|
||||
|
||||
class Notification(Mongo):
|
||||
def get_notification(self, username, page, size):
|
||||
# .sort("_id", pymongo.DESCENDING).limit(size).skip((page - 1) * size)
|
||||
notify = self.db["notification"].find_one(
|
||||
{"username": username}, projection={"_id": False}
|
||||
)
|
||||
if not notify:
|
||||
return {
|
||||
"username": username,
|
||||
"unread_item": [],
|
||||
"read_item": [],
|
||||
"unread_count": 0,
|
||||
"read_count": 0,
|
||||
}
|
||||
|
||||
# size is shared
|
||||
unread = notify.get("unread", [])
|
||||
id_list = []
|
||||
for item in unread[(page - 1) * size : size * page]:
|
||||
id_list.append(item)
|
||||
notify["unread_item"] = self.get_content(id_list)
|
||||
|
||||
size = size - len(unread)
|
||||
read = notify.get("read", [])
|
||||
id_list = []
|
||||
for item in read[(page - 1) * size : size * page]:
|
||||
id_list.append(item)
|
||||
notify["read_item"] = self.get_content(id_list)
|
||||
|
||||
notify.pop("unread", None)
|
||||
notify.pop("read", None)
|
||||
notify["unread_count"] = len(unread)
|
||||
notify["read_count"] = len(read)
|
||||
return notify
|
||||
|
||||
def get_content(self, id_list):
|
||||
comments = (
|
||||
self.db["comment"]
|
||||
.find(
|
||||
{"_id": {"$in": id_list}}, projection={"ip": False, "parent_id": False}
|
||||
)
|
||||
.sort("_id", pymongo.DESCENDING)
|
||||
)
|
||||
comments = list(comments)
|
||||
for comment in comments:
|
||||
comment["id"] = str(comment["_id"])
|
||||
comment.pop("_id")
|
||||
reply_to_id = re.findall(r'"(.*)"', comment["content"])[0]
|
||||
rtc = self.db["comment"].find_one(
|
||||
{"_id": ObjectId(reply_to_id)},
|
||||
projection={"content": True, "_id": False},
|
||||
)
|
||||
comment["reply_to_content"] = rtc["content"]
|
||||
|
||||
return comments
|
||||
|
||||
def update_notification(self, username, verb, comment_id):
|
||||
if verb == "read":
|
||||
v1, v2 = "read", "unread"
|
||||
else:
|
||||
v1, v2 = "unread", "read"
|
||||
self.db["notification"].find_one_and_update(
|
||||
{"username": username},
|
||||
{"$push": {v1: ObjectId(comment_id)}, "$pull": {v2: ObjectId(comment_id)}},
|
||||
)
|
||||
|
||||
return {}
|
||||
Reference in New Issue
Block a user