@@ -132,4 +132,4 @@ BEATMAP_PROXY_PREFIX=b-ppy
|
||||
SAVE_REPLAYS=0
|
||||
REDIS_HOST=localhost
|
||||
SHARED_INTEROP_DOMAIN=http://localhost:8000
|
||||
SERVER_PORT=8006
|
||||
SERVER_PORT=80
|
||||
|
||||
@@ -61,11 +61,11 @@ class Event(UTCBaseModel, SQLModel, table=True):
|
||||
# 临时修复:统一成就事件格式 (TODO: 可在数据迁移完成后移除)
|
||||
if self.type == EventType.ACHIEVEMENT and "achievement" in self.event_payload:
|
||||
achievement_data = self.event_payload["achievement"]
|
||||
if (
|
||||
"achievement_id" in achievement_data
|
||||
and ("name" not in achievement_data or "slug" not in achievement_data)
|
||||
if "achievement_id" in achievement_data and (
|
||||
"name" not in achievement_data or "slug" not in achievement_data
|
||||
):
|
||||
from app.models.achievement import MEDALS
|
||||
|
||||
achievement_id = achievement_data["achievement_id"]
|
||||
for medal in MEDALS:
|
||||
if medal.id == achievement_id:
|
||||
|
||||
@@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
|
||||
class UserpageError(Exception):
|
||||
"""用户页面处理错误基类"""
|
||||
|
||||
def __init__(self, message: str, code: str = "userpage_error"):
|
||||
self.message = message
|
||||
self.code = code
|
||||
@@ -15,6 +16,7 @@ class UserpageError(Exception):
|
||||
|
||||
class ContentTooLongError(UserpageError):
|
||||
"""内容过长错误"""
|
||||
|
||||
def __init__(self, current_length: int, max_length: int):
|
||||
message = f"Content too long. Maximum {max_length} characters allowed, got {current_length}."
|
||||
super().__init__(message, "content_too_long")
|
||||
@@ -24,12 +26,14 @@ class ContentTooLongError(UserpageError):
|
||||
|
||||
class ContentEmptyError(UserpageError):
|
||||
"""内容为空错误"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("Content cannot be empty.", "content_empty")
|
||||
|
||||
|
||||
class BBCodeValidationError(UserpageError):
|
||||
"""BBCode验证错误"""
|
||||
|
||||
def __init__(self, errors: list[str]):
|
||||
message = f"BBCode validation failed: {'; '.join(errors)}"
|
||||
super().__init__(message, "bbcode_validation_error")
|
||||
@@ -38,6 +42,7 @@ class BBCodeValidationError(UserpageError):
|
||||
|
||||
class ForbiddenTagError(UserpageError):
|
||||
"""禁止标签错误"""
|
||||
|
||||
def __init__(self, tag: str):
|
||||
message = f"Forbidden tag '{tag}' is not allowed."
|
||||
super().__init__(message, "forbidden_tag")
|
||||
|
||||
@@ -13,7 +13,7 @@ class UpdateUserpageRequest(BaseModel):
|
||||
body: str = Field(
|
||||
description="用户页面的BBCode原始内容",
|
||||
max_length=60000,
|
||||
examples=["[b]Hello![/b] This is my profile page.\n[color=blue]Blue text[/color]"]
|
||||
examples=["[b]Hello![/b] This is my profile page.\n[color=blue]Blue text[/color]"],
|
||||
)
|
||||
|
||||
@field_validator("body")
|
||||
@@ -47,10 +47,7 @@ class UserpageResponse(BaseModel):
|
||||
class ValidateBBCodeRequest(BaseModel):
|
||||
"""验证BBCode请求模型"""
|
||||
|
||||
content: str = Field(
|
||||
description="要验证的BBCode内容",
|
||||
max_length=60000
|
||||
)
|
||||
content: str = Field(description="要验证的BBCode内容", max_length=60000)
|
||||
|
||||
|
||||
class ValidateBBCodeResponse(BaseModel):
|
||||
|
||||
@@ -25,12 +25,34 @@ class BBCodeService:
|
||||
|
||||
# 允许的HTML标签和属性 - 基于官方实现
|
||||
ALLOWED_TAGS: ClassVar[list[str]] = [
|
||||
"a", "audio", "blockquote", "br", "button", "center", "code", "del", "div", "em", "h2", "h4",
|
||||
"iframe", "img", "li", "ol", "p", "pre", "span", "strong", "u", "ul",
|
||||
"a",
|
||||
"audio",
|
||||
"blockquote",
|
||||
"br",
|
||||
"button",
|
||||
"center",
|
||||
"code",
|
||||
"del",
|
||||
"div",
|
||||
"em",
|
||||
"h2",
|
||||
"h4",
|
||||
"iframe",
|
||||
"img",
|
||||
"li",
|
||||
"ol",
|
||||
"p",
|
||||
"pre",
|
||||
"span",
|
||||
"strong",
|
||||
"u",
|
||||
"ul",
|
||||
# imagemap 相关
|
||||
"map", "area",
|
||||
"map",
|
||||
"area",
|
||||
# 自定义容器
|
||||
"details", "summary",
|
||||
"details",
|
||||
"summary",
|
||||
]
|
||||
|
||||
ALLOWED_ATTRIBUTES: ClassVar[dict[str, list[str]]] = {
|
||||
@@ -57,8 +79,22 @@ class BBCodeService:
|
||||
|
||||
# 危险的BBCode标签(不允许)
|
||||
FORBIDDEN_TAGS: ClassVar[list[str]] = [
|
||||
"script", "iframe", "object", "embed", "form", "input", "textarea",
|
||||
"select", "option", "meta", "link", "style", "title", "head", "html", "body",
|
||||
"script",
|
||||
"iframe",
|
||||
"object",
|
||||
"embed",
|
||||
"form",
|
||||
"input",
|
||||
"textarea",
|
||||
"select",
|
||||
"option",
|
||||
"meta",
|
||||
"link",
|
||||
"style",
|
||||
"title",
|
||||
"head",
|
||||
"html",
|
||||
"body",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
@@ -256,14 +292,11 @@ class BBCodeService:
|
||||
|
||||
if href == "#":
|
||||
# 无链接区域
|
||||
links.append(
|
||||
f'<span class="imagemap__link" style="{style}" title="{title}"></span>'
|
||||
)
|
||||
links.append(f'<span class="imagemap__link" style="{style}" title="{title}"></span>')
|
||||
else:
|
||||
# 有链接区域
|
||||
links.append(
|
||||
f'<a class="imagemap__link" href="{href}" style="{style}" '
|
||||
f'title="{title}"></a>'
|
||||
f'<a class="imagemap__link" href="{href}" style="{style}" title="{title}"></a>'
|
||||
)
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
@@ -340,8 +373,7 @@ class BBCodeService:
|
||||
"""解析 [quote] 标签"""
|
||||
# [quote="author"]content[/quote]
|
||||
pattern1 = r'\[quote="([^"]+)"\]\s*(.*?)\s*\[/quote\]'
|
||||
text = re.sub(pattern1, r"<blockquote><h4>\1 wrote:</h4>\2</blockquote>", text,
|
||||
flags=re.DOTALL | re.IGNORECASE)
|
||||
text = re.sub(pattern1, r"<blockquote><h4>\1 wrote:</h4>\2</blockquote>", text, flags=re.DOTALL | re.IGNORECASE)
|
||||
|
||||
# [quote]content[/quote]
|
||||
pattern2 = r"\[quote\]\s*(.*?)\s*\[/quote\]"
|
||||
|
||||
Reference in New Issue
Block a user