Files
MusicServer/app/sdk/platforms/kg/comment.py
T
snowy-yang dfc06bf4de feat(python): 完成 Python 全量迁移——剩余平台、登录通道与公共层修复
- 新增 5sing/apple/xm/me/qt/lr 平台包与取流实现(含 xm-sign、MissEvan 签名、蜻蜓 HMAC、Apple developer token)
- 新增 wy/kg/tx/lr 登录:扫码、手机号、账密(kg 20028 风控指纹)、cookie 绑定、kg/tx 续期
- request 层:保留多值 Set-Cookie;JSON body 紧凑化(修复 kg 网关验签);直连不经系统代理(对齐 Node)
- 登录路由统一 Body(embed=True),修复扫码轮询 422;lifespan 接入登录续期调度
- ruff/format/pyright 全绿;14 平台真实联网与 HTTP 路由验证通过
2026-10-23 21:55:45 +08:00

129 lines
5.1 KiB
Python

"""酷狗评论(对应 sdk/kg/comment.ts)。
签名:`signatureParams(params)` —— 参数串按键排序拼接后以 android 盐包裹取 MD5。
返回形状是 `{source, comments, total, page, limit, maxPage}`(不是 `list`),
`filterComment` 还要处理 `atlist` 替换与 `pcontent`(楼中楼)两种形态。
"""
from __future__ import annotations
import math
import time
from typing import Any
from app.sdk.common.request import http_fetch
from app.sdk.common.utils import date_format2, decode_name
from app.sdk.platforms.kg.util import signature_params
_UA = (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.24"
)
def _comment_params(hash_value: str, page: int, limit: int) -> str:
return (
"dfid=0&mid=16249512204336365674023395779019"
f"&clienttime={int(time.time() * 1000)}&uuid=0&extdata={hash_value}"
"&appid=1005&code=fc4be23b4e972707f36b8a828a93ba8a"
f"&schash={hash_value}&clientver=11409&p={page}&clienttoken=&pagesize={limit}"
"&ver=10&kugouid=0"
)
def _replace_at(raw: str, at_list: list[dict[str, Any]]) -> str:
for item in at_list:
raw = raw.replace(f"[at={item.get('id')}]", f"@{item.get('name')} ")
return raw
def _format_comment(raw_list: list[dict[str, Any]]) -> list[dict[str, Any]]:
result: list[dict[str, Any]] = []
for item in raw_list:
content = item.get("content") or ""
if item.get("atlist"):
content = _replace_at(str(content), item["atlist"])
images = [image.get("url") for image in item.get("images") or []] if item.get("images") else []
like = item.get("like") or {}
data = {
"id": item.get("id"),
"text": decode_name(content),
"images": images,
"location": item.get("location"),
"time": item.get("addtime"),
"timeStr": date_format2(item.get("addtime") or 0),
"userName": item.get("user_name"),
"avatar": item.get("user_pic"),
"userId": item.get("user_id"),
"likedCount": like.get("likenum"),
"replyNum": item.get("reply_num"),
"reply": [],
}
if item.get("pcontent"):
# 楼中楼:父评论包一层,原评论挂在 reply 里
result.append(
{
"id": item.get("id"),
"text": decode_name(item["pcontent"]),
"time": None,
"userName": item.get("puser"),
"avatar": None,
"userId": item.get("puser_id"),
"likedCount": None,
"replyNum": None,
"reply": [data],
}
)
else:
result.append(data)
return result
async def _get_comment(hash_value: str, page: int, limit: int, hot: bool) -> dict[str, Any]:
path = "topliked" if hot else "newest"
params = _comment_params(hash_value, page, limit)
resp = await http_fetch(
f"http://m.comment.service.kugou.com/r/v1/rank/{path}?{params}&signature={signature_params(params)}",
headers={"User-Agent": _UA},
)
body = resp.body if isinstance(resp.body, dict) else {}
if resp.status_code != 200 or body.get("err_code") != 0:
raise RuntimeError("获取热门评论失败" if hot else "获取评论失败")
total = body.get("count") or 0
return {
"source": "kg",
"comments": _format_comment(body.get("list") or []),
"total": total,
"page": page,
"limit": limit,
"maxPage": math.ceil(total / limit) if limit else 1,
}
async def get_comment(song_info: dict[str, Any], page: int = 1, limit: int = 20) -> dict[str, Any]:
hash_value = str(song_info.get("hash") or song_info.get("songmid") or "")
return await _get_comment(hash_value, page, limit, False)
async def get_hot_comment(song_info: dict[str, Any], page: int = 1, limit: int = 20) -> dict[str, Any]:
hash_value = str(song_info.get("hash") or song_info.get("songmid") or "")
return await _get_comment(hash_value, page, limit, True)
async def get_reply_comment(
song_info: dict[str, Any], reply_id: Any, page: int = 1, limit: int = 100
) -> dict[str, Any]:
songmid = str(song_info.get("songmid") or "")
children_id = str(song_info.get("audioId") or "").split("_")[0] if len(songmid) == 32 else songmid
resp = await http_fetch(
"http://comment.service.kugou.com/index.php?r=commentsv2/getReplyWithLike"
"&code=fc4be23b4e972707f36b8a828a93ba8a"
f"&p={page}&pagesize={limit}&ver=1.01&clientver=8373&kugouid=687373022&need_show_image=1"
f"&appid=1001&childrenid={children_id}&tid={reply_id}",
headers={"User-Agent": "Android712-AndroidPhone-8983-18-0-COMMENT-wifi"},
)
body = resp.body if isinstance(resp.body, dict) else {}
if resp.status_code != 200 or body.get("err_code") != 0:
raise RuntimeError("获取回复评论失败")
return {"source": "kg", "comments": _format_comment(body.get("list") or [])}