Skip to content

Commit

Permalink
feat: last replyer #233 #250
Browse files Browse the repository at this point in the history
  • Loading branch information
lumina37 committed Feb 26, 2025
1 parent 7188758 commit b38e030
Show file tree
Hide file tree
Showing 12 changed files with 465 additions and 8 deletions.
1 change: 1 addition & 0 deletions aiotieba/api/_protobuf/ThreadInfo.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ message ThreadInfo {
int32 is_good = 10;
int32 is_voice_thread = 15;
User author = 18;
User last_replyer = 19;
repeated Voice voice_info = 23;
int32 thread_type = 26;
int64 fid = 27;
Expand Down
16 changes: 8 additions & 8 deletions aiotieba/api/_protobuf/ThreadInfo_pb2.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from . import Voice_pb2 as Voice__pb2

DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
b"\n\x10ThreadInfo.proto\x1a\nUser.proto\x1a\x0bVoice.proto\x1a\x0ePollInfo.proto\x1a\x0fVideoInfo.proto\x1a\x0fPbContent.proto\x1a\x0b\x41gree.proto\x1a\x0bMedia.proto\"\xcc\x08\n\nThreadInfo\x12\n\n\x02id\x18\x01 \x01(\x03\x12\r\n\x05title\x18\x03 \x01(\t\x12\x11\n\treply_num\x18\x04 \x01(\x05\x12\x10\n\x08view_num\x18\x05 \x01(\x05\x12\x15\n\rlast_time_int\x18\x07 \x01(\x05\x12\x0e\n\x06is_top\x18\t \x01(\x05\x12\x0f\n\x07is_good\x18\n \x01(\x05\x12\x17\n\x0fis_voice_thread\x18\x0f \x01(\x05\x12\x15\n\x06\x61uthor\x18\x12 \x01(\x0b\x32\x05.User\x12\x1a\n\nvoice_info\x18\x17 \x03(\x0b\x32\x06.Voice\x12\x13\n\x0bthread_type\x18\x1a \x01(\x05\x12\x0b\n\x03\x66id\x18\x1b \x01(\x03\x12\r\n\x05\x66name\x18\x1c \x01(\t\x12\x13\n\x0bis_livepost\x18\x1e \x01(\x05\x12\x15\n\rfirst_post_id\x18( \x01(\x03\x12\x13\n\x0b\x63reate_time\x18- \x01(\x05\x12\x0f\n\x07post_id\x18\x34 \x01(\x03\x12\x11\n\tauthor_id\x18\x38 \x01(\x03\x12\r\n\x05is_ad\x18; \x01(\r\x12\x1c\n\tpoll_info\x18J \x01(\x0b\x32\t.PollInfo\x12\x1e\n\nvideo_info\x18O \x01(\x0b\x32\n.VideoInfo\x12\x1e\n\x16is_godthread_recommend\x18U \x01(\x05\x12\x15\n\x05\x61gree\x18~ \x01(\x0b\x32\x06.Agree\x12\x12\n\tshare_num\x18\x87\x01 \x01(\x05\x12\x39\n\x12origin_thread_info\x18\x8d\x01 \x01(\x0b\x32\x1c.ThreadInfo.OriginThreadInfo\x12'\n\x12\x66irst_post_content\x18\x8e\x01 \x03(\x0b\x32\n.PbContent\x12\x18\n\x0fis_share_thread\x18\x8f\x01 \x01(\x05\x12\x0f\n\x06tab_id\x18\xaf\x01 \x01(\x05\x12\x13\n\nis_deleted\x18\xb5\x01 \x01(\x05\x12\x14\n\x0bis_frs_mask\x18\xc6\x01 \x01(\x05\x12\x30\n\rcustom_figure\x18\xd3\x01 \x01(\x0b\x32\x18.ThreadInfo.CustomFigure\x12.\n\x0c\x63ustom_state\x18\xd4\x01 \x01(\x0b\x32\x17.ThreadInfo.CustomState\x1a\xe5\x01\n\x10OriginThreadInfo\x12\r\n\x05title\x18\x01 \x01(\t\x12\x15\n\x05media\x18\x02 \x03(\x0b\x32\x06.Media\x12\r\n\x05\x66name\x18\x04 \x01(\t\x12\x0b\n\x03tid\x18\x05 \x01(\t\x12\x0b\n\x03\x66id\x18\x07 \x01(\x03\x12\x1a\n\nvoice_info\x18\x0c \x03(\x0b\x32\x06.Voice\x12\x1e\n\nvideo_info\x18\r \x01(\x0b\x32\n.VideoInfo\x12\x1b\n\x07\x63ontent\x18\x0e \x03(\x0b\x32\n.PbContent\x12\x1c\n\tpoll_info\x18\x15 \x01(\x0b\x32\t.PollInfo\x12\x0b\n\x03pid\x18\x19 \x01(\x03\x1a(\n\x0c\x43ustomFigure\x12\x18\n\x10\x62\x61\x63kground_value\x18\x03 \x01(\t\x1a\x1e\n\x0b\x43ustomState\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\tb\x06proto3"
b"\n\x10ThreadInfo.proto\x1a\nUser.proto\x1a\x0bVoice.proto\x1a\x0ePollInfo.proto\x1a\x0fVideoInfo.proto\x1a\x0fPbContent.proto\x1a\x0b\x41gree.proto\x1a\x0bMedia.proto\"\xe9\x08\n\nThreadInfo\x12\n\n\x02id\x18\x01 \x01(\x03\x12\r\n\x05title\x18\x03 \x01(\t\x12\x11\n\treply_num\x18\x04 \x01(\x05\x12\x10\n\x08view_num\x18\x05 \x01(\x05\x12\x15\n\rlast_time_int\x18\x07 \x01(\x05\x12\x0e\n\x06is_top\x18\t \x01(\x05\x12\x0f\n\x07is_good\x18\n \x01(\x05\x12\x17\n\x0fis_voice_thread\x18\x0f \x01(\x05\x12\x15\n\x06\x61uthor\x18\x12 \x01(\x0b\x32\x05.User\x12\x1b\n\x0clast_replyer\x18\x13 \x01(\x0b\x32\x05.User\x12\x1a\n\nvoice_info\x18\x17 \x03(\x0b\x32\x06.Voice\x12\x13\n\x0bthread_type\x18\x1a \x01(\x05\x12\x0b\n\x03\x66id\x18\x1b \x01(\x03\x12\r\n\x05\x66name\x18\x1c \x01(\t\x12\x13\n\x0bis_livepost\x18\x1e \x01(\x05\x12\x15\n\rfirst_post_id\x18( \x01(\x03\x12\x13\n\x0b\x63reate_time\x18- \x01(\x05\x12\x0f\n\x07post_id\x18\x34 \x01(\x03\x12\x11\n\tauthor_id\x18\x38 \x01(\x03\x12\r\n\x05is_ad\x18; \x01(\r\x12\x1c\n\tpoll_info\x18J \x01(\x0b\x32\t.PollInfo\x12\x1e\n\nvideo_info\x18O \x01(\x0b\x32\n.VideoInfo\x12\x1e\n\x16is_godthread_recommend\x18U \x01(\x05\x12\x15\n\x05\x61gree\x18~ \x01(\x0b\x32\x06.Agree\x12\x12\n\tshare_num\x18\x87\x01 \x01(\x05\x12\x39\n\x12origin_thread_info\x18\x8d\x01 \x01(\x0b\x32\x1c.ThreadInfo.OriginThreadInfo\x12'\n\x12\x66irst_post_content\x18\x8e\x01 \x03(\x0b\x32\n.PbContent\x12\x18\n\x0fis_share_thread\x18\x8f\x01 \x01(\x05\x12\x0f\n\x06tab_id\x18\xaf\x01 \x01(\x05\x12\x13\n\nis_deleted\x18\xb5\x01 \x01(\x05\x12\x14\n\x0bis_frs_mask\x18\xc6\x01 \x01(\x05\x12\x30\n\rcustom_figure\x18\xd3\x01 \x01(\x0b\x32\x18.ThreadInfo.CustomFigure\x12.\n\x0c\x63ustom_state\x18\xd4\x01 \x01(\x0b\x32\x17.ThreadInfo.CustomState\x1a\xe5\x01\n\x10OriginThreadInfo\x12\r\n\x05title\x18\x01 \x01(\t\x12\x15\n\x05media\x18\x02 \x03(\x0b\x32\x06.Media\x12\r\n\x05\x66name\x18\x04 \x01(\t\x12\x0b\n\x03tid\x18\x05 \x01(\t\x12\x0b\n\x03\x66id\x18\x07 \x01(\x03\x12\x1a\n\nvoice_info\x18\x0c \x03(\x0b\x32\x06.Voice\x12\x1e\n\nvideo_info\x18\r \x01(\x0b\x32\n.VideoInfo\x12\x1b\n\x07\x63ontent\x18\x0e \x03(\x0b\x32\n.PbContent\x12\x1c\n\tpoll_info\x18\x15 \x01(\x0b\x32\t.PollInfo\x12\x0b\n\x03pid\x18\x19 \x01(\x03\x1a(\n\x0c\x43ustomFigure\x12\x18\n\x10\x62\x61\x63kground_value\x18\x03 \x01(\t\x1a\x1e\n\x0b\x43ustomState\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\tb\x06proto3"
)

_globals = globals()
Expand All @@ -26,10 +26,10 @@
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals["_THREADINFO"]._serialized_start = 122
_globals["_THREADINFO"]._serialized_end = 1222
_globals["_THREADINFO_ORIGINTHREADINFO"]._serialized_start = 919
_globals["_THREADINFO_ORIGINTHREADINFO"]._serialized_end = 1148
_globals["_THREADINFO_CUSTOMFIGURE"]._serialized_start = 1150
_globals["_THREADINFO_CUSTOMFIGURE"]._serialized_end = 1190
_globals["_THREADINFO_CUSTOMSTATE"]._serialized_start = 1192
_globals["_THREADINFO_CUSTOMSTATE"]._serialized_end = 1222
_globals["_THREADINFO"]._serialized_end = 1251
_globals["_THREADINFO_ORIGINTHREADINFO"]._serialized_start = 948
_globals["_THREADINFO_ORIGINTHREADINFO"]._serialized_end = 1177
_globals["_THREADINFO_CUSTOMFIGURE"]._serialized_start = 1179
_globals["_THREADINFO_CUSTOMFIGURE"]._serialized_end = 1219
_globals["_THREADINFO_CUSTOMSTATE"]._serialized_start = 1221
_globals["_THREADINFO_CUSTOMSTATE"]._serialized_end = 1251
2 changes: 2 additions & 0 deletions aiotieba/api/get_last_replyers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from ._api import CMD, pack_proto, parse_body, request_http, request_ws
from ._classdef import Thread_lp, Threads_lp, UserInfo_lp
55 changes: 55 additions & 0 deletions aiotieba/api/get_last_replyers/_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import yarl

from ...const import APP_BASE_HOST
from ...core import HttpCore, WsCore
from ...exception import TiebaServerError
from ._classdef import Threads_lp
from .protobuf import FrsPageReqIdl4lp_pb2, FrsPageResIdl4lp_pb2

CMD = 301001


def pack_proto(fname: str, pn: int, rn: int, sort: int, is_good: bool) -> bytes:
req_proto = FrsPageReqIdl4lp_pb2.FrsPageReqIdl4lp()
req_proto.data.common._client_type = 2
req_proto.data.common._client_version = "6.0.1"
req_proto.data.kw = fname
req_proto.data.pn = 0 if pn == 1 else pn
req_proto.data.rn = rn
req_proto.data.rn_need = rn + 5
req_proto.data.is_good = is_good
req_proto.data.sort_type = sort

return req_proto.SerializeToString()


def parse_body(body: bytes) -> Threads_lp:
res_proto = FrsPageResIdl4lp_pb2.FrsPageResIdl4lp()
res_proto.ParseFromString(body)

if code := res_proto.error.errorno:
raise TiebaServerError(code, res_proto.error.errmsg)

data_proto = res_proto.data
threads = Threads_lp.from_tbdata(data_proto)

return threads


async def request_http(http_core: HttpCore, fname: str, pn: int, rn: int, sort: int, is_good: bool) -> Threads_lp:
data = pack_proto(fname, pn, rn, sort, is_good)

request = http_core.pack_proto_request(
yarl.URL.build(scheme="http", host=APP_BASE_HOST, path="/c/f/frs/page", query_string=f"cmd={CMD}"),
data,
)

body = await http_core.net_core.send_request(request, read_bufsize=256 * 1024)
return parse_body(body)


async def request_ws(ws_core: WsCore, fname: str, pn: int, rn: int, sort: int, is_good: bool) -> Threads_lp:
data = pack_proto(fname, pn, rn, sort, is_good)

response = await ws_core.send(data, CMD)
return parse_body(await response.read())
268 changes: 268 additions & 0 deletions aiotieba/api/get_last_replyers/_classdef.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
from __future__ import annotations

import dataclasses as dcs
from functools import cached_property

from ...exception import TbErrorExt
from .._classdef import Containers, TypeMessage


@dcs.dataclass
class Page_lp:
"""
页信息
Attributes:
page_size (int): 页大小
current_page (int): 当前页码
total_page (int): 总页码
total_count (int): 总计数
has_more (bool): 是否有后继页
has_prev (bool): 是否有前驱页
"""

page_size: int = 0
current_page: int = 0
total_page: int = 0
total_count: int = 0

has_more: bool = False
has_prev: bool = False

@staticmethod
def from_tbdata(data_proto: TypeMessage) -> Page_lp:
page_size = data_proto.page_size
current_page = data_proto.current_page
total_page = data_proto.total_page
total_count = data_proto.total_count
has_more = bool(data_proto.has_more)
has_prev = bool(data_proto.has_prev)
return Page_lp(page_size, current_page, total_page, total_count, has_more, has_prev)


@dcs.dataclass
class UserInfo_lp:
"""
用户信息
Attributes:
user_id (int): user_id
portrait (str): portrait
user_name (str): 用户名
nick_name_old (str): 旧版昵称
nick_name (str): 用户昵称
show_name (str): 显示名称
log_name (str): 用于在日志中记录用户信息
"""

user_id: int = 0
portrait: str = ""
user_name: str = ""
nick_name_old: str = ""

@staticmethod
def from_tbdata(data_proto: TypeMessage) -> UserInfo_lp:
user_id = data_proto.id
portrait = data_proto.portrait
if "?" in portrait:
portrait = portrait[:-13]
user_name = data_proto.name
nick_name_old = data_proto.name_show
return UserInfo_lp(user_id, portrait, user_name, nick_name_old)

def __str__(self) -> str:
return self.user_name or self.portrait or str(self.user_id)

def __eq__(self, obj: UserInfo_lp) -> bool:
return self.user_id == obj.user_id

def __hash__(self) -> int:
return self.user_id

def __bool__(self) -> bool:
return bool(self.user_id)

@property
def nick_name(self) -> str:
return self.nick_name_old

@property
def show_name(self) -> str:
return self.nick_name_old or self.user_name

@cached_property
def log_name(self) -> str:
if self.user_name:
return self.user_name
elif self.portrait:
return f"{self.nick_name_old}/{self.portrait}"
else:
return str(self.user_id)


@dcs.dataclass
class LastReplyer:
"""
最后回复者的用户信息
Attributes:
user_id (int): user_id
user_name (str): 用户名
nick_name_old (str): 旧版昵称
nick_name (str): 用户昵称
show_name (str): 显示名称
log_name (str): 用于在日志中记录用户信息
"""

user_id: int = 0
user_name: str = ""
nick_name_old: str = ""

@staticmethod
def from_tbdata(data_proto: TypeMessage) -> LastReplyer:
user_id = data_proto.id
user_name = data_proto.name
nick_name_old = data_proto.name_show
return LastReplyer(user_id, user_name, nick_name_old)

def __str__(self) -> str:
return self.user_name or str(self.user_id)

def __eq__(self, obj: LastReplyer) -> bool:
return self.user_id == obj.user_id

def __hash__(self) -> int:
return self.user_id

def __bool__(self) -> bool:
return bool(self.user_id)

@property
def nick_name(self) -> str:
return self.nick_name_old

@property
def show_name(self) -> str:
return self.nick_name_old or self.user_name

@cached_property
def log_name(self) -> str:
return self.user_name or str(self.user_id)


@dcs.dataclass
class Thread_lp:
"""
主题帖信息
Attributes:
text (str): 文本内容
title (str): 标题内容
fid (int): 所在吧id
fname (str): 所在贴吧名
tid (int): 主题帖tid
pid (int): 首楼回复pid
user (UserInfo_lp): 发布者的用户信息
author_id (int): 发布者的user_id
last_replyer (LastReplyer): 最后回复者的用户信息
create_time (int): 创建时间 10位时间戳 以秒为单位
last_time (int): 最后回复时间 10位时间戳 以秒为单位
"""

title: str = ""

fid: int = 0
fname: str = ""
tid: int = 0
pid: int = 0
user: UserInfo_lp = dcs.field(default_factory=UserInfo_lp)
last_replyer: LastReplyer = dcs.field(default_factory=LastReplyer)

create_time: int = 0
last_time: int = 0

@staticmethod
def from_tbdata(data_proto: TypeMessage) -> None:
title = data_proto.title
tid = data_proto.id
pid = data_proto.first_post_id
user = UserInfo_lp.from_tbdata(data_proto.author)
last_replyer = LastReplyer.from_tbdata(data_proto.last_replyer)
create_time = data_proto.create_time
last_time = data_proto.last_time_int
return Thread_lp(title, 0, "", tid, pid, user, last_replyer, create_time, last_time)

def __eq__(self, obj: Thread_lp) -> bool:
return self.pid == obj.pid

def __hash__(self) -> int:
return self.pid

@property
def text(self) -> str:
return self.title

@property
def author_id(self) -> int:
return self.user.user_id


@dcs.dataclass
class Forum_lp:
"""
吧信息
Attributes:
fid (int): 贴吧id
fname (str): 贴吧名
"""

fid: int = 0
fname: str = ""

@staticmethod
def from_tbdata(data_proto: TypeMessage) -> Forum_lp:
forum_proto = data_proto.forum
fid = forum_proto.id
fname = forum_proto.name
return Forum_lp(fid, fname)


@dcs.dataclass
class Threads_lp(TbErrorExt, Containers[Thread_lp]):
"""
主题帖列表
Attributes:
objs (list[Thread_lp]): 主题帖列表
err (Exception | None): 捕获的异常
page (Page_lp): 页信息
has_more (bool): 是否还有下一页
forum (Forum_lp): 所在吧信息
"""

page: Page_lp = dcs.field(default_factory=Page_lp)
forum: Forum_lp = dcs.field(default_factory=Forum_lp)

@staticmethod
def from_tbdata(data_proto: TypeMessage) -> Threads_lp:
page = Page_lp.from_tbdata(data_proto.page)
forum = Forum_lp.from_tbdata(data_proto)

objs = [Thread_lp.from_tbdata(p) for p in data_proto.thread_list]
for thread in objs:
thread.fname = forum.fname
thread.fid = forum.fid

return Threads_lp(objs, page, forum)

@property
def has_more(self) -> bool:
return self.page.has_more
18 changes: 18 additions & 0 deletions aiotieba/api/get_last_replyers/protobuf/FrsPageReqIdl4lp.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// tbclient.FrsPage.FrsPageReqIdl
syntax = "proto3";

import "CommonReq.proto";

message FrsPageReqIdl4lp {
message DataReq {
CommonReq common = 39;
string kw = 1;
int32 rn = 2;
int32 rn_need = 3;
int32 is_good = 4;
int32 cid = 5;
int32 pn = 15;
int32 sort_type = 47;
}
DataReq data = 1;
}
Loading

0 comments on commit b38e030

Please sign in to comment.