Repository: Starry-OvO/aiotieba Branch: master Commit: 2d39220dfce3 Files: 504 Total size: 1.1 MB Directory structure: gitextract_4rqd5pud/ ├── .clang-format ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── discussion.md │ │ └── feature_request.md │ ├── dependabot.yml │ └── workflows/ │ ├── CI-beta.yml │ ├── CI.yml │ ├── Pages.yml │ └── Publish.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs/ │ ├── CNAME │ ├── css/ │ │ └── custom.css │ ├── index.md │ ├── ref/ │ │ ├── classdef/ │ │ │ ├── ats.md │ │ │ ├── bawu_blacklist.md │ │ │ ├── bawu_info.md │ │ │ ├── bawu_perm.md │ │ │ ├── bawu_postlogs.md │ │ │ ├── bawu_userlogs.md │ │ │ ├── blacklist.md │ │ │ ├── blacklist_old.md │ │ │ ├── blocks.md │ │ │ ├── comments.md │ │ │ ├── dislike_forums.md │ │ │ ├── fans.md │ │ │ ├── follow_forums.md │ │ │ ├── follows.md │ │ │ ├── forum_detail.md │ │ │ ├── group_msg.md │ │ │ ├── images.md │ │ │ ├── last_replyers.md │ │ │ ├── member_users.md │ │ │ ├── posts.md │ │ │ ├── profile.md │ │ │ ├── rank_users.md │ │ │ ├── recom_status.md │ │ │ ├── recover_thread.md │ │ │ ├── recovers.md │ │ │ ├── replys.md │ │ │ ├── searches.md │ │ │ ├── self_follow_forums.md │ │ │ ├── square_forums.md │ │ │ ├── statistics.md │ │ │ ├── threads.md │ │ │ ├── unblock_appeals.md │ │ │ ├── user_contents.md │ │ │ └── user_info.md │ │ ├── client.md │ │ ├── config.md │ │ ├── enums.md │ │ └── exception.md │ └── tutorial/ │ ├── async_start.md │ ├── many_utils.md │ └── start.md ├── mkdocs.yml ├── pyproject.toml ├── scripts/ │ └── proto_compile.py ├── src/ │ └── aiotieba/ │ ├── __init__.py │ ├── __version__.py │ ├── api/ │ │ ├── __init__.py │ │ ├── _classdef/ │ │ │ ├── __init__.py │ │ │ ├── common.py │ │ │ ├── container.py │ │ │ ├── contents.py │ │ │ ├── user.py │ │ │ └── vote.py │ │ ├── _protobuf/ │ │ │ ├── Agree.proto │ │ │ ├── Agree_pb2.py │ │ │ ├── CommonReq.proto │ │ │ ├── CommonReq_pb2.py │ │ │ ├── Error.proto │ │ │ ├── Error_pb2.py │ │ │ ├── ForumList.proto │ │ │ ├── ForumList_pb2.py │ │ │ ├── FrsTabInfo.proto │ │ │ ├── FrsTabInfo_pb2.py │ │ │ ├── Lcm.proto │ │ │ ├── Lcm_pb2.py │ │ │ ├── Media.proto │ │ │ ├── Media_pb2.py │ │ │ ├── Page.proto │ │ │ ├── Page_pb2.py │ │ │ ├── PbContent.proto │ │ │ ├── PbContent_pb2.py │ │ │ ├── PollInfo.proto │ │ │ ├── PollInfo_pb2.py │ │ │ ├── Post.proto │ │ │ ├── PostInfoList.proto │ │ │ ├── PostInfoList_pb2.py │ │ │ ├── Post_pb2.py │ │ │ ├── Rpc.proto │ │ │ ├── Rpc_pb2.py │ │ │ ├── SimpleForum.proto │ │ │ ├── SimpleForum_pb2.py │ │ │ ├── SubPostList.proto │ │ │ ├── SubPostList_pb2.py │ │ │ ├── ThreadInfo.proto │ │ │ ├── ThreadInfo_pb2.py │ │ │ ├── User.proto │ │ │ ├── User_pb2.py │ │ │ ├── VideoInfo.proto │ │ │ ├── VideoInfo_pb2.py │ │ │ ├── Voice.proto │ │ │ ├── Voice_pb2.py │ │ │ └── __init__.py │ │ ├── add_bawu/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── add_bawu_blacklist/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── add_blacklist_old/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── add_post/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── protobuf/ │ │ │ ├── AddPostReqIdl.proto │ │ │ ├── AddPostReqIdl_pb2.py │ │ │ ├── AddPostResIdl.proto │ │ │ └── AddPostResIdl_pb2.py │ │ ├── agree/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── block/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── del_bawu/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── del_bawu_blacklist/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── del_blacklist_old/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── del_post/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── del_posts/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── del_thread/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── del_threads/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── dislike_forum/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── follow_forum/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── follow_user/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── get_ats/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_bawu_blacklist/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_bawu_info/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── GetBawuInfoReqIdl.proto │ │ │ ├── GetBawuInfoReqIdl_pb2.py │ │ │ ├── GetBawuInfoResIdl.proto │ │ │ └── GetBawuInfoResIdl_pb2.py │ │ ├── get_bawu_perm/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_bawu_postlogs/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_bawu_userlogs/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_blacklist/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_blacklist_old/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── UserMuteQueryReqIdl.proto │ │ │ ├── UserMuteQueryReqIdl_pb2.py │ │ │ ├── UserMuteQueryResIdl.proto │ │ │ └── UserMuteQueryResIdl_pb2.py │ │ ├── get_blocks/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_cid/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── get_comments/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── PbFloorReqIdl.proto │ │ │ ├── PbFloorReqIdl_pb2.py │ │ │ ├── PbFloorResIdl.proto │ │ │ └── PbFloorResIdl_pb2.py │ │ ├── get_dislike_forums/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── GetDislikeListReqIdl.proto │ │ │ ├── GetDislikeListReqIdl_pb2.py │ │ │ ├── GetDislikeListResIdl.proto │ │ │ └── GetDislikeListResIdl_pb2.py │ │ ├── get_fans/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_fid/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── get_follow_forums/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_follows/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_forum/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_forum_detail/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── GetForumDetailReqIdl.proto │ │ │ ├── GetForumDetailReqIdl_pb2.py │ │ │ ├── GetForumDetailResIdl.proto │ │ │ └── GetForumDetailResIdl_pb2.py │ │ ├── get_forum_level/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── GetLevelInfoReqIdl.proto │ │ │ ├── GetLevelInfoReqIdl_pb2.py │ │ │ ├── GetLevelInfoResIdl.proto │ │ │ └── GetLevelInfoResIdl_pb2.py │ │ ├── get_group_msg/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── GetGroupMsgReqIdl.proto │ │ │ ├── GetGroupMsgReqIdl_pb2.py │ │ │ ├── GetGroupMsgResIdl.proto │ │ │ └── GetGroupMsgResIdl_pb2.py │ │ ├── get_images/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_last_replyers/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── FrsPageReqIdl4lp.proto │ │ │ ├── FrsPageReqIdl4lp_pb2.py │ │ │ ├── FrsPageResIdl4lp.proto │ │ │ └── FrsPageResIdl4lp_pb2.py │ │ ├── get_member_users/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_posts/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── PbPageReqIdl.proto │ │ │ ├── PbPageReqIdl_pb2.py │ │ │ ├── PbPageResIdl.proto │ │ │ └── PbPageResIdl_pb2.py │ │ ├── get_rank_forums/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_rank_users/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_recom_status/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_recover_info/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_recovers/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_replys/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── ReplyMeReqIdl.proto │ │ │ ├── ReplyMeReqIdl_pb2.py │ │ │ ├── ReplyMeResIdl.proto │ │ │ └── ReplyMeResIdl_pb2.py │ │ ├── get_roomlist_by_fid/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_self_follow_forums/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_self_follow_forums_v1/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_selfinfo_initNickname/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_selfinfo_moindex/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_square_forums/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── GetForumSquareReqIdl.proto │ │ │ ├── GetForumSquareReqIdl_pb2.py │ │ │ ├── GetForumSquareResIdl.proto │ │ │ └── GetForumSquareResIdl_pb2.py │ │ ├── get_statistics/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_tab_map/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── SearchPostForumReqIdl.proto │ │ │ ├── SearchPostForumReqIdl_pb2.py │ │ │ ├── SearchPostForumResIdl.proto │ │ │ └── SearchPostForumResIdl_pb2.py │ │ ├── get_threads/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── FrsPageReqIdl.proto │ │ │ ├── FrsPageReqIdl_pb2.py │ │ │ ├── FrsPageResIdl.proto │ │ │ └── FrsPageResIdl_pb2.py │ │ ├── get_uinfo_getUserInfo_web/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_uinfo_getuserinfo_app/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── GetUserInfoReqIdl.proto │ │ │ ├── GetUserInfoReqIdl_pb2.py │ │ │ ├── GetUserInfoResIdl.proto │ │ │ └── GetUserInfoResIdl_pb2.py │ │ ├── get_uinfo_panel/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_uinfo_user_json/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_unblock_appeals/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── get_user_contents/ │ │ │ ├── __init__.py │ │ │ ├── _classdef.py │ │ │ ├── _const.py │ │ │ ├── get_posts/ │ │ │ │ ├── __init__.py │ │ │ │ └── _api.py │ │ │ ├── get_posts_form/ │ │ │ │ ├── __init__.py │ │ │ │ └── _api.py │ │ │ ├── get_threads/ │ │ │ │ ├── __init__.py │ │ │ │ └── _api.py │ │ │ └── protobuf/ │ │ │ ├── UserPostReqIdl.proto │ │ │ ├── UserPostReqIdl_pb2.py │ │ │ ├── UserPostResIdl.proto │ │ │ └── UserPostResIdl_pb2.py │ │ ├── get_user_forum_info/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── good/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── handle_unblock_appeals/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── init_websocket/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── UpdateClientInfoReqIdl.proto │ │ │ ├── UpdateClientInfoReqIdl_pb2.py │ │ │ ├── UpdateClientInfoResIdl.proto │ │ │ └── UpdateClientInfoResIdl_pb2.py │ │ ├── init_z_id/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── login/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── move/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── profile/ │ │ │ ├── __init__.py │ │ │ ├── _classdef.py │ │ │ ├── _const.py │ │ │ ├── get_homepage/ │ │ │ │ ├── __init__.py │ │ │ │ └── _api.py │ │ │ ├── get_uinfo_profile/ │ │ │ │ ├── __init__.py │ │ │ │ └── _api.py │ │ │ └── protobuf/ │ │ │ ├── ProfileReqIdl.proto │ │ │ ├── ProfileReqIdl_pb2.py │ │ │ ├── ProfileResIdl.proto │ │ │ └── ProfileResIdl_pb2.py │ │ ├── push_notify/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── PushNotifyResIdl.proto │ │ │ └── PushNotifyResIdl_pb2.py │ │ ├── recommend/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── recover/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── remove_fan/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── search_exact/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── _classdef.py │ │ ├── send_chatroom_msg/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── send_msg/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── protobuf/ │ │ │ ├── CommitPersonalMsgReqIdl.proto │ │ │ ├── CommitPersonalMsgReqIdl_pb2.py │ │ │ ├── CommitPersonalMsgResIdl.proto │ │ │ └── CommitPersonalMsgResIdl_pb2.py │ │ ├── set_bawu_perm/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── set_blacklist/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── protobuf/ │ │ │ ├── SetUserBlackReqIdl.proto │ │ │ ├── SetUserBlackReqIdl_pb2.py │ │ │ ├── SetUserBlackResIdl.proto │ │ │ └── SetUserBlackResIdl_pb2.py │ │ ├── set_msg_readed/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ └── protobuf/ │ │ │ ├── CommitReceivedPmsgReqIdl.proto │ │ │ ├── CommitReceivedPmsgReqIdl_pb2.py │ │ │ ├── CommitReceivedPmsgResIdl.proto │ │ │ └── CommitReceivedPmsgResIdl_pb2.py │ │ ├── set_nickname_old/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── set_profile/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── set_thread_privacy/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── sign_forum/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── sign_forums/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── sign_growth/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── sync/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── tieba_uid2user_info/ │ │ │ ├── __init__.py │ │ │ ├── _api.py │ │ │ ├── _classdef.py │ │ │ └── protobuf/ │ │ │ ├── GetUserByTiebaUidReqIdl.proto │ │ │ ├── GetUserByTiebaUidReqIdl_pb2.py │ │ │ ├── GetUserByTiebaUidResIdl.proto │ │ │ └── GetUserByTiebaUidResIdl_pb2.py │ │ ├── top/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── unblock/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── undislike_forum/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── unfollow_forum/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ ├── unfollow_user/ │ │ │ ├── __init__.py │ │ │ └── _api.py │ │ └── ungood/ │ │ ├── __init__.py │ │ └── _api.py │ ├── client.py │ ├── config.py │ ├── const.py │ ├── core/ │ │ ├── __init__.py │ │ ├── account.py │ │ ├── blcp.py │ │ ├── http.py │ │ ├── net.py │ │ └── websocket.py │ ├── enums.py │ ├── exception.py │ ├── helper/ │ │ ├── __init__.py │ │ ├── cache.py │ │ ├── crypto/ │ │ │ ├── CMakeLists.txt │ │ │ ├── __init__.py │ │ │ ├── crypto.pyi │ │ │ ├── include/ │ │ │ │ ├── base32/ │ │ │ │ │ └── base32.h │ │ │ │ ├── crc/ │ │ │ │ │ └── crc32.h │ │ │ │ ├── mbedtls/ │ │ │ │ │ ├── alignment.h │ │ │ │ │ ├── common.h │ │ │ │ │ ├── md5.h │ │ │ │ │ ├── private_access.h │ │ │ │ │ └── sha1.h │ │ │ │ ├── rapidjson/ │ │ │ │ │ └── itoa.h │ │ │ │ ├── tbcrypto/ │ │ │ │ │ ├── bb64.h │ │ │ │ │ ├── const.h │ │ │ │ │ ├── cuid.h │ │ │ │ │ ├── error.h │ │ │ │ │ ├── pywrap.h │ │ │ │ │ ├── rc442.h │ │ │ │ │ └── sign.h │ │ │ │ └── xxHash/ │ │ │ │ └── xxhash.h │ │ │ └── src/ │ │ │ ├── base32/ │ │ │ │ └── base32.c │ │ │ ├── crc/ │ │ │ │ └── crc32.c │ │ │ ├── mbedtls/ │ │ │ │ ├── md5.c │ │ │ │ └── sha1.c │ │ │ └── tbcrypto/ │ │ │ ├── bb64.c │ │ │ ├── cuid.c │ │ │ ├── lib.c │ │ │ ├── rc442.c │ │ │ └── sign.c │ │ └── utils.py │ ├── logging.py │ └── typing.py └── tests/ ├── conftest.py ├── test_crypto.py ├── test_get_ats.py ├── test_get_blocks.py ├── test_get_comments.py ├── test_get_fans.py ├── test_get_follow_forums.py ├── test_get_follows.py ├── test_get_forum_detail.py ├── test_get_homepage.py ├── test_get_posts.py ├── test_get_recovers.py ├── test_get_threads.py ├── test_get_user_info.py └── test_get_user_posts.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ BasedOnStyle: Google IndentWidth: 4 ColumnLimit: 120 --- Language: C AccessModifierOffset: -4 IndentPPDirectives: AfterHash IncludeBlocks: Preserve ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf *.py text eol=lf *.pyi text eol=lf *.pyx text eol=lf *.pxd text eol=lf *.h text eol=lf *.c text eol=lf *.cpp text eol=lf *.hpp text eol=lf ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Bug报告 title: '' labels: '' assignees: '' --- **简要描述这个bug** ... **如何复现** 在何种场景下用何种操作复现 **你希望程序作出何种行为** ... **截图(可选)** ... ================================================ FILE: .github/ISSUE_TEMPLATE/discussion.md ================================================ --- name: Discussion about: 技术交流 title: '' labels: '' assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: 功能需求 title: '' labels: '' assignees: '' --- **我需要什么功能** ... **我想将这个功能应用于何种场景** ... ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "pip" directory: "/" target-branch: "develop" schedule: interval: "daily" - package-ecosystem: "github-actions" directory: "/" target-branch: "develop" schedule: interval: "daily" ================================================ FILE: .github/workflows/CI-beta.yml ================================================ name: CI-beta on: workflow_dispatch: jobs: test-beta: name: Test-beta runs-on: ubuntu-latest environment: develop env: PYTHON_VERSION: "3.15" steps: - name: Checkout uses: actions/checkout@v6 with: ref: develop - name: Setup UV uses: astral-sh/setup-uv@v7 - name: Install dependencies run: | uv python pin ${{ env.PYTHON_VERSION }} uv sync - name: Run tests env: TB_BDUSS: ${{ secrets.BDUSS }} TB_STOKEN: ${{ secrets.STOKEN }} run: uv run pytest ================================================ FILE: .github/workflows/CI.yml ================================================ name: CI on: schedule: - cron: "42 6 * * *" push: branches: [develop] paths: - "src/**" - "tests/**" - ".github/workflows/CI.yml" pull_request: branches: [develop] paths: - "src/**" - "tests/**" - ".github/workflows/CI.yml" workflow_dispatch: jobs: test: name: Test runs-on: ubuntu-latest environment: develop strategy: matrix: python-version: ["3.10", "3.14"] steps: - name: Checkout uses: actions/checkout@v6 with: ref: develop - name: Setup UV uses: astral-sh/setup-uv@v7 - name: Install dependencies run: | uv python pin ${{ matrix.python-version }} uv sync - name: Run tests env: TB_BDUSS: ${{ secrets.BDUSS }} TB_STOKEN: ${{ secrets.STOKEN }} run: uv run pytest ================================================ FILE: .github/workflows/Pages.yml ================================================ name: Pages on: push: branches: [develop] paths: - "mkdocs.yml" - "docs/**" - "pyproject.toml" - ".github/workflows/Pages*" pull_request: branches: [develop] paths: - "mkdocs.yml" - "docs/**" - "pyproject.toml" - ".github/workflows/Pages*" jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 with: ref: develop - name: Setup UV uses: astral-sh/setup-uv@v7 - name: Install dependencies run: uv sync --only-group docs - name: Build run: uv run mkdocs build -d site - name: Upload Artifact uses: actions/upload-pages-artifact@v5 with: path: site deploy: needs: build runs-on: ubuntu-latest permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 ================================================ FILE: .github/workflows/Publish.yml ================================================ name: Publish on: push: tags: - "*" workflow_dispatch: jobs: build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-15-intel"] steps: - name: Checkout uses: actions/checkout@v6 - name: Setup UV uses: astral-sh/setup-uv@v7 - name: Build wheels uses: pypa/cibuildwheel@v3.4.1 - uses: actions/upload-artifact@v7 with: name: artifact-wheels-${{ matrix.os }} path: ./wheelhouse/*.whl build_sdist: name: Build source distribution runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Build sdist run: pipx run build --sdist - uses: actions/upload-artifact@v7 with: name: artifact-source path: ./dist/*.tar.gz publish: name: Publish needs: [build_wheels, build_sdist] runs-on: ubuntu-latest environment: name: pypi permissions: id-token: write steps: - name: Merge artifacts uses: actions/upload-artifact/merge@v7 with: name: artifact pattern: artifact-* delete-merged: true - name: Download artifacts uses: actions/download-artifact@v8 with: name: artifact path: dist - name: Publish to PyPI if: ${{ github.event_name == 'push' }} uses: pypa/gh-action-pypi-publish@release/v1 - name: Publish to TestPyPI if: ${{ github.event_name == 'workflow_dispatch' }} uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ ================================================ FILE: .gitignore ================================================ .*/ !.github/ *_cache/ *.py[cd] __pycache__ log/ dist/ *build*/ .python-version *.lock /*.py account.toml database.toml update.md ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15 FATAL_ERROR) if (NOT SKBUILD_PROJECT_NAME) set(SKBUILD_PROJECT_NAME "aiotieba") endif () if (NOT SKBUILD_PROJECT_VERSION) set(SKBUILD_PROJECT_VERSION 0) endif () project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION}) find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module) add_subdirectory(src/${SKBUILD_PROJECT_NAME}/helper/crypto) ================================================ FILE: LICENSE ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: README.md ================================================

GitHub Workflow Status PyPI - Version PyPI - Python Version

--- ## 安装 ```shell pip install aiotieba ``` ## 尝试一下 ```python import asyncio import aiotieba async def main(): async with aiotieba.Client() as client: threads = await client.get_threads("天堂鸡汤") for thread in threads[3:6]: print(f"tid={thread.tid}\ntext={thread.text}") asyncio.run(main()) ``` *输出样例* ```log tid=8537603600 text=一人发一句最喜欢的游戏台词 楼主先来 很喜欢lol布隆说的“夜晚越黑暗,星星就越明亮”,尤其在当下这个有着诸多缺点的世界里,这句话让我感觉舒服了很多。 在人们已不再相信理想主义的至暗时刻,高擎炬火之人便显得更加重要,至少我会坚持我的理想 tid=8093410706 text=大概是剪切板里的一些有意思的话 今天看自己的剪切板快满了,稍微翻翻突然发现以前存的一些话还挺有意思,就放在这里啦 (咦,疑似水帖啊我) tid=8537699088 text=记录一下自己人生第一次当“老师”的经历^_^ 明天我带的孩子们就“毕业”了,第一次当老师我改变了很多也收获了很多,就想着给自己记录一下这段宝贵的经历:-) ``` 继续阅读[**入门教程**](https://aiotieba.cc/tutorial/start) ## 项目特色 + 收录[**数十个常用API**](https://github.com/lumina37/aiotieba/tree/develop/aiotieba/api) + 类型注解全覆盖,方法注释全覆盖,内部命名统一 + 支持protobuf序列化请求参数 + 支持websocket接口 + 与官方版本高度一致的密码学实现 ## 友情链接 + [带UI的吧务管理器 (dog194/TiebaManager)](https://github.com/dog194/TiebaManager) + [VSCode贴吧摸鱼插件 (akacaijizhou/tieba-fish)](https://github.com/akacaijizhou/tieba-fish) + [eztb贴吧工具箱 (Dilettante258/eazy-tieba)](https://www.eztb.org) + [功能全面的贴吧管理QQ bot (TiebaMeow/TiebaManageBot)](https://github.com/TiebaMeow/TiebaManageBot) + [易于部署和使用的 Web 贴吧管理和自动化平台 (TiebaMeow/WebTiebaManager)](https://github.com/TiebaMeow/WebTiebaManager) + [灵活且高可靠的贴吧爬虫 (TiebaMeow/TiebaScraper)](https://github.com/TiebaMeow/TiebaScraper) + [TiebaLite 第三方安卓客户端 (zzc10086/TiebaLite)](https://github.com/zzc10086/TiebaLite) + [C#版本的贴吧接口库 (BaWuZhuShou/AioTieba4DotNet)](https://github.com/BaWuZhuShou/AioTieba4DotNet) + [基于aiotieba的tieba bot (adk23333/BungleCat)](https://github.com/adk23333/BungleCat) + [基于aiotieba的贴吧管理器 (adk23333/tieba-admin)](https://github.com/adk23333/tieba-admin) + [贴吧protobuf定义文件合集 更新至12.51 (n0099/tbclient.protobuf)](https://github.com/n0099/tbclient.protobuf) ## 特别鸣谢

为本开源项目提供的免费产品授权 ================================================ FILE: docs/CNAME ================================================ aiotieba.cc ================================================ FILE: docs/css/custom.css ================================================ [data-md-color-scheme=slate] { --md-hue: 210; --md-default-bg-color: hsla(var(--md-hue), 15%, 10%, 1); --md-typeset-a-color: hsla(var(--md-hue), 75%, 50%, 1); } ================================================ FILE: docs/index.md ================================================ #

GitHub Workflow Status PyPI - Version PyPI - Python Version

--- ## 安装 ```shell pip install aiotieba ``` ## 尝试一下 ```python import asyncio import aiotieba async def main(): async with aiotieba.Client() as client: threads = await client.get_threads("天堂鸡汤") for thread in threads[3:6]: print(f"tid={thread.tid} text={thread.text}") asyncio.run(main()) ``` *输出样例* ```log tid=8537603600 text=一人发一句最喜欢的游戏台词 楼主先来 很喜欢lol布隆说的“夜晚越黑暗,星星就越明亮”,尤其在当下这个有着诸多缺点的世界里,这句话让我感觉舒服了很多在人们已不再相信理想主义的至暗时刻,高擎炬火之人便显得更加重要,至少我会坚持我的理想 --- tid=8093410706 text=大概是剪切板里的一些有意思的话 今天看自己的剪切板快满了,稍微翻翻突然发现以前存的一些话还挺有意思,就放在这里啦 (咦,疑似水帖啊我) --- tid=8537699088 text=记录一下自己人生第一次当“老师”的经历^_^ 明天我带的孩子们就“毕业”了,第一次当老师我改变了很多也收获了很多,就想着给自己记录一下这段宝贵的经历:-) ``` 继续阅读[**入门教程**](https://aiotieba.cc/tutorial/start) ## 项目特色 + 收录[**数十个常用API**](https://github.com/lumina37/aiotieba/tree/develop/aiotieba/api) + 类型注解全覆盖,方法注释全覆盖,类属性注释全覆盖,内部命名统一 + 请求参数支持protobuf序列化 + 支持websocket接口 + 高一致性的密码学实现 ================================================ FILE: docs/ref/classdef/ats.md ================================================ ::: aiotieba.api.get_ats._classdef ================================================ FILE: docs/ref/classdef/bawu_blacklist.md ================================================ ::: aiotieba.api.get_bawu_blacklist._classdef ================================================ FILE: docs/ref/classdef/bawu_info.md ================================================ ::: aiotieba.api.get_bawu_info._classdef ================================================ FILE: docs/ref/classdef/bawu_perm.md ================================================ ::: aiotieba.api.get_bawu_perm._classdef ================================================ FILE: docs/ref/classdef/bawu_postlogs.md ================================================ ::: aiotieba.api.get_bawu_postlogs._classdef ================================================ FILE: docs/ref/classdef/bawu_userlogs.md ================================================ ::: aiotieba.api.get_bawu_userlogs._classdef ================================================ FILE: docs/ref/classdef/blacklist.md ================================================ ::: aiotieba.api.get_blacklist._classdef ================================================ FILE: docs/ref/classdef/blacklist_old.md ================================================ ::: aiotieba.api.get_blacklist_old._classdef ================================================ FILE: docs/ref/classdef/blocks.md ================================================ ::: aiotieba.api.get_blocks._classdef ================================================ FILE: docs/ref/classdef/comments.md ================================================ ::: aiotieba.api.get_comments._classdef ================================================ FILE: docs/ref/classdef/dislike_forums.md ================================================ ::: aiotieba.api.get_dislike_forums._classdef ================================================ FILE: docs/ref/classdef/fans.md ================================================ ::: aiotieba.api.get_fans._classdef ================================================ FILE: docs/ref/classdef/follow_forums.md ================================================ ::: aiotieba.api.get_follow_forums._classdef ================================================ FILE: docs/ref/classdef/follows.md ================================================ ::: aiotieba.api.get_follows._classdef ================================================ FILE: docs/ref/classdef/forum_detail.md ================================================ ::: aiotieba.api.get_forum_detail._classdef ================================================ FILE: docs/ref/classdef/group_msg.md ================================================ ::: aiotieba.api.get_group_msg._classdef ================================================ FILE: docs/ref/classdef/images.md ================================================ ::: aiotieba.api.get_images._classdef ================================================ FILE: docs/ref/classdef/last_replyers.md ================================================ ::: aiotieba.api.get_last_replyers._classdef ================================================ FILE: docs/ref/classdef/member_users.md ================================================ ::: aiotieba.api.get_member_users._classdef ================================================ FILE: docs/ref/classdef/posts.md ================================================ ::: aiotieba.api.get_posts._classdef ================================================ FILE: docs/ref/classdef/profile.md ================================================ ::: aiotieba.api.profile._classdef ================================================ FILE: docs/ref/classdef/rank_users.md ================================================ ::: aiotieba.api.get_rank_users._classdef ================================================ FILE: docs/ref/classdef/recom_status.md ================================================ ::: aiotieba.api.get_recom_status._classdef ================================================ FILE: docs/ref/classdef/recover_thread.md ================================================ ::: aiotieba.api.get_recover_info._classdef ================================================ FILE: docs/ref/classdef/recovers.md ================================================ ::: aiotieba.api.get_recovers._classdef ================================================ FILE: docs/ref/classdef/replys.md ================================================ ::: aiotieba.api.get_replys._classdef ================================================ FILE: docs/ref/classdef/searches.md ================================================ ::: aiotieba.api.search_exact._classdef ================================================ FILE: docs/ref/classdef/self_follow_forums.md ================================================ ::: aiotieba.api.get_self_follow_forums._classdef ================================================ FILE: docs/ref/classdef/square_forums.md ================================================ ::: aiotieba.api.get_square_forums._classdef ================================================ FILE: docs/ref/classdef/statistics.md ================================================ ::: aiotieba.api.get_statistics._classdef ================================================ FILE: docs/ref/classdef/threads.md ================================================ ::: aiotieba.api.get_threads._classdef ================================================ FILE: docs/ref/classdef/unblock_appeals.md ================================================ ::: aiotieba.api.get_unblock_appeals._classdef ================================================ FILE: docs/ref/classdef/user_contents.md ================================================ ::: aiotieba.api.get_user_contents._classdef ================================================ FILE: docs/ref/classdef/user_info.md ================================================ ::: aiotieba.api.tieba_uid2user_info._classdef ::: aiotieba.api.get_uinfo_getuserinfo_app._classdef ::: aiotieba.api.get_uinfo_getUserInfo_web._classdef ::: aiotieba.api.get_uinfo_user_json._classdef ::: aiotieba.api.get_uinfo_panel._classdef ================================================ FILE: docs/ref/client.md ================================================ # 客户端 ## 如何使用 `aiotieba.Client`是aiotieba的核心入口点 (Entry Point),其中封装了大量操作百度贴吧核心API的简便方法,你可以把它理解成一个“客户端” 我们推荐通过异步上下文管理器来使用`Client`,例如: ```python async with aiotieba.Client() as client: ... ``` ## Client ::: aiotieba.Client ================================================ FILE: docs/ref/config.md ================================================ # 超时配置 ::: aiotieba.config ================================================ FILE: docs/ref/enums.md ================================================ # 枚举 ::: aiotieba.enums ================================================ FILE: docs/ref/exception.md ================================================ # 异常处理 ::: aiotieba.exception ================================================ FILE: docs/tutorial/async_start.md ================================================ # 异步编程入门教程 ## 样例代码 本样例将获取并打印吧主题帖列表 ```python import asyncio import aiotieba as tb # [2] 异步函数——`async`关键字 async def main(): # [6] `async with`是什么? async with tb.Client() as client: # [1] CPU在何时离开?——`await`关键字 threads = await client.get_threads("天堂鸡汤") print(threads) # [4] CPU在何时返回?——事件循环 # 官方文档:运行asyncio程序 # https://docs.python.org/zh-cn/3/library/asyncio-task.html#running-an-asyncio-program asyncio.run(main()) ``` ## 样例解析 ### 什么是异步 在计算机中,CPU的速度普遍要比网络IO的速度快几个数量级。为了不让网络IO成为系统性能的瓶颈,我们会希望CPU等高速设备不要原地等待网卡慢悠悠地传输数据,而是可以把IO缓冲区交给网卡芯片就立刻离开,暂时转去执行一些其他任务,这其中就体现了**异步**(Asynchronous)的思想。 *Asynchronous*的前缀*a-*意为*not*,*syn*意为*together*,*chrono*源自古希腊语*khronos*,意为*time*,*-ous*为形容词后缀,合起来就是*not-together-time*,不同时发生的。在计算机领域,*Asynchronous*常常用于形容“多个事件不在同一时间发生”。这里的“同一时间”所强调的并不是时间长短,而是**逻辑上的连贯性**。 应用了异步技术后,在网络请求发出后到网络响应到来前的这段时间,CPU可以暂时离开,切换到其他地方去处理另外的工作。这种逻辑切换使得网络IO的请求事件与响应事件,相对于CPU及其他相关的高速设备而言,不在同一个**时间**发生。*Asynchronous*的词源与其术语含义高度匹配,值得反复品读。 ### CPU在何时离开?——`await`关键字 那么Python是如何实现异步的?在抛出一大堆错综复杂的概念来回答这个问题之前,你可以先带着一个更小的子问题来阅读下面的文章——CPU在何时离开?让我们先来看样例。 在样例`threads = await client.get_threads()`中,`client.get_threads`是一个**异步函数**。它和同步函数类似,都是可调对象(Callable)。`client.get_threads()`调用(call)了这个异步函数,调用异步函数不会像调用同步函数那样返回结果,而是会返回一个“施工方案”,也就是**可等待对象**(`Awaitable`)。此时,在可等待对象`client.get_threads()`的左边,那个至关重要的关键字`await`出现了。 *await*意为“等待”,往往用于表达“以被动的姿态等待某事的发生”,且暗含期待之意。在`threads = await client.get_threads()`中,`await`执行`client.get_threads()`返回的施工方案,并要求上一级执行过程`await main()`必须等待`client.get_threads()`给出执行结果`threads`后,再继续施工。 可能有初学者会迷惑于一个点,`client.get_threads()`会在何时执行?在创建时,还是在受到`await`调度时?下面这个简单的例子可以解答你的疑惑。 ```python import asyncio async def foo(): print("1 - foo_coro is executing") async def main(): foo_coro = foo() print("0 - foo_coro has not been executed!!!") await foo_coro asyncio.run(main()) ``` 输出结果 ```log 0 - foo_coro has not been executed!!! 1 - foo_coro is executing ``` 这说明`foo_coro`并不会在创建时立即执行,施工流程的创建(`foo_coro = foo()`)和执行(`await foo_coro`)是可以分开的。 `main()`的等待行为对应于一个计算机术语**“挂起”**(suspend),后面我们都会使用这个术语来替代“暂停”等口语化的词汇。*suspend*与*pause*意思相近,但他们之间有着微妙的区别——*suspend*往往表示较长时间的暂停,譬如在快节奏游戏中的暂停我们通常会说*pause*而不是*suspend*。 思考一个问题,在`await`关键字的指挥下做出等待行为的是谁?正确答案是`main()`的执行过程,而不是`main()`或者`main`,更不应该是`client.get_threads()`或者CPU。这一套概念辨析可不是什么无用的八股,它特别有助于加深我们对Python异步的理解。施工方案`main()`只是一个可等待对象(`Awaitable`),同样的,`main`也只是一个可调对象(`Callable`)。对象可没有“等待”、“暂停”的说法——“暂停一个对象”?你应该会觉得这句话十分诡异。只有施工方案的执行过程,也就是`main()`的执行过程可以有“暂停施工”、“等待某个任务完成再继续施工”的说法。 在这一小节的最后,我相信各位读者已经能够从具体到抽象,自行总结出`await`的含义——Python中`await`关键字的作用就是执行右侧的可等待对象,并让其当前所处的执行流程挂起以等待右侧的可等待对象给出结果。在本节开头提出的问题也可以解答了,在利用`await`等待一个可等待对象时,如果不能立即获得结果,CPU就会离开。 ### 异步函数——`async`关键字 在Python中,`async`关键字被用于将函数标记为异步的。不论函数体内是否需要等待(`await`),添加了`async`标记的函数都会返回一个可等待对象。 ### 什么是协程 **协程**(`Coroutine`)就是可以在中途挂起和恢复执行的函数流程。调用异步函数`main`所得到的可等待对象`main()`就是一个协程。 ### CPU在何时返回?——事件循环 回调函数(callback function)是这样一种函数:客户需要到柜台取货,但他又不想一直在柜台干等,就把自己的电话号码(回调函数)交给柜台,让柜台在有货之后打电话(执行回调函数)通知他来取。 **事件循环**(`EventLoop`),其中*event*意为事件,*loop*意为循环,就是用来获取事件通知,调度协程执行的循环体。事件循环在每次循环中都会做以下工作:将新增的定时任务添加到优先级队列;将已到执行时间的定时任务的回调函数添加到待执行回调函数列表;从操作系统获取事件通知(比如网卡通过硬件中断通知系统:某个socket有数据到来)并将读/写缓冲区的回调函数添加到待执行列表;最后,执行所有待执行的回调函数。CPU会在“执行回调函数”这一步骤返回先前被挂起的正在等待IO事件的协程,恢复他们的执行。 `asyncio.run`将会使用一个全局事件循环(不存在则新建)来执行作为参数的协程。`asyncio.run(main())`也就是在全局事件循环中执行协程`main()`。从`main()`到`client.get_threads()`一路往下执行,最终抵达一个底层协程,它将一个用于网络通信的socket注册到操作系统内核,然后立即返回一个`Future`对象,表示一个将要到来的结果,由于我们`await`了这个`Future`,调用链上所有的协程都被挂起,直到事件循环从操作系统获取到一个匹配的可读事件后,事件循环再执行对应读缓冲区的回调函数将协程唤醒并继续执行。 ### 为什么`await`只能在异步函数中使用? 协程有多种实现方式,而Python实现的是一种沿用了生成器机制的无栈协程。沿用生成器机制,意味着协程的上下文和生成器一样,被保存在一个对象中;无栈,意味着Python协程的调用链并不是一个栈结构,而是一个酷似链表的结构,最开始被调用的协程为链表头,通过其保存的上下文一路指向最底层的协程。而与无栈协程相对的有栈协程则与线程十分类似,不论同步函数的调用还是异步函数的调用都共享一套调用栈,因此有栈协程可以像线程一样在任何位置挂起,而无栈协程只能在特定位置(如`await`关键字标记的地方)挂起。无栈协程的一大“丑陋”之处就是`await`只能在异步函数中使用,JavaScript/Rust/C++的无栈协程都是如此,这导致如果你要使用异步特性,就必须将`async def`铺满整个调用链。 现在我们来回答标题提出的问题,如果在同步函数的调用过程中使用了`await`来等待异步结果,由于调用同步函数的返回值不是协程,不会在其中保存上下文信息,这导致我们无法在同步函数中找到那个应该恢复执行的正确位置。但如果我就是要在同步函数中记录应当恢复执行的正确位置呢?很好,那么你将实现一个有栈协程,go语言正是如此。 ### `async with`是什么? 也就是一个异步版本的上下文生成器,在使用`__init__`初始化对象之后使用对象的异步方法`__aenter__`进行异步初始化工作(比如创建连接池),使用异步方法`__aexit__`进行异步清理工作(比如关闭所有连接)。 ================================================ FILE: docs/tutorial/many_utils.md ================================================ # 实用工具 ## 签到 ```python from __future__ import annotations import asyncio import aiotieba as tb async def sign(BDUSS_key: str, *, retry_times: int = 0): """ 各种签到 Args: BDUSS (str): 用于创建客户端 retry_times (int, optional): 重试次数. Defaults to 0. """ async with tb.Client(BDUSS_key) as client: # 成长等级签到 for _ in range(retry_times): await asyncio.sleep(1.0) if await client.sign_growth(): break # 签到 await client.sign_forums() # 先一键签到 retry_list: list[str] = [] for pn in range(1, 9999): forums = await client.get_self_follow_forums(pn) retry_list += [forum.fname for forum in forums if not forum.is_signed] if not forums.has_more: break for _ in range(retry_times + 1): new_retry_list: list[str] = [] for fname in retry_list: ret = await client.sign_forum(fname) if ret.err is not None and ret.err.code not in [160002, 340006]: new_retry_list.append(fname) await asyncio.sleep(1.0) if not new_retry_list: break retry_list = new_retry_list async def main(): await sign("在此处输入待签到账号的BDUSS", retry_times=3) await sign("在此处输入另一个待签到账号的BDUSS", retry_times=3) asyncio.run(main()) ``` ## 批量封禁 ```python import asyncio import aiotieba as tb async def main(): async with tb.Client("在此处输入你的BDUSS") as client: for uid in [ 1111, 2222, 3333, ]: await client.block("xxx", uid, day=10) asyncio.run(main()) ``` ## 将个人主页的帖子全部设为隐藏 ```python import asyncio import aiotieba as tb async def main(): async with tb.Client("在此处输入你的BDUSS") as client: # 海象运算符(:=)会在创建threads变量并赋值的同时返回该值,方便while语句检查其是否为空 # 更多信息请搜索“Python海象运算符” while threads := await client.get_user_threads(): await asyncio.gather(*[client.set_thread_private(thread.fid, thread.tid, thread.pid) for thread in threads]) asyncio.run(main()) ``` ## 屏蔽贴吧,使它们不再出现在你的首页推荐里 ```python import asyncio import aiotieba as tb async def main(): async with tb.Client("在此处输入你的BDUSS") as client: await asyncio.gather(*[ client.dislike_forum(fname) for fname in [ "贴吧名A", "贴吧名B", "贴吧名C", ] # 把你要屏蔽的贴吧名填在这个列表里 ]) asyncio.run(main()) ``` ## 解除多个贴吧的屏蔽状态 ```python import asyncio import aiotieba as tb async def main(): async with tb.Client("在此处输入你的BDUSS") as client: # 此列表用于设置例外 # 将你希望依然保持屏蔽的贴吧名填在这个列表里 preserve_fnames = [ "保持屏蔽的贴吧名A", "保持屏蔽的贴吧名B", "保持屏蔽的贴吧名C", ] while 1: forums = await client.get_dislike_forums() await asyncio.gather(*[ client.undislike_forum(forum.fid) for forum in forums if forum.fname not in preserve_fnames ]) if not forums.has_more: break asyncio.run(main()) ``` ## 清除旧版乱码昵称 ```python import asyncio import aiotieba as tb async def main(): async with tb.Client("在此处输入你的BDUSS") as client: user = await client.get_self_info(tb.ReqUInfo.USER_NAME) await client.set_nickname_old(user.user_name) asyncio.run(main()) ``` ## 清空粉丝列表(无法复原的危险操作,请谨慎使用!) ```python import asyncio import aiotieba as tb async def main(): async with tb.Client("在此处输入你的BDUSS") as client: while fans := await client.get_fans(): await asyncio.gather(*[client.remove_fan(fan.user_id) for fan in fans]) asyncio.run(main()) ``` ## 清除所有历史回复(无法复原的危险操作,请谨慎使用!) ```python import asyncio import aiotieba as tb async def main(): async with tb.Client("在此处输入你的BDUSS") as client: while posts_list := await client.get_user_posts(): await asyncio.gather(*[ client.del_post(post.fid, post.tid, post.pid) for posts in posts_list for post in posts ]) asyncio.run(main()) ``` ================================================ FILE: docs/tutorial/start.md ================================================ # 入门教程 阅读本教程,你至少需要对Python的 **上下文管理器** `with...as...` 和 **迭代器** `for...in...` 有基本的印象,因为本文不会对这些重要的基本概念做过于深入的解释 推荐在本地新建一个`.py`文件来运行样例 ## 命名约定 贴吧服务端使用以下名称表示特定的数据 ### BDUSS 贴吧服务端使用BDUSS来确认用户身份 BDUSS是一串由纯ascii字符组成的,长度为192的字符串 !!! warning 使用BDUSS可以完成**一切**不需要手机/邮箱验证码的操作,包括**发帖**/**发私信**/**获取账号上的所有历史发言** BDUSS的过期时间长达数年,一般只能通过退出登录或修改密码使其失效 因此将BDUSS泄露给不受信任的人可能导致长期的账号安全风险和隐私泄露风险 在浏览器的Cookie和各种表单参数中你都能看到它的身影 搜索 你的浏览器型号+如何查看网站的Cookie 就能知道如何获取你的贴吧账号的BDUSS了 以Chrome为例,在任何一个贴吧网页下按F12调出开发者选项,然后你就能在下图的位置找到它 ![Chrome Cookie](https://user-images.githubusercontent.com/48282276/179938990-77139ea2-2d94-4d38-8d7d-9c6a3d99b69e.png) ### user_name 用户名 user_name唯一,但可变,且可以是空值 请注意与nick_name相区分 ### portrait 头像ID 每个贴吧用户都有且仅有一个portrait portrait是一串由纯ascii字符组成的,以tb.1.作为开头的,长度为33~36的字符串(仅有一些远古时期的ip账号不符合这个规则) 譬如我的portrait就是tb.1.8277e641.gUE2cTq4A4z5fi2EHn5k3Q 你可以通过portrait获取用户头像,例如[我的头像](http://tb.himg.baidu.com/sys/portraith/item/tb.1.8277e641.gUE2cTq4A4z5fi2EHn5k3Q) ### user_id 用户ID user_id唯一,不可变,不能为空 请注意将其与用户个人主页的tieba_uid相区分 user_id是一个uint64值(仅有一些远古时期的ip账号不符合这个规则) user_name portrait user_id 都是满足唯一性的用户标识符,并可以通过其中任意一个的值反查其余两个 ### tieba_uid 用户个人主页ID tieba_uid唯一,不可变,但可以为空 请注意将其与用户的user_id相区分 tieba_uid是一个uint64值 可以通过tieba_uid的值反查user_name portrait user_id ### forum_id 吧ID,简称fid 每个贴吧都有且仅有一个fid ### thread_id 主题帖ID,简称tid 每个主题帖都有且仅有一个tid ### post_id 回复ID,简称pid 每个楼层、楼中楼都有且仅有一个pid ## 关于异步编程 如果你不了解Python异步编程,请先阅读[异步编程入门教程](async_start.md) ## 迈出第一步 一个非常简单的入门案例 ### 样例代码 本样例将获取并打印当前账号的用户信息 ```python import asyncio import aiotieba as tb BDUSS = "在这里输入你账号的BDUSS" async def main(): async with tb.Client(BDUSS) as client: user = await client.get_self_info() print(user) asyncio.run(main()) ``` ### 期望结果 如果你的[`BDUSS`](#bduss)填写无误,你会获得类似下面这样的结果 ```log AAAA(你的用户名) ``` ## 内容的层次结构 本样例将协助你理解“主题帖-回复-楼中楼”的三级层次结构,以及如何解析富媒体内容 ### 样例代码 本样例将逐级获取并打印“主题帖-回复-楼中楼”中各层级的部分内容 ```python import asyncio import aiotieba as tb async def main(): async with tb.Client() as client: threads = await client.get_threads("天堂鸡汤") for thread in threads[3:6]: print(thread) # 打印整个主题帖 print(thread.contents) # 打印主题帖中的内容碎片(含富媒体信息) print(thread.contents.emojis) # 仅打印表情相关的内容碎片 selected_thread = threads[4] posts = await client.get_posts(selected_thread.tid) for post in posts[3:6]: print(post) # 打印整个回复 print(post.contents) # 打印回复中的内容碎片 print(post.contents.imgs) # 仅打印图片相关的内容碎片 for post in posts: if post.reply_num == 0: continue comments = await client.get_comments(post.tid, post.pid) for comment in comments: print(comment) # 打印整个楼中楼 print(comment.contents.ats) # 仅打印@相关的内容碎片 break asyncio.run(main()) ``` ## 运行时更改BDUSS 该案例演示了如何在运行时更改BDUSS 建议为每个账号新建`Client`,以避免误用遗留的websocket连接 同样地,你也可以直接向`Client.account`赋值以动态变更用户参数 ### 样例代码 本样例将获取并打印当前账号的用户信息 ```python import asyncio import aiotieba as tb async def main(): async with tb.Client() as client: client.account.BDUSS = "在这里输入你账号的BDUSS" user = await client.get_self_info() print(user) asyncio.run(main()) ``` ### 期望结果 如果你的[`BDUSS`](#bduss)填写无误,你会获得类似下面这样的结果 ```log AAAA(你的用户名) ``` ## 多账号 如何同时使用多个账号? ### 样例代码 本样例将获取并打印多个账号的用户信息 ```python import asyncio import aiotieba as tb BDUSS1 = "在这里输入第一个账号的BDUSS" BDUSS2 = "在这里输入第二个账号的BDUSS" async def main(): async with tb.Client(BDUSS1) as client1, tb.Client(BDUSS2) as client2: user1 = await client1.get_self_info() user2 = await client2.get_self_info() print(f"账号1: {user1}, 账号2: {user2}") asyncio.run(main()) ``` ### 期望结果 如果你的[`BDUSS`](#bduss)填写无误,你会获得类似下面这样的结果 ```log 账号1: AAAA, 账号2: BBBB ``` ## Account的序列化与反序列化 该功能可以用于导出账号参数 ### 样例代码 本样例将演示账号参数的导出与导入,并使用导入生成的Account获取用户信息 ```python import asyncio import aiotieba as tb BDUSS = "在这里输入你账号的BDUSS" async def main(): account1 = tb.Account(BDUSS) dic = account1.to_dict() print(dic) account2 = tb.Account.from_dict(dic) assert account1.BDUSS == account2.BDUSS async with tb.Client(account=account2) as client: user = await client.get_self_info() print(user) asyncio.run(main()) ``` ### 期望结果 如果你的[`BDUSS`](#bduss)填写无误,你会获得类似下面这样的结果 ```log {'BDUSS': '...'} AAAA(你的用户名) ``` ## 简单并发爬虫 ### 样例代码 本样例将同时请求用户个人信息和天堂鸡汤吧首页前30帖,并将他们打印出来 ```python import asyncio import aiotieba as tb BDUSS = "在这里输入你账号的BDUSS" async def main(): async with tb.Client(BDUSS) as client: # [1] 什么是`asyncio.gather`? # 参考官方文档:并发运行任务 # https://docs.python.org/zh-cn/3/library/asyncio-task.html#running-tasks-concurrently user, threads = await asyncio.gather(client.get_self_info(), client.get_threads("天堂鸡汤")) # 将获取的信息打印到日志 print(f"当前用户: {user}") for thread in threads: # Threads支持迭代,因此可以使用for循环逐条打印主题帖信息 # 当然了,Threads也支持使用下标的随机访问 print(f"tid: {thread.tid} 最后回复时间戳: {thread.last_time} 标题: {thread.title}") # 使用asyncio.run执行协程main asyncio.run(main()) ``` ### 样例解析 #### 什么是`asyncio.gather`? 你可以将若干协程作为参数传入`asyncio.gather`,样例中传入了两个协程。如果你忘记了协程和异步函数之间的区别,请及时复习。 `asyncio.gather`会为每个传入的协程创建对应的任务来同时执行它们(并发),同时`asyncio.gather(...)`自身也是一个协程,在前面添加`await`以要求主协程`main()`等待其执行完毕。执行完毕后,返回数据的顺序与传入协程的顺序一致,即`user`对应`client.get_self_info()`,`threads`对应`client.get_threads(...)` ### 期望结果 运行效果如下所示 ```log 当前用户: Starry_OvO tid: 7595618217 最后回复时间戳: 1672461980 标题: 关于负能量帖子的最新规定 tid: 8204562074 最后回复时间戳: 1672502281 标题: 外卖超时退单,心理煎熬 tid: 8165883863 最后回复时间戳: 1672502270 标题: 【记录】我这半醉半醒的人生啊 tid: 8204618726 最后回复时间戳: 1672502254 标题: 记录一下编导生的日常 tid: 8202743003 最后回复时间戳: 1672502252 标题: 2023会更好吗?或者,又是一年的碌碌无为 tid: 8204456677 最后回复时间戳: 1672502301 标题: 2023新年倒计时开始,有人的话请回复 tid: 8203409990 最后回复时间戳: 1672502197 标题: 年尾了,谢谢你们 tid: 8203959170 最后回复时间戳: 1672502156 标题: 求祝福 tid: 8188549079 最后回复时间戳: 1672502122 标题: pollen's club tid: 8204240728 最后回复时间戳: 1672502091 标题: 这是孩子最贵重的东西 tid: 8200916354 最后回复时间戳: 1672502023 标题: 这个是真的吗 tid: 8204206290 最后回复时间戳: 1672501931 标题: 家里突然多了只狗,请大家取个名字 tid: 8204353842 最后回复时间戳: 1672501936 标题: 一个很好的外卖小哥 tid: 8204583367 最后回复时间戳: 1672501911 标题: 何等奇迹!坚韧灵魂! tid: 8204431580 最后回复时间戳: 1672501835 标题: 大家今年想怎么跨年呢? tid: 8204442527 最后回复时间戳: 1672501832 标题: 吧友们,快过年了能不能发一些温馨可爱的图 tid: 8202573308 最后回复时间戳: 1672501923 标题: tid: 8202504004 最后回复时间戳: 1672501740 标题: 吧友们,想听到那4个字 tid: 8203284120 最后回复时间戳: 1672501971 标题: 看到评论区 觉得很暖心 想给吧友分享分享 tid: 8203290932 最后回复时间戳: 1672502300 标题: tid: 8202592714 最后回复时间戳: 1672501686 标题: 不要走啊狗狗 tid: 8165292224 最后回复时间戳: 1672501498 标题: 你想要只肥啾吗? tid: 8202351346 最后回复时间戳: 1672501588 标题: 这就是缘分吗? tid: 8204609134 最后回复时间戳: 1672501304 标题: tid: 8204575619 最后回复时间戳: 1672501526 标题: 标题五个字 tid: 8199583210 最后回复时间戳: 1672501343 标题: 一些有趣的图图 tid: 8204401395 最后回复时间戳: 1672494092 标题: 兄弟们 初来乍到 tid: 8200191186 最后回复时间戳: 1672500928 标题: 我妈做了一件好事 tid: 8204273523 最后回复时间戳: 1672500829 标题: 你如初待我模样 ``` ## 任务队列实现多协程爬虫 ### 样例代码 本样例将通过任务队列实现一个多协程爬虫,快速爬取天堂鸡汤吧的前32页共960条主题帖,并打印其中浏览量最高的10条 ```python from __future__ import annotations import asyncio import time import aiotieba as tb from aiotieba.logging import get_logger as LOG BDUSS = "在这里输入你账号的BDUSS" async def crawler(fname: str): """ 获取贴吧名为fname的贴吧的前32页中浏览量最高的10个主题帖 Args: fname (str): 贴吧名 """ start_time = time.perf_counter() LOG().info("Spider start") # thread_list用来保存主题帖列表 thread_list: list[tb.typing.Thread] = [] # 使用键名"default"对应的BDUSS创建客户端 async with tb.Client(BDUSS) as client: # asyncio.Queue是一个任务队列 # maxsize=8意味着缓冲区长度为8 # 当缓冲区被填满时,调用Queue.put的协程会被阻塞 task_queue = asyncio.Queue(maxsize=8) # 当is_running被设为False后,消费者会在超时后退出 is_running = True async def producer(): """ 生产者协程 """ for pn in range(32, 0, -1): # 生产者使用Queue.put不断地将页码pn填入任务队列task_queue await task_queue.put(pn) # 这里需要nonlocal来允许对闭包外的变量的修改操作(类似于引用传递和值传递的区别) nonlocal is_running # 将is_running设置为False以允许各消费协程超时退出 is_running = False async def worker(i: int): """ 消费者协程 Args: i (int): 协程编号 """ while 1: try: # 消费者协程不断地使用Queue.get从task_queue中拉取由生产者协程提供的页码pn作为任务 # asyncio.wait_for会等待作为参数的协程执行完毕直到超时 # timeout=1即把超时时间设为1秒 # 如果超过1秒未获取到新的页码pn,asyncio.wait_for(...)将抛出asyncio.TimeoutError pn = await asyncio.wait_for(task_queue.get(), timeout=1) LOG().debug(f"Worker#{i} handling pn:{pn}") except asyncio.TimeoutError: # 捕获asyncio.TimeoutError以退出协程 if is_running is False: # 如果is_running为False,意味着不需要再轮询task_queue获取新任务 LOG().debug(f"Worker#{i} quit") # 消费者协程通过return退出 return else: # 执行被分派的任务,即爬取pn页的帖子列表 threads = await client.get_threads(fname, pn) # 这里的nonlocal同样是为了修改闭包外的变量thread_list nonlocal thread_list thread_list += threads # 创建8个消费者协程 workers = [worker(i) for i in range(8)] # 使用asyncio.gather并发执行 # 需要注意这里*workers中的*意为将列表展开成多个参数 # 因为asyncio.gather只接受协程作为参数,不接受协程列表 await asyncio.gather(*workers, producer()) LOG().info(f"Spider complete. Time cost: {time.perf_counter() - start_time:.4f} secs") # 按主题帖浏览量降序排序 thread_list.sort(key=lambda thread: thread.view_num, reverse=True) # 将浏览量最高的10个主题帖的信息打印到日志 for i, thread in enumerate(thread_list[0:10], 1): LOG().info(f"Rank#{i} view_num:{thread.view_num} title:{thread.title}") # 执行协程crawler asyncio.run(crawler("天堂鸡汤")) ``` ### 期望结果 运行效果如下图所示 ```log <2023-01-01 00:03:01.195> [INFO] [crawler] Spider start <2023-01-01 00:03:01.198> [DEBUG] [worker] Worker#0 handling pn:32 <2023-01-01 00:03:01.242> [DEBUG] [worker] Worker#1 handling pn:31 <2023-01-01 00:03:01.245> [DEBUG] [worker] Worker#2 handling pn:30 <2023-01-01 00:03:01.245> [DEBUG] [worker] Worker#3 handling pn:29 <2023-01-01 00:03:01.246> [DEBUG] [worker] Worker#4 handling pn:28 <2023-01-01 00:03:01.247> [DEBUG] [worker] Worker#5 handling pn:27 <2023-01-01 00:03:01.248> [DEBUG] [worker] Worker#6 handling pn:26 <2023-01-01 00:03:01.248> [DEBUG] [worker] Worker#7 handling pn:25 <2023-01-01 00:03:01.599> [DEBUG] [worker] Worker#7 handling pn:24 <2023-01-01 00:03:01.626> [DEBUG] [worker] Worker#4 handling pn:23 <2023-01-01 00:03:01.685> [DEBUG] [worker] Worker#2 handling pn:22 <2023-01-01 00:03:01.711> [DEBUG] [worker] Worker#5 handling pn:21 <2023-01-01 00:03:01.744> [DEBUG] [worker] Worker#3 handling pn:20 <2023-01-01 00:03:01.768> [DEBUG] [worker] Worker#0 handling pn:19 <2023-01-01 00:03:01.776> [DEBUG] [worker] Worker#1 handling pn:18 <2023-01-01 00:03:01.777> [DEBUG] [worker] Worker#6 handling pn:17 <2023-01-01 00:03:01.974> [DEBUG] [worker] Worker#5 handling pn:16 <2023-01-01 00:03:02.041> [DEBUG] [worker] Worker#7 handling pn:15 <2023-01-01 00:03:02.043> [DEBUG] [worker] Worker#4 handling pn:14 <2023-01-01 00:03:02.072> [DEBUG] [worker] Worker#6 handling pn:13 <2023-01-01 00:03:02.083> [DEBUG] [worker] Worker#2 handling pn:12 <2023-01-01 00:03:02.145> [DEBUG] [worker] Worker#3 handling pn:11 <2023-01-01 00:03:02.190> [DEBUG] [worker] Worker#0 handling pn:10 <2023-01-01 00:03:02.197> [DEBUG] [worker] Worker#1 handling pn:9 <2023-01-01 00:03:02.365> [DEBUG] [worker] Worker#7 handling pn:8 <2023-01-01 00:03:02.379> [DEBUG] [worker] Worker#2 handling pn:7 <2023-01-01 00:03:02.425> [DEBUG] [worker] Worker#5 handling pn:6 <2023-01-01 00:03:02.547> [DEBUG] [worker] Worker#6 handling pn:5 <2023-01-01 00:03:02.579> [DEBUG] [worker] Worker#4 handling pn:4 <2023-01-01 00:03:02.606> [DEBUG] [worker] Worker#3 handling pn:3 <2023-01-01 00:03:02.635> [DEBUG] [worker] Worker#0 handling pn:2 <2023-01-01 00:03:02.640> [DEBUG] [worker] Worker#1 handling pn:1 <2023-01-01 00:03:03.789> [DEBUG] [worker] Worker#5 quit <2023-01-01 00:03:03.820> [DEBUG] [worker] Worker#7 quit <2023-01-01 00:03:03.821> [DEBUG] [worker] Worker#2 quit <2023-01-01 00:03:03.821> [DEBUG] [worker] Worker#6 quit <2023-01-01 00:03:03.882> [DEBUG] [worker] Worker#4 quit <2023-01-01 00:03:03.975> [DEBUG] [worker] Worker#0 quit <2023-01-01 00:03:03.975> [DEBUG] [worker] Worker#1 quit <2023-01-01 00:03:03.976> [INFO] [crawler] Spider complete. Time cost: 2.7822 secs <2023-01-01 00:03:03.977> [INFO] [crawler] Rank#1 view_num:295571 title:各位发点暖心小故事吧我先来 <2023-01-01 00:03:03.978> [INFO] [crawler] Rank#2 view_num:285897 title:解决压力大 <2023-01-01 00:03:03.978> [INFO] [crawler] Rank#3 view_num:255771 title:人活着是为了什么 <2023-01-01 00:03:03.978> [INFO] [crawler] Rank#4 view_num:243325 title:面藕,我的面藕😭 <2023-01-01 00:03:03.979> [INFO] [crawler] Rank#5 view_num:222611 title:什么事情是你长大很久之后才明白的? <2023-01-01 00:03:03.979> [INFO] [crawler] Rank#6 view_num:216527 title:教你谈恋爱 <2023-01-01 00:03:03.979> [INFO] [crawler] Rank#7 view_num:214848 title:你已经是只狗了! <2023-01-01 00:03:03.980> [INFO] [crawler] Rank#8 view_num:208130 title:好温暖呀~ <2023-01-01 00:03:03.980> [INFO] [crawler] Rank#9 view_num:206946 title:好温柔的叔叔啊😭 <2023-01-01 00:03:03.980> [INFO] [crawler] Rank#10 view_num:203606 title:你会不会删掉已故亲人的联系方式? ``` ================================================ FILE: mkdocs.yml ================================================ site_name: aiotieba site_description: aiotieba开发文档 site_url: https://aiotieba.cc/ theme: name: material palette: - media: "(prefers-color-scheme: light)" scheme: default toggle: icon: material/brightness-7 name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate toggle: icon: material/brightness-4 name: Switch to light mode features: - navigation.sections - search.suggest - search.highlight language: zh repo_name: lumina37/aiotieba repo_url: https://github.com/lumina37/aiotieba/ nav: - 介绍: index.md - 教程: - 入门教程: tutorial/start.md - 异步编程入门教程: tutorial/async_start.md - 实用微脚本合集: tutorial/many_utils.md - 参考文档: - 客户端 (Client): ref/client.md - 枚举: ref/enums.md - 配置: ref/config.md - 异常处理: ref/exception.md - 类型定义: - forum_detail: ref/classdef/forum_detail.md - threads: ref/classdef/threads.md - posts: ref/classdef/posts.md - comments: ref/classdef/comments.md - last_replyers: ref/classdef/last_replyers.md - searches: ref/classdef/searches.md - user_info: ref/classdef/user_info.md - profile: ref/classdef/profile.md - follow_forums: ref/classdef/follow_forums.md - self_follow_forums: ref/classdef/self_follow_forums.md - user_contents: ref/classdef/user_contents.md - images: ref/classdef/images.md - replys: ref/classdef/replys.md - ats: ref/classdef/ats.md - follows: ref/classdef/follows.md - fans: ref/classdef/fans.md - blacklist: ref/classdef/blacklist.md - blacklist_old: ref/classdef/blacklist_old.md - dislike_forums: ref/classdef/dislike_forums.md - square_forums: ref/classdef/square_forums.md - bawu_info: ref/classdef/bawu_info.md - bawu_perm: ref/classdef/bawu_perm.md - rank_users: ref/classdef/rank_users.md - member_users: ref/classdef/member_users.md - blocks: ref/classdef/blocks.md - recovers: ref/classdef/recovers.md - recover_thread: ref/classdef/recover_thread.md - bawu_userlogs: ref/classdef/bawu_userlogs.md - bawu_postlogs: ref/classdef/bawu_postlogs.md - unblock_appeals: ref/classdef/unblock_appeals.md - bawu_blacklist: ref/classdef/bawu_blacklist.md - statistics: ref/classdef/statistics.md - recom_status: ref/classdef/recom_status.md - group_msg: ref/classdef/group_msg.md markdown_extensions: - md_in_html - admonition - codehilite: css_class: highlight plugins: - search - mkdocstrings: handlers: python: options: show_bases: false show_source: false members_order: source extra_css: - css/custom.css ================================================ FILE: pyproject.toml ================================================ [project] name = "aiotieba" version = "4.7.0" description = "Asynchronous I/O Client for Baidu Tieba" authors = [{ name = "lumina37", email = "starry.qvq@gmail.com" }] urls = { Repository = "https://github.com/lumina37/aiotieba/", Documentation = "https://aiotieba.cc/" } readme = "README.md" keywords = ["baidu", "tieba"] classifiers = [ "Development Status :: 4 - Beta", "Framework :: AsyncIO", "Intended Audience :: Developers", "License :: OSI Approved :: The Unlicense (Unlicense)", "Operating System :: OS Independent", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Internet :: WWW/HTTP :: Session", ] requires-python = ">=3.10,<3.15" dependencies = [ "aiohttp>=3.11.0,<4;python_version>='3.10' and python_version<'3.14'", "aiohttp>=3.13.0,<4;python_version>='3.14'", "beautifulsoup4>=4.7.1,<5", "lxml>=4.6.4,<7;python_version=='3.10'", "lxml>=4.9.2,<7;python_version=='3.11'", "lxml>=4.9.3,<7;python_version=='3.12'", "lxml>=5.3.0,<7;python_version=='3.13'", "lxml>=6.0.1,<7;python_version>='3.14'", "protobuf>=4.21.1,<8", "cryptography>=35.0.0,<47", "async-timeout>=4.0,<6;python_version=='3.10'", "StrEnum>=0.4.0,<0.5;python_version=='3.10'", ] [project.optional-dependencies] img = [ "opencv-contrib-python-headless>=4.6.0.66,<5;sys_platform=='linux'", "opencv-contrib-python>=4.6.0.66,<5;sys_platform!='linux'", ] speedup = [ "orjson>=3.4.7,<4;python_version=='3.10'", "orjson>=3.7.10,<4;python_version=='3.11'", "orjson>=3.9.10,<4;python_version=='3.12'", "orjson>=3.10.7,<4;python_version=='3.13'", "orjson>=3.11.1,<4;python_version>='3.14'", ] [dependency-groups] dev = ["pytest==9.0.3", "pytest-asyncio==1.3.0", "pytest-rerunfailures==16.1"] docs = ["mkdocs-material", "mkdocstrings[python]"] [build-system] requires = ["scikit-build-core>=0.12.2,<0.13"] build-backend = "scikit_build_core.build" [tool.uv] managed = true [tool.scikit-build] sdist.exclude = ["*.proto", ".*", "docs", "scripts", "tests", "mkdocs.yml"] wheel.exclude = ["*.c", "*.h", "*.txt"] [[tool.scikit-build.generate]] path = "aiotieba/__version__.py" template = '''__version__ = "${version}"''' [tool.cibuildwheel] build = "cp310* cp311* cp312* cp313* cp314* pp310* pp311*" skip = "*-win32 *_i686 *_s390x *_ppc64le" enable = ["pypy", "pypy-eol"] build-frontend = "uv" [tool.ruff] line-length = 120 target-version = "py310" preview = true [tool.ruff.lint] select = [ "F", "E", "W", "I", "UP", "YTT", "ASYNC", "B", "A", "C4", "FA", "ICN", "LOG", "G", "PIE", "T20", "PT", "Q", "RSE", "SLOT", "TC", "PTH", "NPY", "PERF", "FURB", ] ignore = ["A005", "E402", "E501", "E266"] [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] "typing.py" = ["F401"] "*_pb2.py" = ["F401"] [tool.pytest.ini_options] addopts = "-q" testpaths = ["tests"] required_plugins = "pytest-asyncio pytest-rerunfailures" asyncio_mode = "strict" asyncio_default_fixture_loop_scope = "function" ================================================ FILE: scripts/proto_compile.py ================================================ from __future__ import annotations import subprocess from pathlib import Path from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Iterator commom_proto_pth = Path("src/aiotieba/api/_protobuf") for fpth in commom_proto_pth.glob("*_pb2.py"): fpth.unlink() subprocess.run("protoc --python_out=. *.proto", cwd=str(commom_proto_pth), check=True, timeout=60.0) def row_filter(rows: list[str], import_perfix: str) -> Iterator[str]: is_runtime_checker = False for row in rows: if row.startswith("#"): continue if not is_runtime_checker and row.startswith("_runtime"): is_runtime_checker = True if is_runtime_checker and row.startswith(")"): is_runtime_checker = False continue if is_runtime_checker: continue if "import runtime_version" in row: continue if row.startswith("import"): row = import_perfix + row yield row for fpth in commom_proto_pth.glob("*_pb2.py"): bak_fpth = fpth.with_suffix(".bak") with ( fpth.open("r") as f, bak_fpth.open("w") as bak_f, ): bak_f.writelines(row_filter(f, "from . ")) fpth.unlink() bak_fpth.rename(fpth) for mod_pth in Path("src/aiotieba/api").glob("*/protobuf"): for fpth in mod_pth.glob("*_pb2.py"): fpth.unlink() subprocess.run("protoc -I../../_protobuf -I. --python_out=. *.proto", cwd=str(mod_pth), check=True, timeout=10.0) for fpth in mod_pth.glob("*_pb2.py"): bak_fpth = fpth.with_suffix(".bak") with ( fpth.open("r") as f, bak_fpth.open("w") as bak_f, ): bak_f.writelines(row_filter(f, "from ..._protobuf ")) fpth.unlink() bak_fpth.rename(fpth) subprocess.run("uvx ruff check src/**/*_pb2.py --fix --unsafe-fixes -s", cwd=".", check=False, timeout=10.0) subprocess.run("uvx ruff format src/**/*_pb2.py -s", cwd=".", check=False, timeout=30.0) ================================================ FILE: src/aiotieba/__init__.py ================================================ """ Asynchronous I/O Client/Reviewer for Baidu Tieba @Author: starry.qvq@gmail.com @License: Unlicense @Documentation: https://aiotieba.cc/ """ from . import const, core, enums, exception, logging, typing from .__version__ import __version__ from .client import Client from .config import ProxyConfig, TimeoutConfig from .core import Account from .enums import * # noqa: F403 from .logging import enable_filelog, get_logger ================================================ FILE: src/aiotieba/__version__.py ================================================ __version__ = "TBD" ================================================ FILE: src/aiotieba/api/__init__.py ================================================ ================================================ FILE: src/aiotieba/api/_classdef/__init__.py ================================================ from ...core import Account from .common import TypeMessage from .container import Containers from .contents import ( FragAt, FragEmoji, FragImage, FragItem, FragLink, FragText, FragTiebaPlus, FragUnknown, TypeFragAt, TypeFragEmoji, TypeFragImage, TypeFragItem, TypeFragLink, TypeFragment, TypeFragText, TypeFragTiebaPlus, ) from .user import UserInfo from .vote import VoteInfo ================================================ FILE: src/aiotieba/api/_classdef/common.py ================================================ from typing import TypeVar from google.protobuf.message import Message TypeMessage = TypeVar("TypeMessage", bound=Message) ================================================ FILE: src/aiotieba/api/_classdef/container.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING, Generic, SupportsIndex, TypeVar, overload if TYPE_CHECKING: from collections.abc import Iterator TypeContainer = TypeVar("TypeContainer") @dcs.dataclass class Containers(Generic[TypeContainer]): """ 内容列表的泛型基类 约定取内容的通用接口 Attributes: objs (list[TypeContainer]): 内容列表 """ objs: list[TypeContainer] = dcs.field(default_factory=list) def __iter__(self) -> Iterator[TypeContainer]: return self.objs.__iter__() @overload def __getitem__(self, idx: SupportsIndex) -> TypeContainer: ... @overload def __getitem__(self, idx: slice) -> list[TypeContainer]: ... def __getitem__(self, idx): return self.objs.__getitem__(idx) def __setitem__(self, idx, val): raise NotImplementedError def __delitem__(self, idx): raise NotImplementedError def __len__(self) -> int: return self.objs.__len__() def __bool__(self) -> bool: return bool(self.objs) ================================================ FILE: src/aiotieba/api/_classdef/contents.py ================================================ from __future__ import annotations import dataclasses as dcs import re from functools import cached_property from typing import TYPE_CHECKING, Any, Protocol, TypeVar import yarl if TYPE_CHECKING: from collections.abc import Mapping from .common import TypeMessage TypeFragment = TypeVar("TypeFragment") @dcs.dataclass class FragText: """ 纯文本碎片 Attributes: text (str): 文本内容 """ text: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragText: text = data_proto.text return FragText(text) @staticmethod def from_json(data_map: Mapping) -> FragText: text = data_map["text"] return FragText(text) class TypeFragText(Protocol): text: str @dcs.dataclass class FragEmoji: """ 表情碎片 Attributes: id (str): 表情图片id desc (str): 表情描述 """ id: str = "" desc: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragEmoji: id_ = data_proto.text desc = data_proto.c return FragEmoji(id_, desc) class TypeFragEmoji(Protocol): id: str desc: str _IMAGEHASH_EXP = re.compile(r"/([a-z0-9]{32,})\.") @dcs.dataclass class FragImage: """ 图像碎片 Attributes: src (str): 小图链接 宽720px big_src (str): 大图链接 宽960px origin_src (str): 原图链接 origin_size (int): 原图大小 show_width (int): 图像在客户端预览显示的宽度 show_height (int): 图像在客户端预览显示的高度 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) big_src: str = dcs.field(default="", repr=False) origin_src: str = dcs.field(default="", repr=False) origin_size: int = 0 show_width: int = 0 show_height: int = 0 hash: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragImage: src = data_proto.cdn_src big_src = data_proto.big_cdn_src origin_src = data_proto.origin_src origin_size = data_proto.origin_size show_width, _, show_height = data_proto.bsize.partition(",") show_width = int(show_width) show_height = int(show_height) if hash_obj := _IMAGEHASH_EXP.search(src): hash_ = hash_obj.group(1) else: hash_ = "" return FragImage(src, big_src, origin_src, origin_size, show_width, show_height, hash_) @dcs.dataclass class TypeFragImage(Protocol): src: str origin_src: str hash: str @dcs.dataclass class FragAt: """ @碎片 Attributes: text (str): 被@用户的昵称 含@ user_id (int): 被@用户的user_id """ text: str = "" user_id: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> FragAt: text = data_proto.text user_id = data_proto.uid return FragAt(text, user_id) class TypeFragAt(Protocol): text: str user_id: int @dcs.dataclass class FragVoice: """ 音频碎片 Attributes: md5 (str): 音频md5 duration (int): 音频长度 以秒为单位 """ md5: str = "" duration: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> FragVoice: md5 = data_proto.voice_md5 duration = data_proto.during_time / 1000 return FragVoice(md5, duration) def __bool__(self) -> bool: return bool(self.md5) class TypeFragVoice(Protocol): md5: str duration: int @dcs.dataclass class FragVideo: """ 视频碎片 Attributes: src (str): 视频链接 cover_src (str): 封面链接 duration (int): 视频长度 width (int): 视频宽度 height (int): 视频高度 view_num (int): 浏览次数 """ src: str = "" cover_src: str = "" duration: int = 0 width: int = 0 height: int = 0 view_num: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> FragVideo: src = data_proto.video_url cover_src = data_proto.thumbnail_url duration = data_proto.video_duration width = data_proto.video_width height = data_proto.video_height view_num = data_proto.play_count return FragVideo(src, cover_src, duration, width, height, view_num) def __bool__(self) -> bool: return bool(self.width) class TypeFragVideo(Protocol): src: str cover_src: str duration: int width: int height: int view_num: int @dcs.dataclass class FragLink: """ 链接碎片 Attributes: text (str): 原链接 title (str): 链接标题 raw_url (yarl.URL): 解析后的原链接 url (yarl.URL): 解析后的去前缀链接 is_external (bool): 是否外部链接 """ text: str = "" title: str = "" raw_url: yarl.URL = dcs.field(default_factory=yarl.URL) @staticmethod def from_proto(data_proto: TypeMessage) -> FragLink: text = data_proto.link title = data_proto.text raw_url = yarl.URL(text) return FragLink(text, title, raw_url) @staticmethod def from_json(data_map: Mapping) -> FragLink: text = data_map["link"] title = data_map["text"] raw_url = yarl.URL(text) return FragLink(text, title, raw_url) @cached_property def url(self) -> yarl.URL: if self.is_external: url = yarl.URL(self.raw_url.query["url"]) else: url = self.raw_url return url @cached_property def is_external(self) -> bool: return self.raw_url.path == "/mo/q/checkurl" class TypeFragLink(Protocol): text: str title: str raw_url: yarl.URL @property def url(self) -> yarl.URL: ... @property def is_external(self) -> bool: ... @dcs.dataclass class FragTiebaPlus: """ 贴吧plus广告碎片 Attributes: text (str): 贴吧plus广告描述 url (yarl.URL): 解析后的贴吧plus广告跳转链接 """ text: str = "" url: yarl.URL = dcs.field(default_factory=yarl.URL) @staticmethod def from_proto(data_proto: TypeMessage) -> FragTiebaPlus: text = data_proto.tiebaplus_info.desc url = yarl.URL(data_proto.tiebaplus_info.jump_url) return FragTiebaPlus(text, url) class TypeFragTiebaPlus(Protocol): text: str url: yarl.URL @dcs.dataclass class FragItem: """ item碎片 Attributes: text (str): item名称 """ text: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragItem: text = data_proto.item.item_name return FragItem(text) class TypeFragItem(Protocol): text: str @dcs.dataclass class FragUnknown: """ 未知碎片 Attributes: data (Any): 原始数据 """ proto: Any @staticmethod def from_proto(data: Any) -> FragUnknown: return FragUnknown(data) @staticmethod def from_json(data: Mapping) -> FragUnknown: return FragUnknown(data) ================================================ FILE: src/aiotieba/api/_classdef/user.py ================================================ from __future__ import annotations import dataclasses as dcs from ...enums import Gender, PrivLike, PrivReply @dcs.dataclass class UserInfo: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_old (str): 旧版昵称 nick_name_new (str): 新版昵称 tieba_uid (int): 用户个人主页uid glevel (int): 贴吧成长等级 gender (Gender): 性别 age (float): 吧龄 以年为单位 post_num (int): 发帖数 agree_num (int): 获赞数 fan_num (int): 粉丝数 follow_num (int): 关注数 forum_num (int): 关注贴吧数 sign (str): 个性签名 ip (str): ip归属地 icons (list[str]): 印记信息 is_vip (bool): 是否超级会员 is_god (bool): 是否大神 is_blocked (bool): 是否被永久封禁屏蔽 priv_like (PrivLike): 关注吧列表的公开状态 priv_reply (PrivReply): 帖子评论权限 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_old: str = "" nick_name_new: str = "" tieba_uid: int = 0 glevel: int = 0 gender: Gender = Gender.UNKNOWN age: float = 0.0 post_num: int = 0 agree_num: int = 0 fan_num: int = 0 follow_num: int = 0 forum_num: int = 0 sign: str = "" ip: str = "" icons: list[str] = dcs.field(default_factory=list) is_vip: bool = False is_god: bool = False is_blocked: bool = False uk: int = 0 bduk: str = "" trigger_id: int = 0 priv_like: PrivLike = PrivLike.PUBLIC priv_reply: PrivReply = PrivReply.ALL def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo) -> 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) def __ior__(self, obj) -> UserInfo: for field in dcs.fields(obj): if hasattr(self, field.name): val = getattr(obj, field.name) setattr(self, field.name, val) return self @property def nick_name(self) -> str: return self.nick_name_new or self.nick_name_old @property def show_name(self) -> str: return self.nick_name_new or self.nick_name_old or self.user_name @property def log_name(self) -> str: if self.user_name: return self.user_name elif self.portrait: return f"{self.nick_name}/{self.portrait}" else: return str(self.user_id) ================================================ FILE: src/aiotieba/api/_classdef/vote.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING if TYPE_CHECKING: from .common import TypeMessage @dcs.dataclass class VoteOption: """ 投票选项信息 Attributes: vote_num (int): 得票数 text (str): 选项描述文字 """ vote_num: int = 0 text: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> VoteOption: vote_num = data_proto.num text = data_proto.text return VoteOption(vote_num, text) @dcs.dataclass class VoteInfo: """ 投票信息 Attributes: title (str): 投票标题 is_multi (bool): 是否多选 options (list[VoteOption]): 选项列表 total_vote (int): 总投票数 total_user (int): 总投票人数 """ title: str = "" is_multi: bool = False options: list[VoteOption] = dcs.field(default_factory=list) total_vote: int = 0 total_user: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> VoteInfo: title = data_proto.title is_multi = bool(data_proto.is_multi) options = [VoteOption.from_proto(p) for p in data_proto.options] total_vote = data_proto.total_poll total_user = data_proto.total_num return VoteInfo(title, is_multi, options, total_vote, total_user) def __len__(self) -> int: return len(self.options) def __bool__(self) -> bool: return bool(self.options) ================================================ FILE: src/aiotieba/api/_protobuf/Agree.proto ================================================ // tbclient.Agree syntax = "proto3"; message Agree { int64 agree_num = 1; int64 disagree_num = 4; } ================================================ FILE: src/aiotieba/api/_protobuf/Agree_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x0b\x41gree.proto"0\n\x05\x41gree\x12\x11\n\tagree_num\x18\x01 \x01(\x03\x12\x14\n\x0c\x64isagree_num\x18\x04 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "Agree_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_AGREE"]._serialized_start = 15 _globals["_AGREE"]._serialized_end = 63 ================================================ FILE: src/aiotieba/api/_protobuf/CommonReq.proto ================================================ // tbclient.CommonReq syntax = "proto3"; message CommonReq { int32 _client_type = 1; string _client_version = 2; string _client_id = 3; string _phone_imei = 5; string _from = 6; string cuid = 7; int64 _timestamp = 8; string model = 9; string BDUSS = 10; string tbs = 11; int32 net_type = 12; string pversion = 24; string _os_version = 25; string brand = 26; string lego_lib_version = 28; string applist = 29; string stoken = 30; string z_id = 31; string cuid_galaxy2 = 32; string cuid_gid = 33; string c3_aid = 35; string sample_id = 36; int32 scr_w = 37; int32 scr_h = 38; double scr_dip = 39; int32 q_type = 40; int32 is_teenager = 41; string sdk_ver = 42; string framework_ver = 43; string naws_game_ver = 44; int64 active_timestamp = 49; int64 first_install_time = 50; int64 last_update_time = 51; string event_day = 53; string android_id = 54; int32 cmode = 55; string start_scheme = 56; int32 start_type = 57; string idfv = 60; string extra = 61; string user_agent = 62; int32 personalized_rec_switch = 63; string device_score = 70; } ================================================ FILE: src/aiotieba/api/_protobuf/CommonReq_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b"\n\x0f\x43ommonReq.proto\"\xc6\x06\n\tCommonReq\x12\x14\n\x0c_client_type\x18\x01 \x01(\x05\x12\x17\n\x0f_client_version\x18\x02 \x01(\t\x12\x12\n\n_client_id\x18\x03 \x01(\t\x12\x13\n\x0b_phone_imei\x18\x05 \x01(\t\x12\r\n\x05_from\x18\x06 \x01(\t\x12\x0c\n\x04\x63uid\x18\x07 \x01(\t\x12\x12\n\n_timestamp\x18\x08 \x01(\x03\x12\r\n\x05model\x18\t \x01(\t\x12\r\n\x05\x42\x44USS\x18\n \x01(\t\x12\x0b\n\x03tbs\x18\x0b \x01(\t\x12\x10\n\x08net_type\x18\x0c \x01(\x05\x12\x10\n\x08pversion\x18\x18 \x01(\t\x12\x13\n\x0b_os_version\x18\x19 \x01(\t\x12\r\n\x05\x62rand\x18\x1a \x01(\t\x12\x18\n\x10lego_lib_version\x18\x1c \x01(\t\x12\x0f\n\x07\x61pplist\x18\x1d \x01(\t\x12\x0e\n\x06stoken\x18\x1e \x01(\t\x12\x0c\n\x04z_id\x18\x1f \x01(\t\x12\x14\n\x0c\x63uid_galaxy2\x18 \x01(\t\x12\x10\n\x08\x63uid_gid\x18! \x01(\t\x12\x0e\n\x06\x63\x33_aid\x18# \x01(\t\x12\x11\n\tsample_id\x18$ \x01(\t\x12\r\n\x05scr_w\x18% \x01(\x05\x12\r\n\x05scr_h\x18& \x01(\x05\x12\x0f\n\x07scr_dip\x18' \x01(\x01\x12\x0e\n\x06q_type\x18( \x01(\x05\x12\x13\n\x0bis_teenager\x18) \x01(\x05\x12\x0f\n\x07sdk_ver\x18* \x01(\t\x12\x15\n\rframework_ver\x18+ \x01(\t\x12\x15\n\rnaws_game_ver\x18, \x01(\t\x12\x18\n\x10\x61\x63tive_timestamp\x18\x31 \x01(\x03\x12\x1a\n\x12\x66irst_install_time\x18\x32 \x01(\x03\x12\x18\n\x10last_update_time\x18\x33 \x01(\x03\x12\x11\n\tevent_day\x18\x35 \x01(\t\x12\x12\n\nandroid_id\x18\x36 \x01(\t\x12\r\n\x05\x63mode\x18\x37 \x01(\x05\x12\x14\n\x0cstart_scheme\x18\x38 \x01(\t\x12\x12\n\nstart_type\x18\x39 \x01(\x05\x12\x0c\n\x04idfv\x18< \x01(\t\x12\r\n\x05\x65xtra\x18= \x01(\t\x12\x12\n\nuser_agent\x18> \x01(\t\x12\x1f\n\x17personalized_rec_switch\x18? \x01(\x05\x12\x14\n\x0c\x64\x65vice_score\x18\x46 \x01(\tb\x06proto3" ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "CommonReq_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_COMMONREQ"]._serialized_start = 20 _globals["_COMMONREQ"]._serialized_end = 858 ================================================ FILE: src/aiotieba/api/_protobuf/Error.proto ================================================ // tbclient.Error syntax = "proto3"; message Error { int32 errorno = 1; string errmsg = 2; } ================================================ FILE: src/aiotieba/api/_protobuf/Error_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x0b\x45rror.proto"(\n\x05\x45rror\x12\x0f\n\x07\x65rrorno\x18\x01 \x01(\x05\x12\x0e\n\x06\x65rrmsg\x18\x02 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "Error_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_ERROR"]._serialized_start = 15 _globals["_ERROR"]._serialized_end = 55 ================================================ FILE: src/aiotieba/api/_protobuf/ForumList.proto ================================================ // Not actually exist syntax = "proto3"; message ForumList { int64 forum_id = 1; string forum_name = 2; int32 member_count = 4; int64 post_num = 7; int64 thread_num = 8; } ================================================ FILE: src/aiotieba/api/_protobuf/ForumList_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x0f\x46orumList.proto"m\n\tForumList\x12\x10\n\x08\x66orum_id\x18\x01 \x01(\x03\x12\x12\n\nforum_name\x18\x02 \x01(\t\x12\x14\n\x0cmember_count\x18\x04 \x01(\x05\x12\x10\n\x08post_num\x18\x07 \x01(\x03\x12\x12\n\nthread_num\x18\x08 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "ForumList_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_FORUMLIST"]._serialized_start = 19 _globals["_FORUMLIST"]._serialized_end = 128 ================================================ FILE: src/aiotieba/api/_protobuf/FrsTabInfo.proto ================================================ // tbclient.FrsTabInfo syntax = "proto3"; message FrsTabInfo { int32 tab_id = 1; string tab_name = 3; } ================================================ FILE: src/aiotieba/api/_protobuf/FrsTabInfo_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x10\x46rsTabInfo.proto".\n\nFrsTabInfo\x12\x0e\n\x06tab_id\x18\x01 \x01(\x05\x12\x10\n\x08tab_name\x18\x03 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "FrsTabInfo_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_FRSTABINFO"]._serialized_start = 20 _globals["_FRSTABINFO"]._serialized_end = 66 ================================================ FILE: src/aiotieba/api/_protobuf/Lcm.proto ================================================ syntax = "proto3"; message Common { string cuid = 1; string device = 2; string os_version = 3; string manufacture = 4; string model_type = 5; string app_id = 6; string app_version = 7; string sdk_version = 8; string network = 9; string rom_version = 10; string user_key = 11; } message LcmNotify { int64 log_id = 1; int32 action = 2; } message LcmRequest { int64 log_id = 1; string token = 2; Common common = 3; int64 timestamp = 4; int32 action = 5; int32 start_type = 6; int32 conn_type = 7; } message LcmResponse { int64 log_id = 1; int32 error_code = 2; string error_msg = 3; int64 next_interval_ms = 4; string server_info = 5; } message RpcData { LcmRequest lcm_request = 1; LcmResponse lcm_response = 2; LcmNotify lcm_notify = 3; } ================================================ FILE: src/aiotieba/api/_protobuf/Lcm_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\tLcm.proto"\xd5\x01\n\x06\x43ommon\x12\x0c\n\x04\x63uid\x18\x01 \x01(\t\x12\x0e\n\x06\x64\x65vice\x18\x02 \x01(\t\x12\x12\n\nos_version\x18\x03 \x01(\t\x12\x13\n\x0bmanufacture\x18\x04 \x01(\t\x12\x12\n\nmodel_type\x18\x05 \x01(\t\x12\x0e\n\x06\x61pp_id\x18\x06 \x01(\t\x12\x13\n\x0b\x61pp_version\x18\x07 \x01(\t\x12\x13\n\x0bsdk_version\x18\x08 \x01(\t\x12\x0f\n\x07network\x18\t \x01(\t\x12\x13\n\x0brom_version\x18\n \x01(\t\x12\x10\n\x08user_key\x18\x0b \x01(\t"+\n\tLcmNotify\x12\x0e\n\x06log_id\x18\x01 \x01(\x03\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\x05"\x8e\x01\n\nLcmRequest\x12\x0e\n\x06log_id\x18\x01 \x01(\x03\x12\r\n\x05token\x18\x02 \x01(\t\x12\x17\n\x06\x63ommon\x18\x03 \x01(\x0b\x32\x07.Common\x12\x11\n\ttimestamp\x18\x04 \x01(\x03\x12\x0e\n\x06\x61\x63tion\x18\x05 \x01(\x05\x12\x12\n\nstart_type\x18\x06 \x01(\x05\x12\x11\n\tconn_type\x18\x07 \x01(\x05"s\n\x0bLcmResponse\x12\x0e\n\x06log_id\x18\x01 \x01(\x03\x12\x12\n\nerror_code\x18\x02 \x01(\x05\x12\x11\n\terror_msg\x18\x03 \x01(\t\x12\x18\n\x10next_interval_ms\x18\x04 \x01(\x03\x12\x13\n\x0bserver_info\x18\x05 \x01(\t"o\n\x07RpcData\x12 \n\x0blcm_request\x18\x01 \x01(\x0b\x32\x0b.LcmRequest\x12"\n\x0clcm_response\x18\x02 \x01(\x0b\x32\x0c.LcmResponse\x12\x1e\n\nlcm_notify\x18\x03 \x01(\x0b\x32\n.LcmNotifyb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "Lcm_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_COMMON"]._serialized_start = 14 _globals["_COMMON"]._serialized_end = 227 _globals["_LCMNOTIFY"]._serialized_start = 229 _globals["_LCMNOTIFY"]._serialized_end = 272 _globals["_LCMREQUEST"]._serialized_start = 275 _globals["_LCMREQUEST"]._serialized_end = 417 _globals["_LCMRESPONSE"]._serialized_start = 419 _globals["_LCMRESPONSE"]._serialized_end = 534 _globals["_RPCDATA"]._serialized_start = 536 _globals["_RPCDATA"]._serialized_end = 647 ================================================ FILE: src/aiotieba/api/_protobuf/Media.proto ================================================ // tbclient.Media syntax = "proto3"; message Media { int32 type = 1; string small_pic = 2; string big_pic = 3; string water_pic = 4; uint32 width = 10; uint32 height = 11; string origin_pic = 15; uint32 origin_size = 16; } ================================================ FILE: src/aiotieba/api/_protobuf/Media_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x0bMedia.proto"\x94\x01\n\x05Media\x12\x0c\n\x04type\x18\x01 \x01(\x05\x12\x11\n\tsmall_pic\x18\x02 \x01(\t\x12\x0f\n\x07\x62ig_pic\x18\x03 \x01(\t\x12\x11\n\twater_pic\x18\x04 \x01(\t\x12\r\n\x05width\x18\n \x01(\r\x12\x0e\n\x06height\x18\x0b \x01(\r\x12\x12\n\norigin_pic\x18\x0f \x01(\t\x12\x13\n\x0borigin_size\x18\x10 \x01(\rb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "Media_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_MEDIA"]._serialized_start = 16 _globals["_MEDIA"]._serialized_end = 164 ================================================ FILE: src/aiotieba/api/_protobuf/Page.proto ================================================ // tbclient.Page syntax = "proto3"; message Page { int32 page_size = 1; int32 current_page = 3; int32 total_count = 4; int32 total_page = 5; int32 has_more = 6; int32 has_prev = 7; } ================================================ FILE: src/aiotieba/api/_protobuf/Page_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\nPage.proto"|\n\x04Page\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x14\n\x0c\x63urrent_page\x18\x03 \x01(\x05\x12\x13\n\x0btotal_count\x18\x04 \x01(\x05\x12\x12\n\ntotal_page\x18\x05 \x01(\x05\x12\x10\n\x08has_more\x18\x06 \x01(\x05\x12\x10\n\x08has_prev\x18\x07 \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "Page_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PAGE"]._serialized_start = 14 _globals["_PAGE"]._serialized_end = 138 ================================================ FILE: src/aiotieba/api/_protobuf/PbContent.proto ================================================ // tbclient.PbContent syntax = "proto3"; message PbContent { uint32 type = 1; string text = 2; string link = 3; string src = 4; string bsize = 5; string cdn_src = 8; string big_cdn_src = 9; string c = 11; string voice_md5 = 12; uint32 during_time = 13; int64 uid = 15; uint32 width = 18; uint32 height = 19; string origin_src = 25; uint32 origin_size = 27; int32 count = 28; message TiebaPlusInfo { string title = 1; string desc = 2; string jump_url = 3; string app_icon = 6; int32 target_type = 12; int32 h5_jump_type = 13; string h5_jump_number = 14; string h5_jump_param = 15; int32 jump_type = 16; string button_desc = 23; } TiebaPlusInfo tiebaplus_info = 40; message Item { string item_name = 2; } Item item = 41; } ================================================ FILE: src/aiotieba/api/_protobuf/PbContent_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x0fPbContent.proto"\xcf\x04\n\tPbContent\x12\x0c\n\x04type\x18\x01 \x01(\r\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x0c\n\x04link\x18\x03 \x01(\t\x12\x0b\n\x03src\x18\x04 \x01(\t\x12\r\n\x05\x62size\x18\x05 \x01(\t\x12\x0f\n\x07\x63\x64n_src\x18\x08 \x01(\t\x12\x13\n\x0b\x62ig_cdn_src\x18\t \x01(\t\x12\t\n\x01\x63\x18\x0b \x01(\t\x12\x11\n\tvoice_md5\x18\x0c \x01(\t\x12\x13\n\x0b\x64uring_time\x18\r \x01(\r\x12\x0b\n\x03uid\x18\x0f \x01(\x03\x12\r\n\x05width\x18\x12 \x01(\r\x12\x0e\n\x06height\x18\x13 \x01(\r\x12\x12\n\norigin_src\x18\x19 \x01(\t\x12\x13\n\x0borigin_size\x18\x1b \x01(\r\x12\r\n\x05\x63ount\x18\x1c \x01(\x05\x12\x30\n\x0etiebaplus_info\x18( \x01(\x0b\x32\x18.PbContent.TiebaPlusInfo\x12\x1d\n\x04item\x18) \x01(\x0b\x32\x0f.PbContent.Item\x1a\xd2\x01\n\rTiebaPlusInfo\x12\r\n\x05title\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x65sc\x18\x02 \x01(\t\x12\x10\n\x08jump_url\x18\x03 \x01(\t\x12\x10\n\x08\x61pp_icon\x18\x06 \x01(\t\x12\x13\n\x0btarget_type\x18\x0c \x01(\x05\x12\x14\n\x0ch5_jump_type\x18\r \x01(\x05\x12\x16\n\x0eh5_jump_number\x18\x0e \x01(\t\x12\x15\n\rh5_jump_param\x18\x0f \x01(\t\x12\x11\n\tjump_type\x18\x10 \x01(\x05\x12\x13\n\x0b\x62utton_desc\x18\x17 \x01(\t\x1a\x19\n\x04Item\x12\x11\n\titem_name\x18\x02 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "PbContent_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PBCONTENT"]._serialized_start = 20 _globals["_PBCONTENT"]._serialized_end = 611 _globals["_PBCONTENT_TIEBAPLUSINFO"]._serialized_start = 374 _globals["_PBCONTENT_TIEBAPLUSINFO"]._serialized_end = 584 _globals["_PBCONTENT_ITEM"]._serialized_start = 586 _globals["_PBCONTENT_ITEM"]._serialized_end = 611 ================================================ FILE: src/aiotieba/api/_protobuf/PollInfo.proto ================================================ // tbclient.PollInfo syntax = "proto3"; message PollInfo { int32 is_multi = 2; int64 total_num = 3; message PollOption { int64 num = 2; string text = 3; } repeated PollOption options = 9; int64 total_poll = 11; string title = 12; } ================================================ FILE: src/aiotieba/api/_protobuf/PollInfo_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b"\n\x0ePollInfo.proto\"\xa2\x01\n\x08PollInfo\x12\x10\n\x08is_multi\x18\x02 \x01(\x05\x12\x11\n\ttotal_num\x18\x03 \x01(\x03\x12%\n\x07options\x18\t \x03(\x0b\x32\x14.PollInfo.PollOption\x12\x12\n\ntotal_poll\x18\x0b \x01(\x03\x12\r\n\x05title\x18\x0c \x01(\t\x1a'\n\nPollOption\x12\x0b\n\x03num\x18\x02 \x01(\x03\x12\x0c\n\x04text\x18\x03 \x01(\tb\x06proto3" ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "PollInfo_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_POLLINFO"]._serialized_start = 19 _globals["_POLLINFO"]._serialized_end = 181 _globals["_POLLINFO_POLLOPTION"]._serialized_start = 142 _globals["_POLLINFO_POLLOPTION"]._serialized_end = 181 ================================================ FILE: src/aiotieba/api/_protobuf/Post.proto ================================================ // tbclient.Post syntax = "proto3"; import "PbContent.proto"; import "SubPostList.proto"; import "User.proto"; import "Agree.proto"; message Post { int64 id = 1; uint32 floor = 3; uint32 time = 4; repeated PbContent content = 5; uint32 sub_post_number = 13; int64 author_id = 19; message SubPost { repeated SubPostList sub_post_list = 2; } SubPost sub_post_list = 15; message SignatureData { message SignatureContent { int32 type = 1; string text = 2; } repeated SignatureContent content = 4; } SignatureData signature = 21; User author = 23; Agree agree = 37; int64 tid = 46; message ChatContent { string bot_uk = 1; } ChatContent chat_content = 78; message SpriteMemeInfo { int64 meme_id = 1; } SpriteMemeInfo sprite_meme_info = 79; } ================================================ FILE: src/aiotieba/api/_protobuf/PostInfoList.proto ================================================ // tbclient.PostInfoList syntax = "proto3"; import "Media.proto"; import "Voice.proto"; import "PollInfo.proto"; import "VideoInfo.proto"; import "PbContent.proto"; import "Agree.proto"; message PostInfoList { uint64 forum_id = 1; uint64 thread_id = 2; uint64 post_id = 3; uint32 create_time = 5; string forum_name = 6; string title = 7; message PostInfoContent { message Abstract { int32 type = 1; string text = 2; string link = 3; string during_time = 6; string voice_md5 = 7; } repeated Abstract post_content = 1; uint64 create_time = 2; uint64 post_type = 3; uint64 post_id = 4; } repeated PostInfoContent content = 8; string user_name = 10; repeated Media media = 16; uint32 reply_num = 17; int64 user_id = 18; string user_portrait = 19; repeated Voice voice_info = 23; uint64 thread_type = 26; PollInfo poll_info = 28; VideoInfo video_info = 29; int32 freq_num = 33; string name_show = 35; int32 share_num = 39; Agree agree = 40; int32 is_share_thread = 44; repeated PbContent first_post_content = 49; } ================================================ FILE: src/aiotieba/api/_protobuf/PostInfoList_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from . import Agree_pb2 as Agree__pb2 from . import Media_pb2 as Media__pb2 from . import PbContent_pb2 as PbContent__pb2 from . import PollInfo_pb2 as PollInfo__pb2 from . import VideoInfo_pb2 as VideoInfo__pb2 from . import Voice_pb2 as Voice__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b"\n\x12PostInfoList.proto\x1a\x0bMedia.proto\x1a\x0bVoice.proto\x1a\x0ePollInfo.proto\x1a\x0fVideoInfo.proto\x1a\x0fPbContent.proto\x1a\x0b\x41gree.proto\"\xf9\x05\n\x0cPostInfoList\x12\x10\n\x08\x66orum_id\x18\x01 \x01(\x04\x12\x11\n\tthread_id\x18\x02 \x01(\x04\x12\x0f\n\x07post_id\x18\x03 \x01(\x04\x12\x13\n\x0b\x63reate_time\x18\x05 \x01(\r\x12\x12\n\nforum_name\x18\x06 \x01(\t\x12\r\n\x05title\x18\x07 \x01(\t\x12.\n\x07\x63ontent\x18\x08 \x03(\x0b\x32\x1d.PostInfoList.PostInfoContent\x12\x11\n\tuser_name\x18\n \x01(\t\x12\x15\n\x05media\x18\x10 \x03(\x0b\x32\x06.Media\x12\x11\n\treply_num\x18\x11 \x01(\r\x12\x0f\n\x07user_id\x18\x12 \x01(\x03\x12\x15\n\ruser_portrait\x18\x13 \x01(\t\x12\x1a\n\nvoice_info\x18\x17 \x03(\x0b\x32\x06.Voice\x12\x13\n\x0bthread_type\x18\x1a \x01(\x04\x12\x1c\n\tpoll_info\x18\x1c \x01(\x0b\x32\t.PollInfo\x12\x1e\n\nvideo_info\x18\x1d \x01(\x0b\x32\n.VideoInfo\x12\x10\n\x08\x66req_num\x18! \x01(\x05\x12\x11\n\tname_show\x18# \x01(\t\x12\x11\n\tshare_num\x18' \x01(\x05\x12\x15\n\x05\x61gree\x18( \x01(\x0b\x32\x06.Agree\x12\x17\n\x0fis_share_thread\x18, \x01(\x05\x12&\n\x12\x66irst_post_content\x18\x31 \x03(\x0b\x32\n.PbContent\x1a\xe6\x01\n\x0fPostInfoContent\x12<\n\x0cpost_content\x18\x01 \x03(\x0b\x32&.PostInfoList.PostInfoContent.Abstract\x12\x13\n\x0b\x63reate_time\x18\x02 \x01(\x04\x12\x11\n\tpost_type\x18\x03 \x01(\x04\x12\x0f\n\x07post_id\x18\x04 \x01(\x04\x1a\\\n\x08\x41\x62stract\x12\x0c\n\x04type\x18\x01 \x01(\x05\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x0c\n\x04link\x18\x03 \x01(\t\x12\x13\n\x0b\x64uring_time\x18\x06 \x01(\t\x12\x11\n\tvoice_md5\x18\x07 \x01(\tb\x06proto3" ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "PostInfoList_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_POSTINFOLIST"]._serialized_start = 112 _globals["_POSTINFOLIST"]._serialized_end = 873 _globals["_POSTINFOLIST_POSTINFOCONTENT"]._serialized_start = 643 _globals["_POSTINFOLIST_POSTINFOCONTENT"]._serialized_end = 873 _globals["_POSTINFOLIST_POSTINFOCONTENT_ABSTRACT"]._serialized_start = 781 _globals["_POSTINFOLIST_POSTINFOCONTENT_ABSTRACT"]._serialized_end = 873 ================================================ FILE: src/aiotieba/api/_protobuf/Post_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from . import Agree_pb2 as Agree__pb2 from . import PbContent_pb2 as PbContent__pb2 from . import SubPostList_pb2 as SubPostList__pb2 from . import User_pb2 as User__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b"\n\nPost.proto\x1a\x0fPbContent.proto\x1a\x11SubPostList.proto\x1a\nUser.proto\x1a\x0b\x41gree.proto\"\xc4\x04\n\x04Post\x12\n\n\x02id\x18\x01 \x01(\x03\x12\r\n\x05\x66loor\x18\x03 \x01(\r\x12\x0c\n\x04time\x18\x04 \x01(\r\x12\x1b\n\x07\x63ontent\x18\x05 \x03(\x0b\x32\n.PbContent\x12\x17\n\x0fsub_post_number\x18\r \x01(\r\x12\x11\n\tauthor_id\x18\x13 \x01(\x03\x12$\n\rsub_post_list\x18\x0f \x01(\x0b\x32\r.Post.SubPost\x12&\n\tsignature\x18\x15 \x01(\x0b\x32\x13.Post.SignatureData\x12\x15\n\x06\x61uthor\x18\x17 \x01(\x0b\x32\x05.User\x12\x15\n\x05\x61gree\x18% \x01(\x0b\x32\x06.Agree\x12\x0b\n\x03tid\x18. \x01(\x03\x12'\n\x0c\x63hat_content\x18N \x01(\x0b\x32\x11.Post.ChatContent\x12.\n\x10sprite_meme_info\x18O \x01(\x0b\x32\x14.Post.SpriteMemeInfo\x1a.\n\x07SubPost\x12#\n\rsub_post_list\x18\x02 \x03(\x0b\x32\x0c.SubPostList\x1av\n\rSignatureData\x12\x35\n\x07\x63ontent\x18\x04 \x03(\x0b\x32$.Post.SignatureData.SignatureContent\x1a.\n\x10SignatureContent\x12\x0c\n\x04type\x18\x01 \x01(\x05\x12\x0c\n\x04text\x18\x02 \x01(\t\x1a\x1d\n\x0b\x43hatContent\x12\x0e\n\x06\x62ot_uk\x18\x01 \x01(\t\x1a!\n\x0eSpriteMemeInfo\x12\x0f\n\x07meme_id\x18\x01 \x01(\x03\x62\x06proto3" ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "Post_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_POST"]._serialized_start = 76 _globals["_POST"]._serialized_end = 656 _globals["_POST_SUBPOST"]._serialized_start = 424 _globals["_POST_SUBPOST"]._serialized_end = 470 _globals["_POST_SIGNATUREDATA"]._serialized_start = 472 _globals["_POST_SIGNATUREDATA"]._serialized_end = 590 _globals["_POST_SIGNATUREDATA_SIGNATURECONTENT"]._serialized_start = 544 _globals["_POST_SIGNATUREDATA_SIGNATURECONTENT"]._serialized_end = 590 _globals["_POST_CHATCONTENT"]._serialized_start = 592 _globals["_POST_CHATCONTENT"]._serialized_end = 621 _globals["_POST_SPRITEMEMEINFO"]._serialized_start = 623 _globals["_POST_SPRITEMEMEINFO"]._serialized_end = 656 ================================================ FILE: src/aiotieba/api/_protobuf/Rpc.proto ================================================ syntax = "proto3"; message ChunkInfo { int64 stream_id = 1; int64 chunk_id = 2; } message EventTimestamp { string event = 1; int64 timestamp_ms = 2; } message RpcNotifyMeta { int64 service_id = 1; int64 method_id = 2; int64 log_id = 3; repeated EventTimestamp event_list = 4; } message RpcRequestMeta { int64 service_id = 1; int64 method_id = 2; int64 log_id = 3; int32 need_common = 4; repeated EventTimestamp event_list = 5; } message RpcResponseMeta { int64 service_id = 1; int64 method_id = 2; int64 log_id = 3; int32 error_code = 4; string error_text = 5; repeated EventTimestamp event_list = 6; } message RpcMeta{ RpcRequestMeta request = 1; RpcResponseMeta response = 2; optional int32 compress_type = 3; int64 correlation_id = 4; int32 attachment_size = 5; ChunkInfo chunk_info = 6; bytes authentication_data = 7; RpcNotifyMeta notify = 8; int32 accept_compress_type = 9; } ================================================ FILE: src/aiotieba/api/_protobuf/Rpc_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\tRpc.proto"0\n\tChunkInfo\x12\x11\n\tstream_id\x18\x01 \x01(\x03\x12\x10\n\x08\x63hunk_id\x18\x02 \x01(\x03"5\n\x0e\x45ventTimestamp\x12\r\n\x05\x65vent\x18\x01 \x01(\t\x12\x14\n\x0ctimestamp_ms\x18\x02 \x01(\x03"k\n\rRpcNotifyMeta\x12\x12\n\nservice_id\x18\x01 \x01(\x03\x12\x11\n\tmethod_id\x18\x02 \x01(\x03\x12\x0e\n\x06log_id\x18\x03 \x01(\x03\x12#\n\nevent_list\x18\x04 \x03(\x0b\x32\x0f.EventTimestamp"\x81\x01\n\x0eRpcRequestMeta\x12\x12\n\nservice_id\x18\x01 \x01(\x03\x12\x11\n\tmethod_id\x18\x02 \x01(\x03\x12\x0e\n\x06log_id\x18\x03 \x01(\x03\x12\x13\n\x0bneed_common\x18\x04 \x01(\x05\x12#\n\nevent_list\x18\x05 \x03(\x0b\x32\x0f.EventTimestamp"\x95\x01\n\x0fRpcResponseMeta\x12\x12\n\nservice_id\x18\x01 \x01(\x03\x12\x11\n\tmethod_id\x18\x02 \x01(\x03\x12\x0e\n\x06log_id\x18\x03 \x01(\x03\x12\x12\n\nerror_code\x18\x04 \x01(\x05\x12\x12\n\nerror_text\x18\x05 \x01(\t\x12#\n\nevent_list\x18\x06 \x03(\x0b\x32\x0f.EventTimestamp"\xa9\x02\n\x07RpcMeta\x12 \n\x07request\x18\x01 \x01(\x0b\x32\x0f.RpcRequestMeta\x12"\n\x08response\x18\x02 \x01(\x0b\x32\x10.RpcResponseMeta\x12\x1a\n\rcompress_type\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x16\n\x0e\x63orrelation_id\x18\x04 \x01(\x03\x12\x17\n\x0f\x61ttachment_size\x18\x05 \x01(\x05\x12\x1e\n\nchunk_info\x18\x06 \x01(\x0b\x32\n.ChunkInfo\x12\x1b\n\x13\x61uthentication_data\x18\x07 \x01(\x0c\x12\x1e\n\x06notify\x18\x08 \x01(\x0b\x32\x0e.RpcNotifyMeta\x12\x1c\n\x14\x61\x63\x63\x65pt_compress_type\x18\t \x01(\x05\x42\x10\n\x0e_compress_typeb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "Rpc_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_CHUNKINFO"]._serialized_start = 13 _globals["_CHUNKINFO"]._serialized_end = 61 _globals["_EVENTTIMESTAMP"]._serialized_start = 63 _globals["_EVENTTIMESTAMP"]._serialized_end = 116 _globals["_RPCNOTIFYMETA"]._serialized_start = 118 _globals["_RPCNOTIFYMETA"]._serialized_end = 225 _globals["_RPCREQUESTMETA"]._serialized_start = 228 _globals["_RPCREQUESTMETA"]._serialized_end = 357 _globals["_RPCRESPONSEMETA"]._serialized_start = 360 _globals["_RPCRESPONSEMETA"]._serialized_end = 509 _globals["_RPCMETA"]._serialized_start = 512 _globals["_RPCMETA"]._serialized_end = 809 ================================================ FILE: src/aiotieba/api/_protobuf/SimpleForum.proto ================================================ // tbclient.SimpleForum syntax = "proto3"; message SimpleForum { int64 id = 1; string name = 2; string first_class = 7; string second_class = 8; int32 member_num = 12; int32 post_num = 13; } ================================================ FILE: src/aiotieba/api/_protobuf/SimpleForum_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x11SimpleForum.proto"x\n\x0bSimpleForum\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x66irst_class\x18\x07 \x01(\t\x12\x14\n\x0csecond_class\x18\x08 \x01(\t\x12\x12\n\nmember_num\x18\x0c \x01(\x05\x12\x10\n\x08post_num\x18\r \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "SimpleForum_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_SIMPLEFORUM"]._serialized_start = 21 _globals["_SIMPLEFORUM"]._serialized_end = 141 ================================================ FILE: src/aiotieba/api/_protobuf/SubPostList.proto ================================================ // tbclient.SubPost syntax = "proto3"; import "PbContent.proto"; import "User.proto"; import "Agree.proto"; message SubPostList { int64 id = 1; repeated PbContent content = 2; uint32 time = 3; int64 author_id = 4; string title = 5; uint32 floor = 6; User author = 7; Agree agree = 9; } message SubPost { uint64 pid = 1; repeated SubPostList sub_post_list = 2; } ================================================ FILE: src/aiotieba/api/_protobuf/SubPostList_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from . import Agree_pb2 as Agree__pb2 from . import PbContent_pb2 as PbContent__pb2 from . import User_pb2 as User__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x11SubPostList.proto\x1a\x0fPbContent.proto\x1a\nUser.proto\x1a\x0b\x41gree.proto"\xa3\x01\n\x0bSubPostList\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x1b\n\x07\x63ontent\x18\x02 \x03(\x0b\x32\n.PbContent\x12\x0c\n\x04time\x18\x03 \x01(\r\x12\x11\n\tauthor_id\x18\x04 \x01(\x03\x12\r\n\x05title\x18\x05 \x01(\t\x12\r\n\x05\x66loor\x18\x06 \x01(\r\x12\x15\n\x06\x61uthor\x18\x07 \x01(\x0b\x32\x05.User\x12\x15\n\x05\x61gree\x18\t \x01(\x0b\x32\x06.Agree";\n\x07SubPost\x12\x0b\n\x03pid\x18\x01 \x01(\x04\x12#\n\rsub_post_list\x18\x02 \x03(\x0b\x32\x0c.SubPostListb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "SubPostList_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_SUBPOSTLIST"]._serialized_start = 64 _globals["_SUBPOSTLIST"]._serialized_end = 227 _globals["_SUBPOST"]._serialized_start = 229 _globals["_SUBPOST"]._serialized_end = 288 ================================================ FILE: src/aiotieba/api/_protobuf/ThreadInfo.proto ================================================ // tbclient.ThreadInfo syntax = "proto3"; import "User.proto"; import "Voice.proto"; import "PollInfo.proto"; import "VideoInfo.proto"; import "PbContent.proto"; import "Agree.proto"; import "Media.proto"; message ThreadInfo { int64 id = 1; string title = 3; int32 reply_num = 4; int32 view_num = 5; int32 last_time_int = 7; int32 is_top = 9; 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; string fname = 28; int32 is_livepost = 30; int64 first_post_id = 40; int32 create_time = 45; int64 post_id = 52; int64 author_id = 56; uint32 is_ad = 59; PollInfo poll_info = 74; VideoInfo video_info = 79; int32 is_godthread_recommend = 85; Agree agree = 126; int32 share_num = 135; message OriginThreadInfo { string title = 1; repeated Media media = 2; string fname = 4; string tid = 5; int64 fid = 7; repeated Voice voice_info = 12; VideoInfo video_info = 13; repeated PbContent content = 14; PollInfo poll_info = 21; int64 pid = 25; } OriginThreadInfo origin_thread_info = 141; repeated PbContent first_post_content = 142; int32 is_share_thread = 143; int32 tab_id = 175; int32 is_deleted = 181; int32 is_frs_mask = 198; } ================================================ FILE: src/aiotieba/api/_protobuf/ThreadInfo_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from . import Agree_pb2 as Agree__pb2 from . import Media_pb2 as Media__pb2 from . import PbContent_pb2 as PbContent__pb2 from . import PollInfo_pb2 as PollInfo__pb2 from . import User_pb2 as User__pb2 from . import VideoInfo_pb2 as VideoInfo__pb2 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\"\xbd\x07\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\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\x62\x06proto3" ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "ThreadInfo_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_THREADINFO"]._serialized_start = 122 _globals["_THREADINFO"]._serialized_end = 1079 _globals["_THREADINFO_ORIGINTHREADINFO"]._serialized_start = 850 _globals["_THREADINFO_ORIGINTHREADINFO"]._serialized_end = 1079 ================================================ FILE: src/aiotieba/api/_protobuf/User.proto ================================================ // tbclient.User syntax = "proto3"; message User { int64 id = 2; string name = 3; string name_show = 4; string portrait = 5; message Icon { string name = 1; } repeated Icon iconinfo = 17; int32 is_coreuser = 20; int32 level_id = 23; int32 is_bawu = 25; string bawu_type = 26; string BDUSS = 29; int32 fans_num = 30; int32 concern_num = 31; int32 sex = 32; int32 my_like_num = 33; string intro = 34; int32 post_num = 37; string tb_age = 38; int32 gender = 42; message PrivSets { int32 location = 1; int32 like = 2; int32 group = 3; int32 post = 4; int32 friend = 5; int32 live = 6; int32 reply = 7; int32 bazhu_show_inside = 8; int32 bazhu_show_outside = 9; } PrivSets priv_sets = 45; int32 is_friend = 46; message LikeForumInfo { string forum_name = 1; uint64 forum_id = 2; } repeated LikeForumInfo likeForum = 47; int32 is_guanfang = 52; message UserVipInfo { uint32 v_status = 1; uint32 v_level = 5; } UserVipInfo vipInfo = 61; message TshowInfo { string name = 2; } repeated TshowInfo new_tshow_icon = 65; int32 is_fans = 91; message NewGodInfo { int32 status = 1; uint32 field_id = 2; string field_name = 3; } NewGodInfo new_god_data = 101; int32 is_default_avatar = 106; string tieba_uid = 120; string ip_address = 127; message UserGrowth { uint32 level_id = 1; } UserGrowth user_growth = 137; } ================================================ FILE: src/aiotieba/api/_protobuf/User_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\nUser.proto"\xd6\x08\n\x04User\x12\n\n\x02id\x18\x02 \x01(\x03\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x11\n\tname_show\x18\x04 \x01(\t\x12\x10\n\x08portrait\x18\x05 \x01(\t\x12\x1c\n\x08iconinfo\x18\x11 \x03(\x0b\x32\n.User.Icon\x12\x13\n\x0bis_coreuser\x18\x14 \x01(\x05\x12\x10\n\x08level_id\x18\x17 \x01(\x05\x12\x0f\n\x07is_bawu\x18\x19 \x01(\x05\x12\x11\n\tbawu_type\x18\x1a \x01(\t\x12\r\n\x05\x42\x44USS\x18\x1d \x01(\t\x12\x10\n\x08\x66\x61ns_num\x18\x1e \x01(\x05\x12\x13\n\x0b\x63oncern_num\x18\x1f \x01(\x05\x12\x0b\n\x03sex\x18 \x01(\x05\x12\x13\n\x0bmy_like_num\x18! \x01(\x05\x12\r\n\x05intro\x18" \x01(\t\x12\x10\n\x08post_num\x18% \x01(\x05\x12\x0e\n\x06tb_age\x18& \x01(\t\x12\x0e\n\x06gender\x18* \x01(\x05\x12!\n\tpriv_sets\x18- \x01(\x0b\x32\x0e.User.PrivSets\x12\x11\n\tis_friend\x18. \x01(\x05\x12&\n\tlikeForum\x18/ \x03(\x0b\x32\x13.User.LikeForumInfo\x12\x13\n\x0bis_guanfang\x18\x34 \x01(\x05\x12"\n\x07vipInfo\x18= \x01(\x0b\x32\x11.User.UserVipInfo\x12\'\n\x0enew_tshow_icon\x18\x41 \x03(\x0b\x32\x0f.User.TshowInfo\x12\x0f\n\x07is_fans\x18[ \x01(\x05\x12&\n\x0cnew_god_data\x18\x65 \x01(\x0b\x32\x10.User.NewGodInfo\x12\x19\n\x11is_default_avatar\x18j \x01(\x05\x12\x11\n\ttieba_uid\x18x \x01(\t\x12\x12\n\nip_address\x18\x7f \x01(\t\x12&\n\x0buser_growth\x18\x89\x01 \x01(\x0b\x32\x10.User.UserGrowth\x1a\x14\n\x04Icon\x12\x0c\n\x04name\x18\x01 \x01(\t\x1a\xab\x01\n\x08PrivSets\x12\x10\n\x08location\x18\x01 \x01(\x05\x12\x0c\n\x04like\x18\x02 \x01(\x05\x12\r\n\x05group\x18\x03 \x01(\x05\x12\x0c\n\x04post\x18\x04 \x01(\x05\x12\x0e\n\x06\x66riend\x18\x05 \x01(\x05\x12\x0c\n\x04live\x18\x06 \x01(\x05\x12\r\n\x05reply\x18\x07 \x01(\x05\x12\x19\n\x11\x62\x61zhu_show_inside\x18\x08 \x01(\x05\x12\x1a\n\x12\x62\x61zhu_show_outside\x18\t \x01(\x05\x1a\x35\n\rLikeForumInfo\x12\x12\n\nforum_name\x18\x01 \x01(\t\x12\x10\n\x08\x66orum_id\x18\x02 \x01(\x04\x1a\x30\n\x0bUserVipInfo\x12\x10\n\x08v_status\x18\x01 \x01(\r\x12\x0f\n\x07v_level\x18\x05 \x01(\r\x1a\x19\n\tTshowInfo\x12\x0c\n\x04name\x18\x02 \x01(\t\x1a\x42\n\nNewGodInfo\x12\x0e\n\x06status\x18\x01 \x01(\x05\x12\x10\n\x08\x66ield_id\x18\x02 \x01(\r\x12\x12\n\nfield_name\x18\x03 \x01(\t\x1a\x1e\n\nUserGrowth\x12\x10\n\x08level_id\x18\x01 \x01(\rb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "User_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_USER"]._serialized_start = 15 _globals["_USER"]._serialized_end = 1125 _globals["_USER_ICON"]._serialized_start = 699 _globals["_USER_ICON"]._serialized_end = 719 _globals["_USER_PRIVSETS"]._serialized_start = 722 _globals["_USER_PRIVSETS"]._serialized_end = 893 _globals["_USER_LIKEFORUMINFO"]._serialized_start = 895 _globals["_USER_LIKEFORUMINFO"]._serialized_end = 948 _globals["_USER_USERVIPINFO"]._serialized_start = 950 _globals["_USER_USERVIPINFO"]._serialized_end = 998 _globals["_USER_TSHOWINFO"]._serialized_start = 1000 _globals["_USER_TSHOWINFO"]._serialized_end = 1025 _globals["_USER_NEWGODINFO"]._serialized_start = 1027 _globals["_USER_NEWGODINFO"]._serialized_end = 1093 _globals["_USER_USERGROWTH"]._serialized_start = 1095 _globals["_USER_USERGROWTH"]._serialized_end = 1125 ================================================ FILE: src/aiotieba/api/_protobuf/VideoInfo.proto ================================================ // tbclient.VideoInfo syntax = "proto3"; message VideoInfo { string video_url = 2; uint32 video_duration = 3; uint32 video_width = 4; uint32 video_height = 5; string thumbnail_url = 6; int32 play_count = 10; } ================================================ FILE: src/aiotieba/api/_protobuf/VideoInfo_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x0fVideoInfo.proto"\x8c\x01\n\tVideoInfo\x12\x11\n\tvideo_url\x18\x02 \x01(\t\x12\x16\n\x0evideo_duration\x18\x03 \x01(\r\x12\x13\n\x0bvideo_width\x18\x04 \x01(\r\x12\x14\n\x0cvideo_height\x18\x05 \x01(\r\x12\x15\n\rthumbnail_url\x18\x06 \x01(\t\x12\x12\n\nplay_count\x18\n \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "VideoInfo_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_VIDEOINFO"]._serialized_start = 20 _globals["_VIDEOINFO"]._serialized_end = 160 ================================================ FILE: src/aiotieba/api/_protobuf/Voice.proto ================================================ // tbclient.Voice syntax = "proto3"; message Voice { int32 during_time = 2; string voice_md5 = 3; } ================================================ FILE: src/aiotieba/api/_protobuf/Voice_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x0bVoice.proto"/\n\x05Voice\x12\x13\n\x0b\x64uring_time\x18\x02 \x01(\x05\x12\x11\n\tvoice_md5\x18\x03 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "Voice_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_VOICE"]._serialized_start = 15 _globals["_VOICE"]._serialized_end = 62 ================================================ FILE: src/aiotieba/api/_protobuf/__init__.py ================================================ ================================================ FILE: src/aiotieba/api/add_bawu/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/add_bawu/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...enums import BawuType from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) async def request(http_core: HttpCore, fid: int, user_name: str, bawu_type: BawuType) -> BoolResponse: data = [ ("fn", "-"), ("fid", fid), ("team_un", user_name), ("type", bawu_type), ("tbs", http_core.account.tbs), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/bawuteamadd"), data ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/add_bawu_blacklist/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/add_bawu_blacklist/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := res_json["errno"]: raise TiebaServerError(code, res_json["errmsg"]) async def request(http_core: HttpCore, fname: str, user_id: int) -> BoolResponse: data = [ ("tbs", http_core.account.tbs), ("user_id", user_id), ("word", fname), ("ie", "utf-8"), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/bawu2/platform/addBlack"), data ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/add_blacklist_old/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/add_blacklist_old/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) if code := int(res_json["errorno"]): raise TiebaServerError(code, res_json["errmsg"]) async def request(http_core: HttpCore, user_id: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("mute_user", user_id), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/user/userMuteAdd"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/add_post/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws ================================================ FILE: src/aiotieba/api/add_post/_api.py ================================================ import datetime import time import yarl from ...__version__ import __version__ from ...const import APP_BASE_HOST from ...core import Account, HttpCore, WsCore from ...exception import BoolResponse, TiebaServerError, TiebaValueError from .protobuf import AddPostReqIdl_pb2, AddPostResIdl_pb2 CMD = 309731 def parse_body(body: bytes) -> None: res_proto = AddPostResIdl_pb2.AddPostResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) if int(res_proto.data.info.need_vcode): raise TiebaValueError("Need verify code") def pack_proto(account: Account, fname: str, fid: int, tid: int, show_name: str, content: str) -> bytes: req_proto = AddPostReqIdl_pb2.AddPostReqIdl() req_proto.data.common.BDUSS = account.BDUSS req_proto.data.common._client_type = 2 req_proto.data.common._client_version = "12.35.1.0" req_proto.data.common._client_id = account.client_id req_proto.data.common._phone_imei = "000000000000000" req_proto.data.common._from = "1008621x" req_proto.data.common.cuid = account.cuid_galaxy2 current_ts = time.time() current_tsms = int(current_ts * 1000) current_dt = datetime.datetime.fromtimestamp(current_ts) req_proto.data.common._timestamp = current_tsms req_proto.data.common.model = "SM-G988N" req_proto.data.common.tbs = account.tbs req_proto.data.common.net_type = 1 req_proto.data.common.pversion = "1.0.3" req_proto.data.common._os_version = "9" req_proto.data.common.brand = "samsung" req_proto.data.common.lego_lib_version = "3.0.0" req_proto.data.common.applist = "" req_proto.data.common.stoken = account.STOKEN req_proto.data.common.z_id = account.z_id req_proto.data.common.cuid_galaxy2 = account.cuid_galaxy2 req_proto.data.common.cuid_gid = "" req_proto.data.common.c3_aid = account.c3_aid req_proto.data.common.sample_id = account.sample_id req_proto.data.common.scr_w = 720 req_proto.data.common.scr_w = 1280 req_proto.data.common.scr_dip = 1.5 req_proto.data.common.q_type = 0 req_proto.data.common.is_teenager = 0 req_proto.data.common.sdk_ver = "2.34.0" req_proto.data.common.framework_ver = "3340042" req_proto.data.common.naws_game_ver = "1038000" req_proto.data.common.active_timestamp = current_tsms - 86400 * 30 req_proto.data.common.first_install_time = current_tsms - 86400 * 30 req_proto.data.common.last_update_time = current_tsms - 86400 * 30 req_proto.data.common.event_day = f"{current_dt.year}{current_dt.month}{current_dt.day}" req_proto.data.common.android_id = account.android_id req_proto.data.common.cmode = 1 req_proto.data.common.start_scheme = "" req_proto.data.common.start_type = 1 req_proto.data.common.idfv = "0" req_proto.data.common.extra = "" req_proto.data.common.user_agent = f"aiotieba/{__version__}" req_proto.data.common.personalized_rec_switch = 1 req_proto.data.common.device_score = "0.4" req_proto.data.anonymous = "1" req_proto.data.can_no_forum = "0" req_proto.data.is_feedback = "0" req_proto.data.takephoto_num = "0" req_proto.data.entrance_type = "0" req_proto.data.vcode_tag = "12" req_proto.data.new_vcode = "1" req_proto.data.content = content req_proto.data.fid = str(fid) req_proto.data.v_fid = "" req_proto.data.v_fname = "" req_proto.data.kw = fname req_proto.data.is_barrage = "0" req_proto.data.from_fourm_id = str(fid) req_proto.data.tid = str(tid) req_proto.data.is_ad = "0" req_proto.data.post_from = "3" req_proto.data.name_show = show_name req_proto.data.is_pictxt = "0" req_proto.data.show_custom_figure = 0 req_proto.data.is_show_bless = 0 return req_proto.SerializeToString() async def request_http( http_core: HttpCore, fname: str, fid: int, tid: int, show_name: str, content: str ) -> BoolResponse: data = pack_proto(http_core.account, fname, fid, tid, show_name, content) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/post/add", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() async def request_ws(ws_core: WsCore, fname: str, fid: int, tid: int, show_name: str, content: str) -> BoolResponse: data = pack_proto(ws_core.account, fname, fid, tid, show_name, content) response = await ws_core.send(data, CMD) parse_body(await response.read()) return BoolResponse() ================================================ FILE: src/aiotieba/api/add_post/protobuf/AddPostReqIdl.proto ================================================ // tbclient.AddPost.AddPostReqIdl syntax = "proto3"; import "CommonReq.proto"; message AddPostReqIdl { message DataReq { CommonReq common = 1; string anonymous = 6; string can_no_forum = 7; string is_feedback = 8; string takephoto_num = 9; string entrance_type = 10; string vcode_tag = 16; string new_vcode = 18; string content = 19; string fid = 26; string v_fid = 28; string v_fname = 29; string kw = 30; string is_barrage = 31; string barrage_time = 32; string from_fourm_id = 44; string tid = 45; string is_ad = 51; string post_from = 55; string name_show = 58; string is_pictxt = 60; int32 show_custom_figure = 64; int32 is_show_bless = 67; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/add_post/protobuf/AddPostReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x13\x41\x64\x64PostReqIdl.proto\x1a\x0f\x43ommonReq.proto"\x82\x04\n\rAddPostReqIdl\x12$\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x16.AddPostReqIdl.DataReq\x1a\xca\x03\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\n.CommonReq\x12\x11\n\tanonymous\x18\x06 \x01(\t\x12\x14\n\x0c\x63\x61n_no_forum\x18\x07 \x01(\t\x12\x13\n\x0bis_feedback\x18\x08 \x01(\t\x12\x15\n\rtakephoto_num\x18\t \x01(\t\x12\x15\n\rentrance_type\x18\n \x01(\t\x12\x11\n\tvcode_tag\x18\x10 \x01(\t\x12\x11\n\tnew_vcode\x18\x12 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x13 \x01(\t\x12\x0b\n\x03\x66id\x18\x1a \x01(\t\x12\r\n\x05v_fid\x18\x1c \x01(\t\x12\x0f\n\x07v_fname\x18\x1d \x01(\t\x12\n\n\x02kw\x18\x1e \x01(\t\x12\x12\n\nis_barrage\x18\x1f \x01(\t\x12\x14\n\x0c\x62\x61rrage_time\x18 \x01(\t\x12\x15\n\rfrom_fourm_id\x18, \x01(\t\x12\x0b\n\x03tid\x18- \x01(\t\x12\r\n\x05is_ad\x18\x33 \x01(\t\x12\x11\n\tpost_from\x18\x37 \x01(\t\x12\x11\n\tname_show\x18: \x01(\t\x12\x11\n\tis_pictxt\x18< \x01(\t\x12\x1a\n\x12show_custom_figure\x18@ \x01(\x05\x12\x15\n\ris_show_bless\x18\x43 \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "AddPostReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_ADDPOSTREQIDL"]._serialized_start = 41 _globals["_ADDPOSTREQIDL"]._serialized_end = 555 _globals["_ADDPOSTREQIDL_DATAREQ"]._serialized_start = 97 _globals["_ADDPOSTREQIDL_DATAREQ"]._serialized_end = 555 ================================================ FILE: src/aiotieba/api/add_post/protobuf/AddPostResIdl.proto ================================================ // tbclient.AddPost.AddPostResIdl syntax = "proto3"; import "Error.proto"; message AddPostResIdl { Error error = 1; message DataRes { string video_id = 4; string msg = 5; string pre_msg = 6; string color_msg = 7; message PostAntiInfo { string need_vcode = 3; } PostAntiInfo info = 14; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/add_post/protobuf/AddPostResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x13\x41\x64\x64PostResIdl.proto\x1a\x0b\x45rror.proto"\xf2\x01\n\rAddPostResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12$\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x16.AddPostResIdl.DataRes\x1a\xa3\x01\n\x07\x44\x61taRes\x12\x10\n\x08video_id\x18\x04 \x01(\t\x12\x0b\n\x03msg\x18\x05 \x01(\t\x12\x0f\n\x07pre_msg\x18\x06 \x01(\t\x12\x11\n\tcolor_msg\x18\x07 \x01(\t\x12\x31\n\x04info\x18\x0e \x01(\x0b\x32#.AddPostResIdl.DataRes.PostAntiInfo\x1a"\n\x0cPostAntiInfo\x12\x12\n\nneed_vcode\x18\x03 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "AddPostResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_ADDPOSTRESIDL"]._serialized_start = 37 _globals["_ADDPOSTRESIDL"]._serialized_end = 279 _globals["_ADDPOSTRESIDL_DATARES"]._serialized_start = 116 _globals["_ADDPOSTRESIDL_DATARES"]._serialized_end = 279 _globals["_ADDPOSTRESIDL_DATARES_POSTANTIINFO"]._serialized_start = 245 _globals["_ADDPOSTRESIDL_DATARES_POSTANTIINFO"]._serialized_end = 279 ================================================ FILE: src/aiotieba/api/agree/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/agree/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request( http_core: HttpCore, tid: int, pid: int, is_comment: bool, is_disagree: bool, is_undo: bool ) -> BoolResponse: if pid: obj_type = 2 if is_comment else 1 else: obj_type = 3 data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("agree_type", 5 if is_disagree else 2), ("cuid", http_core.account.cuid_galaxy2), ("obj_type", obj_type), ("op_type", str(int(is_undo))), ("post_id", pid), ("tbs", http_core.account.tbs), ("thread_id", tid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/agree/opAgree"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/block/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/block/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int, portrait: str, day: int, reason: str) -> BoolResponse: is_svip_block = 0 if day in [1, 3, 10] else 1 data = [ ("BDUSS", http_core.account.BDUSS), ("day", day), ("fid", fid), ("is_loop_ban", is_svip_block), ("ntn", "banid"), ("portrait", portrait), ("reason", reason), ("tbs", http_core.account.tbs), ("word", "-"), ("z", 6), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/commitprison"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/del_bawu/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/del_bawu/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...enums import BawuType from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) async def request(http_core: HttpCore, fid: int, portrait: str, bawu_type: BawuType) -> BoolResponse: data = [ ("fn", "-"), ("fid", fid), ("team_un", "-"), ("team_uid", portrait), ("bawu_type", bawu_type), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/bawuteamclear"), data ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/del_bawu_blacklist/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/del_bawu_blacklist/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := res_json["errno"]: raise TiebaServerError(code, res_json["errmsg"]) async def request(http_core: HttpCore, fname: str, user_id: int) -> BoolResponse: data = [ ("word", fname), ("tbs", http_core.account.tbs), ("list[]", user_id), ("ie", "utf-8"), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/bawu2/platform/cancelBlack"), data ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/del_blacklist_old/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/del_blacklist_old/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) if code := int(res_json["errorno"]): raise TiebaServerError(code, res_json["errmsg"]) async def request(http_core: HttpCore, user_id: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("mute_user", user_id), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/user/userMuteDel"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/del_post/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/del_post/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int, tid: int, pid: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("fid", fid), ("pid", pid), ("tbs", http_core.account.tbs), ("z", tid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/delpost"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/del_posts/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/del_posts/_api.py ================================================ from __future__ import annotations from typing import TYPE_CHECKING import yarl from ...const import APP_BASE_HOST from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json if TYPE_CHECKING: from ...core import HttpCore def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int, tid: int, pids: list[int], block: bool) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("forum_id", fid), ("post_ids", ",".join(map(str, pids))), ("tbs", http_core.account.tbs), ("thread_id", tid), ("type", 2 if block else 1), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/multiDelPost"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/del_thread/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/del_thread/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int, tid: int, is_hide: bool) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("fid", fid), ("is_frs_mask", int(is_hide)), ("tbs", http_core.account.tbs), ("z", tid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/delthread"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/del_threads/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/del_threads/_api.py ================================================ from __future__ import annotations from typing import TYPE_CHECKING import yarl from ...const import APP_BASE_HOST from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json if TYPE_CHECKING: from ...core import HttpCore def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int, tids: list[int], block: bool) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("forum_id", fid), ("tbs", http_core.account.tbs), ("thread_ids", ",".join(map(str, tids))), ("type", 2 if block else 1), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/multiDelThread"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/dislike_forum/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/dislike_forum/_api.py ================================================ import time import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import pack_json, parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ( "dislike", pack_json([{"tid": 1, "dislike_ids": 7, "fid": fid, "click_time": int(time.time() * 1000)}]), ), ("dislike_from", "homepage"), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/excellent/submitDislike"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/follow_forum/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/follow_forum/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) if "error" in res_json and (code := int(res_json["error"]["errno"])): raise TiebaServerError(code, res_json["error"]["errmsg"]) async def request(http_core: HttpCore, fid: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("fid", fid), ("tbs", http_core.account.tbs), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/forum/like"), data ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/follow_user/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/follow_user/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, portrait: str) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("portrait", portrait), ("tbs", http_core.account.tbs), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/user/follow"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/get_ats/__init__.py ================================================ from ._api import parse_body, request from ._classdef import At, Ats ================================================ FILE: src/aiotieba/api/get_ats/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import Ats def parse_body(body: bytes) -> Ats: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) ats = Ats.from_json(res_json) return ats async def request(http_core: HttpCore, pn: int) -> Ats: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("pn", pn), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/u/feed/atme"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_ats/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...enums import PrivLike, PrivReply from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class Page_at: """ 页信息 Attributes: current_page (int): 当前页码 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 has_more: bool = False has_prev: int = False @staticmethod def from_json(data_map: Mapping) -> Page_at: current_page = int(data_map["current_page"]) has_more = bool(int(data_map["has_more"])) has_prev = bool(int(data_map["has_prev"])) return Page_at(current_page, has_more, has_prev) @dcs.dataclass class UserInfo_at: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 priv_like (PrivLike): 关注吧列表的公开状态 priv_reply (PrivReply): 帖子评论权限 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" priv_like: PrivLike = PrivLike.PUBLIC priv_reply: PrivReply = PrivReply.ALL @staticmethod def from_json(data_map: Mapping) -> UserInfo_at: user_id = int(data_map["id"]) portrait = data_map["portrait"] if "?" in portrait: portrait = portrait[:-13] user_name = data_map["name"] nick_name_new = data_map["name_show"] if priv_sets := data_map["priv_sets"]: priv_like = PrivLike(int(priv_sets.get("like", 1))) priv_reply = PrivReply(int(priv_sets.get("reply", 1))) else: priv_like = PrivLike.PUBLIC priv_reply = PrivReply.ALL return UserInfo_at(user_id, portrait, user_name, nick_name_new, priv_like, priv_reply) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_at) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class At: """ @信息 Attributes: text (str): 文本内容 fname (str): 所在贴吧名 tid (int): 所在主题帖id pid (int): 回复id user (UserInfo_at): 发布者的用户信息 author_id (int): 发布者的user_id is_comment (bool): 是否楼中楼 is_thread (bool): 是否主题帖 create_time (int): 创建时间 """ text: str = "" fname: str = "" tid: int = 0 pid: int = 0 user: UserInfo_at = dcs.field(default_factory=UserInfo_at) is_comment: bool = False is_thread: bool = False create_time: int = 0 @staticmethod def from_json(data_map: Mapping) -> At: text = data_map["content"] fname = data_map["fname"] tid = int(data_map["thread_id"]) pid = int(data_map["post_id"]) user = UserInfo_at.from_json(data_map["replyer"]) is_comment = bool(int(data_map["is_floor"])) is_thread = bool(int(data_map["is_first_post"])) create_time = int(data_map["time"]) return At(text, fname, tid, pid, user, is_comment, is_thread, create_time) def __eq__(self, obj: At) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @property def author_id(self) -> int: return self.user.user_id @dcs.dataclass class Ats(TbErrorExt, Containers[At]): """ @信息列表 Attributes: objs (list[At]): @信息列表 err (Exception | None): 捕获的异常 page (Page_at): 页信息 has_more (bool): 是否还有下一页 """ page: Page_at = dcs.field(default_factory=Page_at) @staticmethod def from_json(data_map: Mapping) -> Ats: objs = [At.from_json(m) for m in data_map.get("at_list", [])] page = Page_at.from_json(data_map["page"]) return Ats(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_bawu_blacklist/__init__.py ================================================ from ._api import parse_body, request from ._classdef import BawuBlacklistUser, BawuBlacklistUsers ================================================ FILE: src/aiotieba/api/get_bawu_blacklist/_api.py ================================================ import bs4 import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ._classdef import BawuBlacklistUsers def parse_body(body: bytes) -> BawuBlacklistUsers: soup = bs4.BeautifulSoup(body, "lxml") bawu_blacklist_users = BawuBlacklistUsers.from_xml(soup) return bawu_blacklist_users async def request(http_core: HttpCore, fname: str, pn: int) -> BawuBlacklistUsers: params = [ ("word", fname), ("pn", pn), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/bawu2/platform/listBlackUser"), params ) body = await http_core.net_core.send_request(request, read_bufsize=16 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_bawu_blacklist/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: import bs4 @dcs.dataclass class BawuBlacklistUser: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" @staticmethod def from_xml(data_tag: bs4.element.Tag) -> BawuBlacklistUser: user_info_item = data_tag.previous_sibling.input user_name = user_info_item["data-user-name"] user_id = int(user_info_item["data-user-id"]) portrait = data_tag.a["href"][14:-17] return BawuBlacklistUser(user_id, portrait, user_name) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: BawuBlacklistUser) -> 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 log_name(self) -> str: return str(self) @dcs.dataclass class Page_bwblacklist: """ 页信息 Attributes: current_page (int): 当前页码 total_page (int): 总页码 total_count (int): 总计数 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 total_page: int = 0 total_count: int = 0 has_more: bool = False has_prev: bool = False def from_xml(data_soup: bs4.BeautifulSoup) -> Page_bwblacklist: total_count_tag = data_soup.find("div", class_="breadcrumbs") total_count = int(total_count_tag.em.text) page_tag = data_soup.find("div", class_="tbui_pagination").find("li", class_="active") if page_tag is None: if total_count != 0: current_page = 1 total_page = 1 else: current_page = 0 total_page = 0 else: current_page = int(page_tag.text) total_page_item = page_tag.parent.next_sibling total_page = int(total_page_item.text[1:-1]) has_more = current_page < total_page has_prev = current_page > 1 return Page_bwblacklist(current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class BawuBlacklistUsers(TbErrorExt, Containers[BawuBlacklistUser]): """ 吧务黑名单列表 Attributes: objs (list[BawuBlacklistUser]): 吧务黑名单列表 err (Exception | None): 捕获的异常 page (Page_bwblacklist): 页信息 has_more (bool): 是否还有下一页 """ page: Page_bwblacklist = dcs.field(default_factory=Page_bwblacklist) @staticmethod def from_xml(data_soup: bs4.BeautifulSoup) -> BawuBlacklistUsers: objs = [BawuBlacklistUser.from_xml(t) for t in data_soup("td", class_="left_cell")] page = Page_bwblacklist.from_xml(data_soup) return BawuBlacklistUsers(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_bawu_info/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import BawuInfo, UserInfo_bawu ================================================ FILE: src/aiotieba/api/get_bawu_info/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import BawuInfo from .protobuf import GetBawuInfoReqIdl_pb2, GetBawuInfoResIdl_pb2 CMD = 301007 def pack_proto(fid: int) -> bytes: req_proto = GetBawuInfoReqIdl_pb2.GetBawuInfoReqIdl() req_proto.data.common._client_version = LATEST_VERSION req_proto.data.fid = fid return req_proto.SerializeToString() def parse_body(body: bytes) -> BawuInfo: res_proto = GetBawuInfoResIdl_pb2.GetBawuInfoResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data bawu_info = BawuInfo.from_proto(data_proto) return bawu_info async def request_http(http_core: HttpCore, fid: int) -> BawuInfo: data = pack_proto(fid) request = http_core.pack_proto_request( yarl.URL.build(scheme="http", host=APP_BASE_HOST, path="/c/f/forum/getBawuInfo", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, fid: int) -> BawuInfo: data = pack_proto(fid) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_bawu_info/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING if TYPE_CHECKING: from .._classdef import TypeMessage @dcs.dataclass class UserInfo_bawu: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 level (int): 等级 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" level: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_bawu: user_id = data_proto.user_id portrait = data_proto.portrait user_name = data_proto.user_name nick_name_new = data_proto.name_show level = data_proto.user_level return UserInfo_bawu(user_id, portrait, user_name, nick_name_new, level) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_bawu) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class BawuInfo: """ 吧务团队信息 Attributes: all (list[UserInfo_bawu]): 所有吧务 admin (list[UserInfo_bawu]): 大吧主 manager (list[UserInfo_bawu]): 小吧主 voice_editor (list[UserInfo_bawu]): 语音小编 image_editor (list[UserInfo_bawu]): 图片小编 video_editor (list[UserInfo_bawu]): 视频小编 broadcast_editor (list[UserInfo_bawu]): 广播小编 journal_chief_editor (list[UserInfo_bawu]): 吧刊主编 journal_editor (list[UserInfo_bawu]): 吧刊小编 profess_admin (list[UserInfo_bawu]): 职业吧主 fourth_admin (list[UserInfo_bawu]): 第四吧主 """ all: list[UserInfo_bawu] = dcs.field(default_factory=list, repr=False) admin: list[UserInfo_bawu] = dcs.field(default_factory=list) manager: list[UserInfo_bawu] = dcs.field(default_factory=list) voice_editor: list[UserInfo_bawu] = dcs.field(default_factory=list) image_editor: list[UserInfo_bawu] = dcs.field(default_factory=list) video_editor: list[UserInfo_bawu] = dcs.field(default_factory=list) broadcast_editor: list[UserInfo_bawu] = dcs.field(default_factory=list) journal_chief_editor: list[UserInfo_bawu] = dcs.field(default_factory=list) journal_editor: list[UserInfo_bawu] = dcs.field(default_factory=list) profess_admin: list[UserInfo_bawu] = dcs.field(default_factory=list) fourth_admin: list[UserInfo_bawu] = dcs.field(default_factory=list) @staticmethod def from_proto(data_proto: TypeMessage) -> BawuInfo: all_ = [] r_protos = data_proto.bawu_team_info.bawu_team_list _dict = {r_proto.role_name: [UserInfo_bawu.from_proto(p) for p in r_proto.role_info] for r_proto in r_protos} def extract(role_name: str) -> list[UserInfo_bawu]: if users := _dict.get(role_name): all_.extend(users) else: users = [] return users admin = extract("吧主") manager = extract("小吧主") voice_editor = extract("语音小编") image_editor = extract("图片小编") video_editor = extract("视频小编") broadcast_editor = extract("广播小编") journal_chief_editor = extract("吧刊主编") journal_editor = extract("吧刊小编") profess_admin = extract("职业吧主") fourth_admin = extract("第四吧主") return BawuInfo( all_, admin, manager, voice_editor, image_editor, video_editor, broadcast_editor, journal_chief_editor, journal_editor, profess_admin, fourth_admin, ) ================================================ FILE: src/aiotieba/api/get_bawu_info/protobuf/GetBawuInfoReqIdl.proto ================================================ // tbclient.GetBawuInfo.GetBawuInfoReqIdl syntax = "proto3"; import "CommonReq.proto"; message GetBawuInfoReqIdl { message DataReq { CommonReq common = 1; uint64 fid = 2; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_bawu_info/protobuf/GetBawuInfoReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x17GetBawuInfoReqIdl.proto\x1a\x0f\x43ommonReq.proto"q\n\x11GetBawuInfoReqIdl\x12(\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1a.GetBawuInfoReqIdl.DataReq\x1a\x32\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\n.CommonReq\x12\x0b\n\x03\x66id\x18\x02 \x01(\x04\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetBawuInfoReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETBAWUINFOREQIDL"]._serialized_start = 44 _globals["_GETBAWUINFOREQIDL"]._serialized_end = 157 _globals["_GETBAWUINFOREQIDL_DATAREQ"]._serialized_start = 107 _globals["_GETBAWUINFOREQIDL_DATAREQ"]._serialized_end = 157 ================================================ FILE: src/aiotieba/api/get_bawu_info/protobuf/GetBawuInfoResIdl.proto ================================================ // tbclient.GetBawuInfo.GetBawuInfoResIdl syntax = "proto3"; import "Error.proto"; message GetBawuInfoResIdl { message DataRes { message BawuTeam { int32 total_num = 1; message BawuRoleDes { string role_name = 1; message BawuRoleInfoPub { int64 user_id = 2; string portrait = 5; int32 user_level = 6; string user_name = 8; string name_show = 9; } repeated BawuRoleInfoPub role_info = 2; } repeated BawuRoleDes bawu_team_list = 2; } BawuTeam bawu_team_info = 1; } DataRes data = 1; Error error = 2; } ================================================ FILE: src/aiotieba/api/get_bawu_info/protobuf/GetBawuInfoResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x17GetBawuInfoResIdl.proto\x1a\x0b\x45rror.proto"\xed\x03\n\x11GetBawuInfoResIdl\x12(\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1a.GetBawuInfoResIdl.DataRes\x12\x15\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x06.Error\x1a\x96\x03\n\x07\x44\x61taRes\x12;\n\x0e\x62\x61wu_team_info\x18\x01 \x01(\x0b\x32#.GetBawuInfoResIdl.DataRes.BawuTeam\x1a\xcd\x02\n\x08\x42\x61wuTeam\x12\x11\n\ttotal_num\x18\x01 \x01(\x05\x12G\n\x0e\x62\x61wu_team_list\x18\x02 \x03(\x0b\x32/.GetBawuInfoResIdl.DataRes.BawuTeam.BawuRoleDes\x1a\xe4\x01\n\x0b\x42\x61wuRoleDes\x12\x11\n\trole_name\x18\x01 \x01(\t\x12R\n\trole_info\x18\x02 \x03(\x0b\x32?.GetBawuInfoResIdl.DataRes.BawuTeam.BawuRoleDes.BawuRoleInfoPub\x1an\n\x0f\x42\x61wuRoleInfoPub\x12\x0f\n\x07user_id\x18\x02 \x01(\x03\x12\x10\n\x08portrait\x18\x05 \x01(\t\x12\x12\n\nuser_level\x18\x06 \x01(\x05\x12\x11\n\tuser_name\x18\x08 \x01(\t\x12\x11\n\tname_show\x18\t \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetBawuInfoResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETBAWUINFORESIDL"]._serialized_start = 41 _globals["_GETBAWUINFORESIDL"]._serialized_end = 534 _globals["_GETBAWUINFORESIDL_DATARES"]._serialized_start = 128 _globals["_GETBAWUINFORESIDL_DATARES"]._serialized_end = 534 _globals["_GETBAWUINFORESIDL_DATARES_BAWUTEAM"]._serialized_start = 201 _globals["_GETBAWUINFORESIDL_DATARES_BAWUTEAM"]._serialized_end = 534 _globals["_GETBAWUINFORESIDL_DATARES_BAWUTEAM_BAWUROLEDES"]._serialized_start = 306 _globals["_GETBAWUINFORESIDL_DATARES_BAWUTEAM_BAWUROLEDES"]._serialized_end = 534 _globals["_GETBAWUINFORESIDL_DATARES_BAWUTEAM_BAWUROLEDES_BAWUROLEINFOPUB"]._serialized_start = 424 _globals["_GETBAWUINFORESIDL_DATARES_BAWUTEAM_BAWUROLEDES_BAWUROLEINFOPUB"]._serialized_end = 534 ================================================ FILE: src/aiotieba/api/get_bawu_perm/__init__.py ================================================ from ._api import parse_body, request from ._classdef import BawuPerm ================================================ FILE: src/aiotieba/api/get_bawu_perm/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import BawuPerm def parse_body(body: bytes) -> BawuPerm: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) data_map = res_json["data"] perm = BawuPerm.from_json(data_map) return perm async def request(http_core: HttpCore, fid: int, portrait: str) -> BawuPerm: params = [ ("forum_id", fid), ("portrait", portrait), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/getAuthToolPerm"), params ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_bawu_perm/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...enums import BawuPermType from ...exception import TbErrorExt if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class BawuPerm(TbErrorExt): """ 吧务已分配的权限 Attributes: err (Exception | None): 捕获的异常 perms (BawuPermType): 吧务已分配的权限 """ perms: BawuPermType = BawuPermType.NULL def from_json(data_map: Mapping) -> BawuPerm: perms = BawuPermType.NULL for cate in ["category_user", "category_thread"]: perm_setting = data_map["perm_setting"] for unblock_perm_dict in perm_setting[cate]: if not unblock_perm_dict["switch"]: continue perm_idx: int = unblock_perm_dict["perm"] - 2 perm = [ BawuPermType.RECOVER_APPEAL, BawuPermType.RECOVER, BawuPermType.UNBLOCK, BawuPermType.UNBLOCK_APPEAL, ][perm_idx] perms |= perm return BawuPerm(perms) ================================================ FILE: src/aiotieba/api/get_bawu_postlogs/__init__.py ================================================ from ._api import parse_body, request from ._classdef import Postlog, Postlogs ================================================ FILE: src/aiotieba/api/get_bawu_postlogs/_api.py ================================================ from __future__ import annotations import time from typing import TYPE_CHECKING from urllib.parse import quote import bs4 import yarl from ...const import WEB_BASE_HOST from ...enums import BawuSearchType from ._classdef import Postlogs if TYPE_CHECKING: import datetime from ...core import HttpCore def parse_body(body: bytes) -> Postlogs: soup = bs4.BeautifulSoup(body, "lxml") bawu_postlogs = Postlogs.from_xml(soup) return bawu_postlogs async def request( http_core: HttpCore, fname: str, pn: int, search_value: str, search_type: BawuSearchType, start_dt: datetime.datetime | None, end_dt: datetime.datetime | None, op_type: int, ) -> Postlogs: params = [ ("word", fname), ("pn", pn), ("ie", "utf-8"), ] if op_type: params.append(("op_type", op_type)) if search_value: if search_type == BawuSearchType.USER: search_value = quote(search_value) extend_params = [ ("svalue", search_value), ("stype", "post_uname"), ] else: extend_params = [ ("svalue", search_value), ("stype", "op_uname"), ] params += extend_params if start_dt: begin = int(start_dt.timestamp()) if end_dt is None: end = int(time.time()) else: end = int(end_dt.timestamp()) extend_params = [ ("end", end), ("begin", begin), ] params += extend_params request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/bawu2/platform/listPostLog"), params ) body = await http_core.net_core.send_request(request, read_bufsize=32 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_bawu_postlogs/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from datetime import datetime from typing import TYPE_CHECKING from ...exception import TbErrorExt from ...helper import default_datetime from .._classdef import Containers from .._classdef.contents import _IMAGEHASH_EXP if TYPE_CHECKING: import bs4 @dcs.dataclass class Media_postlog: """ 媒体信息 Attributes: src (str): 小图链接 origin_src (str): 原图链接 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) origin_src: str = dcs.field(default="", repr=False) hash: str = "" @staticmethod def from_xml(data_tag: bs4.element.Tag) -> Media_postlog: if img_item := data_tag.img: src = img_item["original"] hash_ = _IMAGEHASH_EXP.search(src).group(1) else: src = "" hash_ = "" origin_src = data_tag["href"] return Media_postlog(src, origin_src, hash_) @dcs.dataclass class Postlog: """ 吧务帖子管理日志 Attributes: text (str): 文本内容 title (str): 所在主题帖标题 medias (list[Media_postlog]): 媒体列表 tid (int): 所在主题帖id pid (int): 回复id op_type (str): 操作类型 post_portrait (str): 发帖用户的portrait post_time (datetime): 发帖时间 不含年份 op_user_name (str): 操作人用户名 op_time (datetime): 操作时间 """ text: str = "" title: str = "" medias: list[Media_postlog] = dcs.field(default_factory=list) tid: int = 0 pid: int = 0 op_type: str = "" post_portrait: str = "" post_time: datetime = dcs.field(default_factory=default_datetime) op_user_name: str = "" op_time: datetime = dcs.field(default_factory=default_datetime) @staticmethod def from_xml(data_tag: bs4.element.Tag) -> Postlog: left_cell_item = data_tag.td post_meta_item = left_cell_item.find("div", class_="post_meta") post_user_item = post_meta_item.div post_portrait = post_user_item.a["href"][14:-17] post_time_item = post_meta_item.time post_time_str = post_time_item.text post_time_month = int(post_time_str[:2]) post_time_day = int(post_time_str[3:5]) post_time_hour = int(post_time_str[7:9]) post_time_minute = int(post_time_str[10:]) post_time = datetime(1904, post_time_month, post_time_day, post_time_hour, post_time_minute) post_content_item = post_meta_item.next_sibling title_item = post_content_item.h1.a url: str = title_item["href"] tid = int(url[3 : url.find("?")]) pid = int(url[url.rfind("#") + 1 :]) title: str = title_item["title"] text_item = post_content_item.div text = text_item.string[12:] if pid == tid or not title.startswith("回复:"): # is thread pid = 0 text = f"{title}\n{text}" else: title = title.removeprefix("回复:") if media_list_item := text_item.next_sibling: medias = [Media_postlog.from_xml(tag) for tag in media_list_item.find_all("a")] else: medias = [] op_type_item = left_cell_item.next_sibling op_type = op_type_item.string op_user_name_item = op_type_item.next_sibling op_user_name = op_user_name_item.string op_time_item = op_user_name_item.next_sibling op_time = datetime.strptime(op_time_item.text, "%Y-%m-%d%H:%M") return Postlog(text, title, medias, tid, pid, op_type, post_portrait, post_time, op_user_name, op_time) @dcs.dataclass class Page_postlog: """ 页信息 Attributes: current_page (int): 当前页码 total_page (int): 总页码 total_count (int): 总计数 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 total_page: int = 0 total_count: int = 0 has_more: bool = False has_prev: bool = False @staticmethod def from_xml(data_soup: bs4.BeautifulSoup) -> Page_postlog: total_count_tag = data_soup.find("div", class_="breadcrumbs") total_count = int(total_count_tag.em.text) page_tag = data_soup.find("div", class_="tbui_pagination").find("li", class_="active") if page_tag is None: if total_count != 0: current_page = 1 total_page = 1 else: current_page = 0 total_page = 0 else: current_page = int(page_tag.text) total_page_item = page_tag.parent.next_sibling total_page = int(total_page_item.text[1:-1]) has_more = current_page < total_page has_prev = current_page > 1 return Page_postlog(current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class Postlogs(TbErrorExt, Containers[Postlog]): """ 吧务帖子管理日志表 Attributes: objs (list[Postlog]): 吧务帖子管理日志表 err (Exception | None): 捕获的异常 page (Page_postlog): 页信息 has_more (bool): 是否还有下一页 """ page: Page_postlog = dcs.field(default_factory=Page_postlog) @staticmethod def from_xml(data_soup: bs4.BeautifulSoup) -> Postlogs: objs = [Postlog.from_xml(t) for t in data_soup.find("tbody").find_all("tr")] page = Page_postlog.from_xml(data_soup) return Postlogs(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_bawu_userlogs/__init__.py ================================================ from ._api import parse_body, request from ._classdef import Userlog, Userlogs ================================================ FILE: src/aiotieba/api/get_bawu_userlogs/_api.py ================================================ from __future__ import annotations import time from typing import TYPE_CHECKING from urllib.parse import quote import bs4 import yarl from ...const import WEB_BASE_HOST from ...enums import BawuSearchType from ._classdef import Userlogs if TYPE_CHECKING: import datetime from ...core import HttpCore def parse_body(body: bytes) -> Userlogs: soup = bs4.BeautifulSoup(body, "lxml") bawu_userlogs = Userlogs.from_xml(soup) return bawu_userlogs async def request( http_core: HttpCore, fname: str, pn: int, search_value: str, search_type: BawuSearchType, start_dt: datetime.datetime | None, end_dt: datetime.datetime | None, op_type: int, ) -> Userlogs: params = [ ("word", fname), ("pn", pn), ("ie", "utf-8"), ] if op_type: params.append(("op_type", op_type)) if search_value: if search_type == BawuSearchType.USER: search_value = quote(search_value) extend_params = [ ("svalue", search_value), ("stype", "post_uname"), ] else: extend_params = [ ("svalue", search_value), ("stype", "op_uname"), ] params += extend_params if start_dt: begin = int(start_dt.timestamp()) if end_dt is None: end = int(time.time()) else: end = int(end_dt.timestamp()) extend_params = [ ("end", end), ("begin", begin), ] params += extend_params request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/bawu2/platform/listUserLog"), params ) body = await http_core.net_core.send_request(request, read_bufsize=16 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_bawu_userlogs/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from datetime import datetime from typing import TYPE_CHECKING from ...exception import TbErrorExt from ...helper import default_datetime from .._classdef import Containers if TYPE_CHECKING: import bs4 @dcs.dataclass class Userlog: """ 吧务用户管理日志 Attributes: op_type (str): 操作类型 op_duration (int): 操作作用时长 user_portrait (str): 被操作用户的portrait op_user_name (str): 操作人用户名 op_time (datetime.datetime): 操作时间 """ op_type: str = "" op_duration: int = 0 user_portrait: str = "" op_user_name: str = "" op_time: datetime = dcs.field(default_factory=default_datetime) @staticmethod def from_xml(data_tag: bs4.element.Tag) -> Userlog: left_cell_item = data_tag.td post_user_item = left_cell_item.a user_portrait = post_user_item["href"][14:-17] op_type_item = left_cell_item.next_sibling.next_sibling op_type = op_type_item.string op_duration_item = op_type_item.next_sibling op_duration = op_duration_item.string.replace(" ", "") op_duration = 0 if "天" not in op_duration else int(op_duration[:-1]) op_user_name_item = op_duration_item.next_sibling op_user_name = op_user_name_item.string op_time_item = op_user_name_item.next_sibling op_time = datetime.strptime(op_time_item.text, "%Y-%m-%d %H:%M") return Userlog(op_type, op_duration, user_portrait, op_user_name, op_time) @dcs.dataclass class Page_userlog: """ 页信息 Attributes: current_page (int): 当前页码 total_page (int): 总页码 total_count (int): 总计数 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 total_page: int = 0 total_count: int = 0 has_more: bool = False has_prev: bool = False @staticmethod def from_xml(data_soup: bs4.BeautifulSoup) -> Page_userlog: total_count_tag = data_soup.find("div", class_="breadcrumbs") total_count = int(total_count_tag.em.text) page_tag = data_soup.find("div", class_="tbui_pagination").find("li", class_="active") if page_tag is None: if total_count != 0: current_page = 1 total_page = 1 else: current_page = 0 total_page = 0 else: current_page = int(page_tag.text) total_page_item = page_tag.parent.next_sibling total_page = int(total_page_item.text[1:-1]) has_more = current_page < total_page has_prev = current_page > 1 return Page_userlog(current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class Userlogs(TbErrorExt, Containers[Userlog]): """ 吧务用户管理日志表 Attributes: objs (list[Postlog]): 吧务用户管理日志表 err (Exception | None): 捕获的异常 page (Page_userlog): 页信息 has_more (bool): 是否还有下一页 """ page: Page_userlog = dcs.field(default_factory=Page_userlog) @staticmethod def from_xml(data_soup: bs4.BeautifulSoup) -> Userlogs: objs = [Userlog.from_xml(t) for t in data_soup.find("tbody").find_all("tr")] page = Page_userlog.from_xml(data_soup) return Userlogs(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_blacklist/__init__.py ================================================ from ._api import parse_body, request from ._classdef import BlacklistUser, BlacklistUsers ================================================ FILE: src/aiotieba/api/get_blacklist/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import BlacklistUsers def parse_body(body: bytes) -> BlacklistUsers: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) blacklist_users = BlacklistUsers.from_json(res_json) return blacklist_users async def request(http_core: HttpCore) -> BlacklistUsers: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/u/user/userBlackPage"), data ) body = await http_core.net_core.send_request(request, read_bufsize=32 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_blacklist/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...enums import BlacklistType from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class BlacklistUser: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 btype (BlacklistType): 黑名单类型 FOLLOW禁止关注 INTERACT禁止互动 CHAT禁止私信 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" btype: BlacklistType = BlacklistType.NULL @staticmethod def from_json(data_map: Mapping) -> BlacklistUser: user_id = int(data_map["uid"]) portrait = data_map["portrait"] if "?" in portrait: portrait = portrait[:-13] user_name = data_map["user_name"] nick_name_new = data_map["name_show"] btype = BlacklistType.NULL perm: dict[str, str] = data_map["perm_list"] if int(perm["follow"]): btype |= BlacklistType.FOLLOW if int(perm["chat"]): btype |= BlacklistType.CHAT if int(perm["interact"]): btype |= BlacklistType.INTERACT return BlacklistUser(user_id, portrait, user_name, nick_name_new, btype) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: BlacklistUser) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class BlacklistUsers(TbErrorExt, Containers[BlacklistUser]): """ 新版用户黑名单列表 Attributes: objs (list[BlacklistUser]): 新版用户黑名单列表 err (Exception | None): 捕获的异常 """ @staticmethod def from_json(data_map: Mapping) -> BlacklistUsers: objs = [BlacklistUser.from_json(m) for m in data_map.get("user_perm_list", [])] return BlacklistUsers(objs) ================================================ FILE: src/aiotieba/api/get_blacklist_old/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import BlacklistOldUser, BlacklistOldUsers ================================================ FILE: src/aiotieba/api/get_blacklist_old/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import Account, HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import BlacklistOldUsers from .protobuf import UserMuteQueryReqIdl_pb2, UserMuteQueryResIdl_pb2 CMD = 303028 def pack_proto(account: Account, pn: int, rn: int) -> bytes: req_proto = UserMuteQueryReqIdl_pb2.UserMuteQueryReqIdl() req_proto.data.common.BDUSS = account.BDUSS req_proto.data.common._client_version = LATEST_VERSION req_proto.data.pn = pn req_proto.data.rn = rn return req_proto.SerializeToString() def parse_body(proto: bytes) -> BlacklistOldUsers: res_proto = UserMuteQueryResIdl_pb2.UserMuteQueryResIdl() res_proto.ParseFromString(proto) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data blacklist_users = BlacklistOldUsers.from_proto(data_proto) return blacklist_users async def request_http(http_core: HttpCore, pn: int, rn: int) -> BlacklistOldUsers: data = pack_proto(http_core.account, pn, rn) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/u/user/userMuteQuery", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, pn: int, rn: int) -> BlacklistOldUsers: data = pack_proto(ws_core.account, pn, rn) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_blacklist_old/_classdef.py ================================================ 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 BlacklistOldUser: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_old (str): 旧版昵称 until_time (int): 解禁时间 10位时间戳 以秒为单位 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_old: str = "" until_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> BlacklistOldUser: user_id = data_proto.user_id portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.user_name nick_name_old = data_proto.name_show until_time = data_proto.mute_time return BlacklistOldUser(user_id, portrait, user_name, nick_name_old, until_time) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: BlacklistOldUser) -> 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 @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 Page_blacklist: """ 页信息 Attributes: current_page (int): 当前页码 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 has_more: bool = False has_prev: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> Page_blacklist: current_page = data_proto.current_page has_more = bool(data_proto.has_more) has_prev = bool(data_proto.has_prev) return Page_blacklist(current_page, has_more, has_prev) @dcs.dataclass class BlacklistOldUsers(TbErrorExt, Containers[BlacklistOldUser]): """ 旧版用户黑名单列表 Attributes: objs (list[BlacklistOldUser]): 旧版用户黑名单列表 err (Exception | None): 捕获的异常 page (Page_blacklist): 页信息 has_more (bool): 是否还有下一页 """ page: Page_blacklist = dcs.field(default_factory=Page_blacklist) @staticmethod def from_proto(data_proto: TypeMessage) -> BlacklistOldUsers: objs = [BlacklistOldUser.from_proto(p) for p in data_proto.mute_user] page = Page_blacklist.from_proto(data_proto.page) return BlacklistOldUsers(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_blacklist_old/protobuf/UserMuteQueryReqIdl.proto ================================================ // tbclient.UserMuteQuery.UserMuteQueryReqIdl syntax = "proto3"; import "CommonReq.proto"; message UserMuteQueryReqIdl { message DataReq { CommonReq common = 2; uint32 pn = 4; uint32 rn = 5; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_blacklist_old/protobuf/UserMuteQueryReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x19UserMuteQueryReqIdl.proto\x1a\x0f\x43ommonReq.proto"\x80\x01\n\x13UserMuteQueryReqIdl\x12*\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1c.UserMuteQueryReqIdl.DataReq\x1a=\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x02 \x01(\x0b\x32\n.CommonReq\x12\n\n\x02pn\x18\x04 \x01(\r\x12\n\n\x02rn\x18\x05 \x01(\rb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "UserMuteQueryReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_USERMUTEQUERYREQIDL"]._serialized_start = 47 _globals["_USERMUTEQUERYREQIDL"]._serialized_end = 175 _globals["_USERMUTEQUERYREQIDL_DATAREQ"]._serialized_start = 114 _globals["_USERMUTEQUERYREQIDL_DATAREQ"]._serialized_end = 175 ================================================ FILE: src/aiotieba/api/get_blacklist_old/protobuf/UserMuteQueryResIdl.proto ================================================ // tbclient.UserMuteQuery.UserMuteQueryResIdl syntax = "proto3"; import "Error.proto"; import "Page.proto"; message UserMuteQueryResIdl { message DataRes { message MuteUser { int64 user_id = 1; string user_name = 2; int32 mute_time = 3; string portrait = 4; string name_show = 5; } repeated MuteUser mute_user = 1; Page page = 2; } DataRes data = 1; Error error = 2; } ================================================ FILE: src/aiotieba/api/get_blacklist_old/protobuf/UserMuteQueryResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import Page_pb2 as Page__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x19UserMuteQueryResIdl.proto\x1a\x0b\x45rror.proto\x1a\nPage.proto"\x9b\x02\n\x13UserMuteQueryResIdl\x12*\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1c.UserMuteQueryResIdl.DataRes\x12\x15\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x06.Error\x1a\xc0\x01\n\x07\x44\x61taRes\x12\x38\n\tmute_user\x18\x01 \x03(\x0b\x32%.UserMuteQueryResIdl.DataRes.MuteUser\x12\x13\n\x04page\x18\x02 \x01(\x0b\x32\x05.Page\x1a\x66\n\x08MuteUser\x12\x0f\n\x07user_id\x18\x01 \x01(\x03\x12\x11\n\tuser_name\x18\x02 \x01(\t\x12\x11\n\tmute_time\x18\x03 \x01(\x05\x12\x10\n\x08portrait\x18\x04 \x01(\t\x12\x11\n\tname_show\x18\x05 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "UserMuteQueryResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_USERMUTEQUERYRESIDL"]._serialized_start = 55 _globals["_USERMUTEQUERYRESIDL"]._serialized_end = 338 _globals["_USERMUTEQUERYRESIDL_DATARES"]._serialized_start = 146 _globals["_USERMUTEQUERYRESIDL_DATARES"]._serialized_end = 338 _globals["_USERMUTEQUERYRESIDL_DATARES_MUTEUSER"]._serialized_start = 236 _globals["_USERMUTEQUERYRESIDL_DATARES_MUTEUSER"]._serialized_end = 338 ================================================ FILE: src/aiotieba/api/get_blocks/__init__.py ================================================ from ._api import parse_body, request from ._classdef import Block, Blocks ================================================ FILE: src/aiotieba/api/get_blocks/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import Blocks def parse_body(body: bytes) -> Blocks: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) blocks = Blocks.from_json(res_json) return blocks async def request(http_core: HttpCore, fid: int, name: str, pn: int) -> Blocks: params = [ ("fn", "-"), ("fid", fid), ("word", name), ("is_ajax", 1), ("pn", pn), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/bawublock"), params ) body = await http_core.net_core.send_request(request, read_bufsize=32 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_blocks/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING import bs4 from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class Block: """ 待解封用户信息 Attributes: user_id (int): user_id user_name (str): 用户名 nick_name_old (str): 旧版昵称 day (int): 封禁天数 """ user_id: int = 0 user_name: str = "" nick_name_old: str = "" day: int = 0 @staticmethod def from_xml(data_tag: bs4.element.Tag) -> Block: id_tag = data_tag.a user_id = int(id_tag["attr-uid"]) user_name = id_tag["attr-un"] nick_name_old = id_tag["attr-nn"] day = int(id_tag["attr-blockday"]) return Block(user_id, user_name, nick_name_old, day) @dcs.dataclass class Page_block: """ 页信息 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_json(data_map: Mapping) -> Page_block: page_size = data_map["size"] current_page = data_map["pn"] total_page = data_map["total_page"] total_count = data_map["total_count"] has_more = data_map["have_next"] has_prev = current_page > 1 return Page_block(page_size, current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class Blocks(TbErrorExt, Containers[Block]): """ 待恢复帖子列表 Attributes: objs (list[Block]): 待恢复帖子列表 err (Exception | None): 捕获的异常 page (Page_block): 页信息 has_more (bool): 是否还有下一页 """ page: Page_block = dcs.field(default_factory=Page_block) def from_json(data_map: Mapping) -> Blocks: data_soup = bs4.BeautifulSoup(data_map["data"]["content"], "lxml") objs = [Block.from_xml(t) for t in data_soup("li")] page = Page_block.from_json(data_map["data"]["page"]) return Blocks(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_cid/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/get_cid/_api.py ================================================ from __future__ import annotations from typing import TYPE_CHECKING import yarl from ...const import APP_BASE_HOST from ...exception import TiebaServerError from ...helper import parse_json if TYPE_CHECKING: from ...core import HttpCore def parse_body(body: bytes) -> list[dict[str, str | int]]: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) cates = res_json["cates"] return cates async def request(http_core: HttpCore, fname: str) -> list[dict[str, str | int]]: data = [ ("BDUSS", http_core.account.BDUSS), ("word", fname), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/goodlist"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_comments/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import Comment, Comments, Post_c, Thread_c, UserInfo_c, UserInfo_cp, UserInfo_ct ================================================ FILE: src/aiotieba/api/get_comments/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, STABLE_VERSION from ...core import HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import Comments from .protobuf import PbFloorReqIdl_pb2, PbFloorResIdl_pb2 CMD = 302002 def pack_proto(tid: int, pid: int, pn: int, is_comment: bool) -> bytes: req_proto = PbFloorReqIdl_pb2.PbFloorReqIdl() req_proto.data.common._client_type = 2 req_proto.data.common._client_version = STABLE_VERSION req_proto.data.kz = tid if is_comment: req_proto.data.spid = pid else: req_proto.data.pid = pid req_proto.data.pn = pn return req_proto.SerializeToString() def parse_body(body: bytes) -> Comments: res_proto = PbFloorResIdl_pb2.PbFloorResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data comments = Comments.from_proto(data_proto) return comments async def request_http(http_core: HttpCore, tid: int, pid: int, pn: int, is_comment: bool) -> Comments: data = pack_proto(tid, pid, pn, is_comment) request = http_core.pack_proto_request( yarl.URL.build(scheme="http", host=APP_BASE_HOST, path="/c/f/pb/floor", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, tid: int, pid: int, pn: int, is_comment: bool) -> Comments: data = pack_proto(tid, pid, pn, is_comment) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_comments/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from ...enums import Gender, PrivLike, PrivReply, ThreadType from ...exception import TbErrorExt from ...helper import deprecated from ...logging import get_logger as LOG from .._classdef import Containers, TypeMessage from .._classdef.contents import ( _IMAGEHASH_EXP, FragAt, FragEmoji, FragLink, FragText, FragTiebaPlus, FragUnknown, FragVoice, TypeFragment, TypeFragText, ) FragText_c = FragText_cp = FragText FragEmoji_c = FragEmoji_cp = FragEmoji FragAt_c = FragAt_cp = FragAt FragLink_c = FragLink_cp = FragLink FragTiebaPlus_c = FragTiebaPlus_cp = FragTiebaPlus FragVoice_c = FragVoice_cp = FragVoice @dcs.dataclass class Contents_c(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 emojis (list[FragEmoji_c]): 表情碎片列表 ats (list[FragAt_c]): @碎片列表 links (list[FragLink_c]): 链接碎片列表 tiebapluses (list[FragTiebaPlus_c]): 贴吧plus碎片列表 voice (FragVoice_c): 音频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) emojis: list[FragEmoji_c] = dcs.field(default_factory=list, repr=False) ats: list[FragAt_c] = dcs.field(default_factory=list, repr=False) links: list[FragLink_c] = dcs.field(default_factory=list, repr=False) tiebapluses: list[FragTiebaPlus_c] = dcs.field(default_factory=list, repr=False) voice: FragVoice_c = dcs.field(default_factory=FragVoice_c, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_c: content_protos = data_proto.content texts = [] emojis = [] ats = [] links = [] tiebapluses = [] voice = FragVoice_c() def _frags(): for proto in content_protos: _type = proto.type # 0纯文本 9电话号 18话题 27百科词条 if _type in [0, 9, 18, 27]: frag = FragText_c.from_proto(proto) texts.append(frag) yield frag # 11:tid=5047676428 elif _type in [2, 11]: frag = FragEmoji_c.from_proto(proto) emojis.append(frag) yield frag elif _type == 4: frag = FragAt_c.from_proto(proto) ats.append(frag) texts.append(frag) yield frag elif _type == 1: frag = FragLink_c.from_proto(proto) links.append(frag) texts.append(frag) yield frag elif _type == 10: # voice frag = FragVoice_c.from_proto(proto) nonlocal voice voice = frag yield frag # 35|36:tid=7769728331 / 37:tid=7760184147 elif _type in [35, 36, 37]: frag = FragTiebaPlus_c.from_proto(proto) tiebapluses.append(frag) texts.append(frag) yield frag # outdated tiebaplus elif _type == 34: continue else: yield FragUnknown.from_proto(proto) objs = list(_frags()) return Contents_c(objs, texts, emojis, ats, links, tiebapluses, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class UserInfo_c: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 level (int): 等级 gender (Gender): 性别 icons (list[str]): 印记信息 is_bawu (bool): 是否吧务 is_vip (bool): 是否超级会员 is_god (bool): 是否大神 priv_like (PrivLike): 关注吧列表的公开状态 priv_reply (PrivReply): 帖子评论权限 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" level: int = 0 gender: Gender = Gender.UNKNOWN icons: list[str] = dcs.field(default_factory=list) is_bawu: bool = False is_vip: bool = False is_god: bool = False priv_like: PrivLike = PrivLike.PUBLIC priv_reply: PrivReply = PrivReply.ALL @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_c: user_id = data_proto.id portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.name nick_name_new = data_proto.name_show level = data_proto.level_id gender = Gender(data_proto.gender) icons = [name for i in data_proto.iconinfo if (name := i.name)] is_bawu = bool(data_proto.is_bawu) is_vip = bool(data_proto.new_tshow_icon) is_god = bool(data_proto.new_god_data.status) priv_like = PrivLike(priv_like) if (priv_like := data_proto.priv_sets.like) else PrivLike.PUBLIC priv_reply = PrivReply(priv_reply) if (priv_reply := data_proto.priv_sets.reply) else PrivReply.ALL return UserInfo_c( user_id, portrait, user_name, nick_name_new, level, gender, icons, is_bawu, is_vip, is_god, priv_like, priv_reply, ) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_c) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class Comment: """ 楼中楼信息 Attributes: text (str): 文本内容 contents (Contents_c): 正文内容碎片列表 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 所在主题帖id ppid (int): 所在楼层id pid (int): 楼中楼id user (UserInfo_c): 发布者的用户信息 author_id (int): 发布者的user_id reply_to_id (int): 被回复者的user_id floor (int): 所在楼层数 agree (int): 点赞数 disagree (int): 点踩数 create_time (int): 创建时间 10位时间戳 以秒为单位 is_thread_author (bool): 是否楼主 """ contents: Contents_c = dcs.field(default_factory=Contents_c) fid: int = 0 fname: str = "" tid: int = 0 ppid: int = 0 pid: int = 0 user: UserInfo_c = dcs.field(default_factory=UserInfo_c) reply_to_id: int = 0 floor: int = 0 agree: int = 0 disagree: int = 0 create_time: int = 0 is_thread_author: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> None: contents = Contents_c.from_proto(data_proto) reply_to_id = 0 if contents: first_frag = contents[0] if ( isinstance(first_frag, FragText_c) and first_frag.text == "回复 " and (reply_to_id := data_proto.content[1].uid) ): reply_to_id = reply_to_id if isinstance(contents[1], FragAt_c): del contents.ats[0] contents.objs = contents.objs[2:] contents.texts = contents.texts[2:] if contents.texts: first_text_frag = contents.texts[0] first_text_frag.text = first_text_frag.text.removeprefix(" :") pid = data_proto.id user = UserInfo_c.from_proto(data_proto.author) agree = data_proto.agree.agree_num disagree = data_proto.agree.disagree_num create_time = data_proto.time return Comment(contents, 0, "", 0, 0, pid, user, reply_to_id, 0, agree, disagree, create_time, False) def __eq__(self, obj: Comment) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @property def text(self) -> str: return self.contents.text @property def author_id(self) -> int: return self.user.user_id @dcs.dataclass class Page_c: """ 页信息 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_proto(data_proto: TypeMessage) -> Page_c: 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 = current_page < total_page has_prev = current_page > 1 return Page_c(page_size, current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class Forum_c: """ 吧信息 Attributes: fid (int): 贴吧id fname (str): 贴吧名 category (str): 一级分类 subcategory (str): 二级分类 """ fid: int = 0 fname: str = "" category: str = "" subcategory: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> Forum_c: fid = data_proto.id fname = data_proto.name category = data_proto.first_class subcategory = data_proto.second_class return Forum_c(fid, fname, category, subcategory) @dcs.dataclass class UserInfo_ct: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 level (int): 等级 is_god (bool): 是否大神 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" level: int = 0 is_god: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_ct: user_id = data_proto.id portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.name nick_name_new = data_proto.name_show level = data_proto.level_id is_god = bool(data_proto.new_god_data.status) return UserInfo_ct(user_id, portrait, user_name, nick_name_new, level, is_god) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_ct) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class Thread_c: """ 主题帖信息 Attributes: title (str): 标题内容 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 主题帖tid user (UserInfo_ct): 发布者的用户信息 author_id (int): 发布者的user_id type (ThreadType): 帖子类型 is_help (bool): 是否为求助帖 reply_num (int): 回复数 """ title: str = "" fid: int = 0 fname: str = "" tid: int = 0 user: UserInfo_ct = dcs.field(default_factory=UserInfo_ct) type: ThreadType = ThreadType.UNKNOWN reply_num: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> Thread_c: title = data_proto.title tid = data_proto.id user = UserInfo_ct.from_proto(data_proto.author) type_ = ThreadType(data_proto.thread_type) if type_ == ThreadType.UNKNOWN: LOG().debug("Unknown thread type. tid=%d, type=%s", tid, data_proto.thread_type) reply_num = data_proto.reply_num return Thread_c(title, 0, "", tid, user, type_, reply_num) def __eq__(self, obj: Thread_c) -> bool: return self.tid == obj.tid def __hash__(self) -> int: return self.tid @property def author_id(self) -> int: return self.user.user_id @property @deprecated("使用 thread.type == ThreadType.HELP 作为替代") def is_help(self) -> bool: return self.type == ThreadType.HELP @dcs.dataclass class FragImage_cp: """ 图像碎片 Attributes: src (str): 小图链接 宽720px 一定是静态图 big_src (str): 大图链接 宽960px origin_src (str): 原图链接 origin_size (int): 原图大小 show_width (int): 图像在客户端预览显示的宽度 show_height (int): 图像在客户端预览显示的高度 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) big_src: str = dcs.field(default="", repr=False) origin_src: str = dcs.field(default="", repr=False) origin_size: int = 0 show_width: int = 0 show_height: int = 0 hash: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragImage_cp: src = data_proto.cdn_src big_src = data_proto.big_cdn_src origin_src = data_proto.origin_src origin_size = data_proto.origin_size show_width, _, show_height = data_proto.bsize.partition(",") show_width = int(show_width) show_height = int(show_height) if hash_obj := _IMAGEHASH_EXP.search(src): hash_ = hash_obj.group(1) else: hash_ = "" return FragImage_cp(src, big_src, origin_src, origin_size, show_width, show_height, hash_) @dcs.dataclass class Contents_cp(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 emojis (list[FragEmoji_cp]): 表情碎片列表 imgs (list[FragImage_cp]): 图像碎片列表 ats (list[FragAt_cp]): @碎片列表 links (list[FragLink_cp]): 链接碎片列表 tiebapluses (list[FragTiebaPlus_cp]): 贴吧plus碎片列表 voice (FragVoice_cp): 音频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) emojis: list[FragEmoji_cp] = dcs.field(default_factory=list, repr=False) imgs: list[FragImage_cp] = dcs.field(default_factory=list, repr=False) ats: list[FragAt_cp] = dcs.field(default_factory=list, repr=False) links: list[FragLink_cp] = dcs.field(default_factory=list, repr=False) tiebapluses: list[FragTiebaPlus_cp] = dcs.field(default_factory=list, repr=False) voice: FragVoice_cp = dcs.field(default_factory=FragVoice_cp, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_cp: content_protos = data_proto.content texts = [] emojis = [] imgs = [] ats = [] links = [] tiebapluses = [] voice = FragVoice_cp() def _frags(): for proto in content_protos: _type = proto.type # 0纯文本 9电话号 18话题 27百科词条 if _type in [0, 9, 18, 27]: frag = FragText_cp.from_proto(proto) texts.append(frag) yield frag # 11:tid=5047676428 elif _type in [2, 11]: frag = FragEmoji_cp.from_proto(proto) emojis.append(frag) yield frag # 20:tid=5470214675 elif _type in [3, 20]: frag = FragImage_cp.from_proto(proto) imgs.append(frag) yield frag elif _type == 4: frag = FragAt_cp.from_proto(proto) ats.append(frag) texts.append(frag) yield frag elif _type == 1: frag = FragLink_cp.from_proto(proto) links.append(frag) texts.append(frag) yield frag elif _type == 10: # voice frag = FragVoice_cp.from_proto(proto) nonlocal voice voice = frag yield frag # 35|36:tid=7769728331 / 37:tid=7760184147 elif _type in [35, 36, 37]: frag = FragTiebaPlus_cp.from_proto(proto) tiebapluses.append(frag) texts.append(frag) yield frag # outdated tiebaplus elif _type == 34: continue else: yield FragUnknown.from_proto(proto) objs = list(_frags()) return Contents_cp(objs, texts, emojis, imgs, ats, links, tiebapluses, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class UserInfo_cp: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 level (int): 等级 gender (Gender): 性别 is_bawu (bool): 是否吧务 is_vip (bool): 是否超级会员 is_god (bool): 是否大神 priv_like (PrivLike): 关注吧列表的公开状态 priv_reply (PrivReply): 帖子评论权限 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" level: int = 0 gender: Gender = Gender.UNKNOWN is_bawu: bool = False is_vip: bool = False is_god: bool = False priv_like: PrivLike = PrivLike.PUBLIC priv_reply: PrivReply = PrivReply.ALL @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_cp: user_id = data_proto.id portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.name nick_name_new = data_proto.name_show level = data_proto.level_id gender = Gender(data_proto.gender) is_bawu = bool(data_proto.is_bawu) is_vip = bool(data_proto.new_tshow_icon) is_god = bool(data_proto.new_god_data.status) priv_like = PrivLike(priv_like) if (priv_like := data_proto.priv_sets.like) else PrivLike.PUBLIC priv_reply = PrivReply(priv_reply) if (priv_reply := data_proto.priv_sets.reply) else PrivReply.ALL return UserInfo_cp( user_id, portrait, user_name, nick_name_new, level, gender, is_bawu, is_vip, is_god, priv_like, priv_reply ) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_cp) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class Post_c: """ 楼层信息 Attributes: text (str): 文本内容 contents (Contents_cp): 正文内容碎片列表 sign (str): 小尾巴文本内容 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 所在主题帖id pid (int): 回复id user (UserInfo_cp): 发布者的用户信息 author_id (int): 发布者的user_id floor (int): 楼层数 create_time (int): 创建时间 10位时间戳 以秒为单位 """ contents: Contents_cp = dcs.field(default_factory=Contents_cp) sign: str = "" fid: int = 0 fname: str = "" tid: int = 0 pid: int = 0 user: UserInfo_cp = dcs.field(default_factory=UserInfo_cp) floor: int = 0 create_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> Post_c: contents = Contents_cp.from_proto(data_proto) sign = "".join(p.text for p in data_proto.signature.content if p.type == 0) pid = data_proto.id user = UserInfo_cp.from_proto(data_proto.author) floor = data_proto.floor create_time = data_proto.time return Post_c(contents, sign, 0, "", 0, pid, user, floor, create_time) def __eq__(self, obj: Post_c) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @cached_property def text(self) -> str: if self.sign: text = f"{self.contents.text}\n{self.sign}" else: text = self.contents.text return text @property def author_id(self) -> int: return self.user.user_id @dcs.dataclass class Comments(TbErrorExt, Containers[Comment]): """ 楼中楼列表 Attributes: objs (list[Comment]): 楼中楼列表 err (Exception | None): 捕获的异常 page (Page_c): 页信息 has_more (bool): 是否还有下一页 forum (Forum_c): 所在吧信息 thread (Thread_c): 所在主题帖信息 post (Post_c): 所在楼层信息 """ page: Page_c = dcs.field(default_factory=Page_c) forum: Forum_c = dcs.field(default_factory=Forum_c) thread: Thread_c = dcs.field(default_factory=Thread_c) post: Post_c = dcs.field(default_factory=Post_c) @staticmethod def from_proto(data_proto: TypeMessage) -> Comments: page = Page_c.from_proto(data_proto.page) forum = Forum_c.from_proto(data_proto.forum) thread = Thread_c.from_proto(data_proto.thread) thread.fid = forum.fid thread.fname = forum.fname post = Post_c.from_proto(data_proto.post) post.fid = thread.fid post.fname = thread.fname post.tid = thread.tid objs = [Comment.from_proto(p) for p in data_proto.subpost_list] for comment in objs: comment.fid = forum.fid comment.fname = forum.fname comment.tid = thread.tid comment.ppid = post.pid comment.floor = post.floor comment.is_thread_author = thread.author_id == comment.author_id return Comments(objs, page, forum, thread, post) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_comments/protobuf/PbFloorReqIdl.proto ================================================ // tbclient.PbFloor.PbFloorReqIdl syntax = "proto3"; import "CommonReq.proto"; message PbFloorReqIdl { message DataReq { CommonReq common = 9; int64 kz = 1; int64 pid = 2; int64 spid = 3; int32 pn = 4; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_comments/protobuf/PbFloorReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x13PbFloorReqIdl.proto\x1a\x0f\x43ommonReq.proto"\x8f\x01\n\rPbFloorReqIdl\x12$\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x16.PbFloorReqIdl.DataReq\x1aX\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\t \x01(\x0b\x32\n.CommonReq\x12\n\n\x02kz\x18\x01 \x01(\x03\x12\x0b\n\x03pid\x18\x02 \x01(\x03\x12\x0c\n\x04spid\x18\x03 \x01(\x03\x12\n\n\x02pn\x18\x04 \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "PbFloorReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PBFLOORREQIDL"]._serialized_start = 41 _globals["_PBFLOORREQIDL"]._serialized_end = 184 _globals["_PBFLOORREQIDL_DATAREQ"]._serialized_start = 96 _globals["_PBFLOORREQIDL_DATAREQ"]._serialized_end = 184 ================================================ FILE: src/aiotieba/api/get_comments/protobuf/PbFloorResIdl.proto ================================================ // tbclient.PbFloor.PbFloorResIdl syntax = "proto3"; import "Error.proto"; import "Page.proto"; import "Post.proto"; import "ThreadInfo.proto"; import "SimpleForum.proto"; import "SubPostList.proto"; message PbFloorResIdl { Error error = 1; message DataRes { Page page = 1; Post post = 3; repeated SubPostList subpost_list = 4; ThreadInfo thread = 5; SimpleForum forum = 6; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_comments/protobuf/PbFloorResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import Page_pb2 as Page__pb2 from ..._protobuf import Post_pb2 as Post__pb2 from ..._protobuf import SimpleForum_pb2 as SimpleForum__pb2 from ..._protobuf import SubPostList_pb2 as SubPostList__pb2 from ..._protobuf import ThreadInfo_pb2 as ThreadInfo__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x13PbFloorResIdl.proto\x1a\x0b\x45rror.proto\x1a\nPage.proto\x1a\nPost.proto\x1a\x10ThreadInfo.proto\x1a\x11SimpleForum.proto\x1a\x11SubPostList.proto"\xe0\x01\n\rPbFloorResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12$\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x16.PbFloorResIdl.DataRes\x1a\x91\x01\n\x07\x44\x61taRes\x12\x13\n\x04page\x18\x01 \x01(\x0b\x32\x05.Page\x12\x13\n\x04post\x18\x03 \x01(\x0b\x32\x05.Post\x12"\n\x0csubpost_list\x18\x04 \x03(\x0b\x32\x0c.SubPostList\x12\x1b\n\x06thread\x18\x05 \x01(\x0b\x32\x0b.ThreadInfo\x12\x1b\n\x05\x66orum\x18\x06 \x01(\x0b\x32\x0c.SimpleForumb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "PbFloorResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PBFLOORRESIDL"]._serialized_start = 117 _globals["_PBFLOORRESIDL"]._serialized_end = 341 _globals["_PBFLOORRESIDL_DATARES"]._serialized_start = 196 _globals["_PBFLOORRESIDL_DATARES"]._serialized_end = 341 ================================================ FILE: src/aiotieba/api/get_dislike_forums/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import DislikeForum, DislikeForums ================================================ FILE: src/aiotieba/api/get_dislike_forums/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import Account, HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import DislikeForums from .protobuf import GetDislikeListReqIdl_pb2, GetDislikeListResIdl_pb2 CMD = 309692 def pack_proto(account: Account, pn: int, rn: int) -> bytes: req_proto = GetDislikeListReqIdl_pb2.GetDislikeListReqIdl() req_proto.data.common.BDUSS = account.BDUSS req_proto.data.common._client_version = LATEST_VERSION req_proto.data.pn = pn req_proto.data.rn = rn return req_proto.SerializeToString() def parse_body(body: bytes) -> DislikeForums: res_proto = GetDislikeListResIdl_pb2.GetDislikeListResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data dislike_forums = DislikeForums.from_proto(data_proto) return dislike_forums async def request_http(http_core: HttpCore, pn: int, rn: int) -> DislikeForums: data = pack_proto(http_core.account, pn, rn) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/u/user/getDislikeList", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, pn: int, rn: int) -> DislikeForums: data = pack_proto(ws_core.account, pn, rn) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_dislike_forums/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from ...exception import TbErrorExt from .._classdef import Containers, TypeMessage @dcs.dataclass class Page_dislikef: """ 页信息 Attributes: current_page (int): 当前页码 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 has_more: bool = False has_prev: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> Page_dislikef: current_page = data_proto.cur_page has_more = bool(data_proto.has_more) has_prev = current_page > 1 return Page_dislikef(current_page, has_more, has_prev) @dcs.dataclass class DislikeForum: """ 吧广场贴吧信息 Attributes: fid (int): 贴吧id fname (str): 贴吧名 member_num (int): 吧会员数 post_num (int): 发帖量 thread_num (int): 主题帖数 is_followed (bool): 是否已关注 """ fid: int = 0 fname: str = "" member_num: int = 0 post_num: int = 0 thread_num: int = 0 is_followed: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> DislikeForum: fid = data_proto.forum_id fname = data_proto.forum_name member_num = data_proto.member_count post_num = data_proto.post_num thread_num = data_proto.thread_num return DislikeForum(fid, fname, member_num, post_num, thread_num) def __eq__(self, obj: DislikeForum) -> bool: return self.fid == obj.fid def __hash__(self) -> int: return self.fid @dcs.dataclass class DislikeForums(TbErrorExt, Containers[DislikeForum]): """ 首页推荐屏蔽的贴吧列表 Attributes: objs (list[DislikeForum]): 首页推荐屏蔽的贴吧列表 err (Exception | None): 捕获的异常 page (Page_dislikef): 页信息 """ page: Page_dislikef = dcs.field(default_factory=Page_dislikef) @staticmethod def from_proto(data_proto: TypeMessage) -> DislikeForums: objs = [DislikeForum.from_proto(p) for p in data_proto.forum_list] page = Page_dislikef.from_proto(data_proto) return DislikeForums(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_dislike_forums/protobuf/GetDislikeListReqIdl.proto ================================================ // tbclient.GetDislikeList.GetDislikeListReqIdl syntax = "proto3"; import "CommonReq.proto"; message GetDislikeListReqIdl { message DataReq { CommonReq common = 1; int32 pn = 3; int32 rn = 4; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_dislike_forums/protobuf/GetDislikeListReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1aGetDislikeListReqIdl.proto\x1a\x0f\x43ommonReq.proto"\x82\x01\n\x14GetDislikeListReqIdl\x12+\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1d.GetDislikeListReqIdl.DataReq\x1a=\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\n.CommonReq\x12\n\n\x02pn\x18\x03 \x01(\x05\x12\n\n\x02rn\x18\x04 \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetDislikeListReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETDISLIKELISTREQIDL"]._serialized_start = 48 _globals["_GETDISLIKELISTREQIDL"]._serialized_end = 178 _globals["_GETDISLIKELISTREQIDL_DATAREQ"]._serialized_start = 117 _globals["_GETDISLIKELISTREQIDL_DATAREQ"]._serialized_end = 178 ================================================ FILE: src/aiotieba/api/get_dislike_forums/protobuf/GetDislikeListResIdl.proto ================================================ // tbclient.GetDislikeList.GetDislikeListResIdl syntax = "proto3"; import "Error.proto"; import "ForumList.proto"; message GetDislikeListResIdl { Error error = 1; message DataRes { repeated ForumList forum_list = 1; int32 has_more = 2; int32 cur_page = 3; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_dislike_forums/protobuf/GetDislikeListResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import ForumList_pb2 as ForumList__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1aGetDislikeListResIdl.proto\x1a\x0b\x45rror.proto\x1a\x0f\x46orumList.proto"\xa9\x01\n\x14GetDislikeListResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12+\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x1d.GetDislikeListResIdl.DataRes\x1aM\n\x07\x44\x61taRes\x12\x1e\n\nforum_list\x18\x01 \x03(\x0b\x32\n.ForumList\x12\x10\n\x08has_more\x18\x02 \x01(\x05\x12\x10\n\x08\x63ur_page\x18\x03 \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetDislikeListResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETDISLIKELISTRESIDL"]._serialized_start = 61 _globals["_GETDISLIKELISTRESIDL"]._serialized_end = 230 _globals["_GETDISLIKELISTRESIDL_DATARES"]._serialized_start = 153 _globals["_GETDISLIKELISTRESIDL_DATARES"]._serialized_end = 230 ================================================ FILE: src/aiotieba/api/get_fans/__init__.py ================================================ from ._api import parse_body, request from ._classdef import Fan, Fans ================================================ FILE: src/aiotieba/api/get_fans/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import Fans def parse_body(body: bytes) -> Fans: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) fans = Fans.from_json(res_json) return fans async def request(http_core: HttpCore, user_id: int, pn: int) -> Fans: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("pn", pn), ("uid", user_id), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/u/fans/page"), data ) body = await http_core.net_core.send_request(request, read_bufsize=16 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_fans/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class Fan: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" @staticmethod def from_json(data_map: Mapping) -> Fan: user_id = int(data_map["id"]) portrait = data_map["portrait"] if "?" in portrait: portrait = portrait[:-13] user_name = data_map["name"] nick_name_new = data_map["name_show"] return Fan(user_id, portrait, user_name, nick_name_new) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: Fan) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class Page_fan: """ 页信息 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_json(data_map: Mapping) -> Page_fan: page_size = int(data_map["page_size"]) current_page = int(data_map["current_page"]) total_page = int(data_map["total_page"]) total_count = int(data_map["total_count"]) has_more = bool(int(data_map["has_more"])) has_prev = bool(int(data_map["has_prev"])) return Page_fan(page_size, current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class Fans(TbErrorExt, Containers[Fan]): """ 粉丝列表 Attributes: objs (list[Fan]): 粉丝列表 err (Exception | None): 捕获的异常 page (Page_fan): 页信息 has_more (bool): 是否还有下一页 """ page: Page_fan = dcs.field(default_factory=Page_fan) @staticmethod def from_json(data_map: Mapping) -> Fans: objs = [Fan.from_json(m) for m in data_map["user_list"]] page = Page_fan.from_json(data_map["page"]) return Fans(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_fid/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/get_fid/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError, TiebaValueError from ...helper import parse_json def parse_body(body: bytes) -> int: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) if not (fid := res_json["data"]["fid"]): raise TiebaValueError("fid is 0") return fid async def request(http_core: HttpCore, fname: str) -> int: params = [ ("fname", fname), ("ie", "utf-8"), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="http", host=WEB_BASE_HOST, path="/f/commit/share/fnameShareApi"), params ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_follow_forums/__init__.py ================================================ from ._api import parse_body, request from ._classdef import FollowForum, FollowForums ================================================ FILE: src/aiotieba/api/get_follow_forums/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import FollowForums def parse_body(body: bytes) -> FollowForums: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) follow_forums = FollowForums.from_json(res_json) return follow_forums async def request(http_core: HttpCore, user_id: int, pn: int, rn: int) -> FollowForums: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("friend_uid", user_id), ("page_no", pn), ("page_size", rn), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/f/forum/like"), data ) body = await http_core.net_core.send_request(request, read_bufsize=16 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_follow_forums/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class FollowForum: """ 关注吧信息 Attributes: fid (int): 贴吧id fname (str): 贴吧名 level (int): 用户等级 exp (int): 经验值 """ fid: int = 0 fname: str = "" level: int = 0 exp: int = 0 @staticmethod def from_json(data_map: Mapping) -> FollowForum: fid = int(data_map["id"]) fname = data_map["name"] level = int(data_map["level_id"]) exp = int(data_map["cur_score"]) return FollowForum(fid, fname, level, exp) def __eq__(self, obj: FollowForum) -> bool: return self.fid == obj.fid def __hash__(self) -> int: return self.fid @dcs.dataclass class FollowForums(TbErrorExt, Containers[FollowForum]): """ 用户关注贴吧列表 Attributes: objs (list[Forum]): 用户关注贴吧列表 err (Exception | None): 捕获的异常 has_more (bool): 是否还有下一页 """ has_more: bool = False @staticmethod def from_json(data_map: Mapping) -> FollowForums: if forum_list := data_map.get("forum_list", {}): forum_dicts = forum_list.get("non-gconforum", []) objs = [FollowForum.from_json(m) for m in forum_dicts] forum_dicts = forum_list.get("gconforum", []) objs += [FollowForum.from_json(m) for m in forum_dicts] has_more = bool(int(data_map["has_more"])) else: objs = [] has_more = False return FollowForums(objs, has_more) ================================================ FILE: src/aiotieba/api/get_follows/__init__.py ================================================ from ._api import parse_body, request from ._classdef import Follow, Follows ================================================ FILE: src/aiotieba/api/get_follows/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import Follows def parse_body(body: bytes) -> Follows: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) follows = Follows.from_json(res_json) return follows async def request(http_core: HttpCore, user_id: int, pn: int) -> Follows: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("pn", pn), ("uid", user_id), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/u/follow/followList"), data ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_follows/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class Follow: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" @staticmethod def from_json(data_map: Mapping) -> Follow: user_id = int(data_map["id"]) portrait = data_map["portrait"] if "?" in portrait: portrait = portrait[:-13] user_name = data_map["name"] nick_name_new = data_map["name_show"] return Follow(user_id, portrait, user_name, nick_name_new) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: Follow) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class Page_follow: """ 页信息 Attributes: current_page (int): 当前页码 total_count (int): 总计数 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 total_count: int = 0 has_more: bool = False has_prev: bool = False @staticmethod def from_json(data_map: Mapping) -> Page_follow: current_page = int(data_map["pn"]) total_count = int(data_map["total_follow_num"]) has_more = bool(int(data_map["has_more"])) has_prev = current_page > 1 return Page_follow(current_page, total_count, has_more, has_prev) @dcs.dataclass class Follows(TbErrorExt, Containers[Follow]): """ 粉丝列表 Attributes: objs (list[Follow]): 粉丝列表 err (Exception | None): 捕获的异常 page (Page_follow): 页信息 has_more (bool): 是否还有下一页 """ page: Page_follow = dcs.field(default_factory=Page_follow) @staticmethod def from_json(data_map: Mapping) -> Follows: objs = [Follow.from_json(m) for m in data_map["follow_list"]] page = Page_follow.from_json(data_map) return Follows(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_forum/__init__.py ================================================ from ._api import parse_body, request from ._classdef import Forum ================================================ FILE: src/aiotieba/api/get_forum/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import Forum def parse_body(body: bytes) -> Forum: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) forum_dict = res_json["forum"] forum = Forum.from_json(forum_dict) return forum async def request(http_core: HttpCore, fname: str) -> Forum: data = [("kw", fname)] request = http_core.pack_form_request( yarl.URL.build(scheme="http", host=APP_BASE_HOST, path="/c/f/frs/frsBottom"), data ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_forum/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class Forum(TbErrorExt): """ 贴吧信息 Attributes: err (Exception | None): 捕获的异常 fid (int): 贴吧id fname (str): 贴吧名 category (str): 一级分类 subcategory (str): 二级分类 small_avatar (str): 吧头像(小) slogan (str): 吧标语 member_num (int): 吧会员数 post_num (int): 发帖量 thread_num (int): 主题帖数 has_bawu (bool): 是否有吧务 """ fid: int = 0 fname: str = "" category: str = "" subcategory: str = "" small_avatar: str = "" slogan: str = "" member_num: int = 0 post_num: int = 0 thread_num: int = 0 has_bawu: bool = False @staticmethod def from_json(data_map: Mapping) -> Forum: fid = data_map["id"] fname = data_map["name"] category = data_map["first_class"] subcategory = data_map["second_class"] small_avatar = data_map["avatar"] slogan = data_map["slogan"] member_num = data_map["member_num"] post_num = data_map["post_num"] thread_num = data_map["thread_num"] has_bawu = "managers" in data_map return Forum( fid, fname, category, subcategory, small_avatar, slogan, member_num, post_num, thread_num, has_bawu ) ================================================ FILE: src/aiotieba/api/get_forum_detail/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import Forum_detail ================================================ FILE: src/aiotieba/api/get_forum_detail/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import Forum_detail from .protobuf import GetForumDetailReqIdl_pb2, GetForumDetailResIdl_pb2 CMD = 303021 def pack_proto(fid: int) -> bytes: req_proto = GetForumDetailReqIdl_pb2.GetForumDetailReqIdl() req_proto.data.common._client_version = LATEST_VERSION req_proto.data.forum_id = fid return req_proto.SerializeToString() def parse_body(body: bytes) -> Forum_detail: res_proto = GetForumDetailResIdl_pb2.GetForumDetailResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data forum = Forum_detail.from_proto(data_proto) return forum async def request_http(http_core: HttpCore, fid: int) -> Forum_detail: data = pack_proto(fid) request = http_core.pack_proto_request( yarl.URL.build(scheme="http", host=APP_BASE_HOST, path="/c/f/forum/getforumdetail", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=4 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, fid: int) -> Forum_detail: data = pack_proto(fid) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_forum_detail/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt if TYPE_CHECKING: from .._classdef import TypeMessage @dcs.dataclass class Forum_detail(TbErrorExt): """ 贴吧信息 Attributes: err (Exception | None): 捕获的异常 fid (int): 贴吧id fname (str): 贴吧名 category (str): 一级分类 small_avatar (str): 吧头像(小) origin_avatar (str): 吧头像(原图) slogan (str): 吧标语 member_num (int): 吧会员数 post_num (int): 发帖量 has_bawu (bool): 是否有吧务 """ fid: int = 0 fname: str = "" category: str = "" small_avatar: str = "" origin_avatar: str = "" slogan: str = "" member_num: int = 0 post_num: int = 0 has_bawu: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> Forum_detail: forum_proto = data_proto.forum_info fid = forum_proto.forum_id fname = forum_proto.forum_name category = forum_proto.lv1_name small_avatar = forum_proto.avatar origin_avatar = forum_proto.avatar_origin slogan = forum_proto.slogan member_num = forum_proto.member_count post_num = forum_proto.thread_count has_bawu = data_proto.election_tab.new_strategy_text == "已有吧主" return Forum_detail(fid, fname, category, small_avatar, origin_avatar, slogan, member_num, post_num, has_bawu) ================================================ FILE: src/aiotieba/api/get_forum_detail/protobuf/GetForumDetailReqIdl.proto ================================================ // tbclient.GetForumDetail.GetForumDetailReqIdl syntax = "proto3"; import "CommonReq.proto"; message GetForumDetailReqIdl { message DataReq { int64 forum_id = 1; CommonReq common = 2; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_forum_detail/protobuf/GetForumDetailReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1aGetForumDetailReqIdl.proto\x1a\x0f\x43ommonReq.proto"|\n\x14GetForumDetailReqIdl\x12+\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1d.GetForumDetailReqIdl.DataReq\x1a\x37\n\x07\x44\x61taReq\x12\x10\n\x08\x66orum_id\x18\x01 \x01(\x03\x12\x1a\n\x06\x63ommon\x18\x02 \x01(\x0b\x32\n.CommonReqb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetForumDetailReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETFORUMDETAILREQIDL"]._serialized_start = 47 _globals["_GETFORUMDETAILREQIDL"]._serialized_end = 171 _globals["_GETFORUMDETAILREQIDL_DATAREQ"]._serialized_start = 116 _globals["_GETFORUMDETAILREQIDL_DATAREQ"]._serialized_end = 171 ================================================ FILE: src/aiotieba/api/get_forum_detail/protobuf/GetForumDetailResIdl.proto ================================================ // tbclient.GetForumDetail.GetForumDetailResIdl syntax = "proto3"; import "Error.proto"; message GetForumDetailResIdl { Error error = 1; message DataRes { message RecommendForumInfo { string avatar = 1; uint64 forum_id = 2; string forum_name = 3; uint32 member_count = 5; uint32 thread_count = 6; string slogan = 7; string lv1_name = 18; string avatar_origin = 20; } RecommendForumInfo forum_info = 1; message ManagerElectionTab { string new_strategy_text = 5; } ManagerElectionTab election_tab = 8; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_forum_detail/protobuf/GetForumDetailResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1aGetForumDetailResIdl.proto\x1a\x0b\x45rror.proto"\xd7\x03\n\x14GetForumDetailResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12+\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x1d.GetForumDetailResIdl.DataRes\x1a\xfa\x02\n\x07\x44\x61taRes\x12\x44\n\nforum_info\x18\x01 \x01(\x0b\x32\x30.GetForumDetailResIdl.DataRes.RecommendForumInfo\x12\x46\n\x0c\x65lection_tab\x18\x08 \x01(\x0b\x32\x30.GetForumDetailResIdl.DataRes.ManagerElectionTab\x1a\xaf\x01\n\x12RecommendForumInfo\x12\x0e\n\x06\x61vatar\x18\x01 \x01(\t\x12\x10\n\x08\x66orum_id\x18\x02 \x01(\x04\x12\x12\n\nforum_name\x18\x03 \x01(\t\x12\x14\n\x0cmember_count\x18\x05 \x01(\r\x12\x14\n\x0cthread_count\x18\x06 \x01(\r\x12\x0e\n\x06slogan\x18\x07 \x01(\t\x12\x10\n\x08lv1_name\x18\x12 \x01(\t\x12\x15\n\ravatar_origin\x18\x14 \x01(\t\x1a/\n\x12ManagerElectionTab\x12\x19\n\x11new_strategy_text\x18\x05 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetForumDetailResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETFORUMDETAILRESIDL"]._serialized_start = 44 _globals["_GETFORUMDETAILRESIDL"]._serialized_end = 515 _globals["_GETFORUMDETAILRESIDL_DATARES"]._serialized_start = 137 _globals["_GETFORUMDETAILRESIDL_DATARES"]._serialized_end = 515 _globals["_GETFORUMDETAILRESIDL_DATARES_RECOMMENDFORUMINFO"]._serialized_start = 291 _globals["_GETFORUMDETAILRESIDL_DATARES_RECOMMENDFORUMINFO"]._serialized_end = 466 _globals["_GETFORUMDETAILRESIDL_DATARES_MANAGERELECTIONTAB"]._serialized_start = 468 _globals["_GETFORUMDETAILRESIDL_DATARES_MANAGERELECTIONTAB"]._serialized_end = 515 ================================================ FILE: src/aiotieba/api/get_forum_level/__init__.py ================================================ from ._api import request_http from ._classdef import LevelInfo ================================================ FILE: src/aiotieba/api/get_forum_level/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import Account, HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import LevelInfo from .protobuf import GetLevelInfoReqIdl_pb2, GetLevelInfoResIdl_pb2 CMD = 301005 def pack_proto(account: Account, fid: int) -> bytes: req_proto = GetLevelInfoReqIdl_pb2.GetLevelInfoReqIdl() req_proto.data.common.BDUSS = account.BDUSS req_proto.data.forum_id = fid return req_proto.SerializeToString() def parse_body(body: bytes) -> LevelInfo: res_proto = GetLevelInfoResIdl_pb2.GetLevelInfoResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data level_info = LevelInfo.from_proto(data_proto) return level_info async def request_http(http_core: HttpCore, forum_id: int) -> LevelInfo: data = pack_proto(http_core.account, forum_id) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/f/forum/getLevelInfo", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, forum_id: int) -> LevelInfo: data = pack_proto(ws_core.account, forum_id) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_forum_level/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING if TYPE_CHECKING: from .._classdef import TypeMessage @dcs.dataclass class LevelInfo: """ 用户于某贴吧的等级信息 Attributes: user_level (int): 等级数值 level_name (str): 等级名称 is_like (int): 是否已关注 """ level_name: str = "" user_level: int = 0 is_like: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> LevelInfo: user_level = data_proto.user_level level_name = data_proto.level_name is_like = data_proto.is_like return LevelInfo(level_name, user_level, is_like) ================================================ FILE: src/aiotieba/api/get_forum_level/protobuf/GetLevelInfoReqIdl.proto ================================================ // tbclient.GetLevelInfo.GetLevelInfoReqIdl syntax = "proto3"; import "CommonReq.proto"; message GetLevelInfoReqIdl { message DataReq { CommonReq common = 2; int64 forum_id = 1; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_forum_level/protobuf/GetLevelInfoReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x18GetLevelInfoReqIdl.proto\x1a\x0f\x43ommonReq.proto"x\n\x12GetLevelInfoReqIdl\x12)\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1b.GetLevelInfoReqIdl.DataReq\x1a\x37\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x02 \x01(\x0b\x32\n.CommonReq\x12\x10\n\x08\x66orum_id\x18\x01 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetLevelInfoReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETLEVELINFOREQIDL"]._serialized_start = 45 _globals["_GETLEVELINFOREQIDL"]._serialized_end = 165 _globals["_GETLEVELINFOREQIDL_DATAREQ"]._serialized_start = 110 _globals["_GETLEVELINFOREQIDL_DATAREQ"]._serialized_end = 165 ================================================ FILE: src/aiotieba/api/get_forum_level/protobuf/GetLevelInfoResIdl.proto ================================================ // tbclient.GetLevelInfo.GetLevelInfoResIdl syntax = "proto3"; import "Error.proto"; message GetLevelInfoResIdl { Error error = 2; message DataRes { int32 is_like = 2; // repeated LevelInfo level_info = 1; string level_name = 4; int32 user_level = 3; } DataRes data = 1; } ================================================ FILE: src/aiotieba/api/get_forum_level/protobuf/GetLevelInfoResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x18GetLevelInfoResIdl.proto\x1a\x0b\x45rror.proto"\x9a\x01\n\x12GetLevelInfoResIdl\x12\x15\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x06.Error\x12)\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1b.GetLevelInfoResIdl.DataRes\x1a\x42\n\x07\x44\x61taRes\x12\x0f\n\x07is_like\x18\x02 \x01(\x05\x12\x12\n\nlevel_name\x18\x04 \x01(\t\x12\x12\n\nuser_level\x18\x03 \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetLevelInfoResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETLEVELINFORESIDL"]._serialized_start = 42 _globals["_GETLEVELINFORESIDL"]._serialized_end = 196 _globals["_GETLEVELINFORESIDL_DATARES"]._serialized_start = 130 _globals["_GETLEVELINFORESIDL_DATARES"]._serialized_end = 196 ================================================ FILE: src/aiotieba/api/get_group_msg/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request from ._classdef import UserInfo_ws, WsMessage, WsMsgGroup, WsMsgGroups ================================================ FILE: src/aiotieba/api/get_group_msg/_api.py ================================================ from __future__ import annotations from typing import TYPE_CHECKING from ...exception import TiebaServerError from ._classdef import WsMsgGroups from .protobuf import GetGroupMsgReqIdl_pb2, GetGroupMsgResIdl_pb2 if TYPE_CHECKING: from ...core import Account, WsCore CMD = 202003 def pack_proto(account: Account, group_ids: list[int], msg_ids: list[int], get_type: int) -> bytes: req_proto = GetGroupMsgReqIdl_pb2.GetGroupMsgReqIdl() for group_id, msg_id in zip(group_ids, msg_ids, strict=False): group_proto = req_proto.data.groupMids.add() group_proto.groupId = group_id group_proto.lastMsgId = msg_id req_proto.data.gettype = str(get_type) req_proto.cuid = f"{account.cuid}|com.baidu.tieba_mini12.35.1.0" return req_proto.SerializeToString() def parse_body(body: bytes) -> WsMsgGroups: res_proto = GetGroupMsgResIdl_pb2.GetGroupMsgResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data groups = WsMsgGroups.from_proto(data_proto) return groups async def request(ws_core: WsCore, group_ids: list[int], get_type: int) -> WsMsgGroups: msg_ids = [ws_core.mid_manager.get_msg_id(gid) for gid in group_ids] data = pack_proto(ws_core.account, group_ids, msg_ids, get_type) resp = await ws_core.send(data, CMD) return parse_body(await resp.read()) ================================================ FILE: src/aiotieba/api/get_group_msg/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from ...exception import TbErrorExt from .._classdef import Containers, TypeMessage @dcs.dataclass class UserInfo_ws: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_ws: user_id = data_proto.userId portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.userName return UserInfo_ws(user_id, portrait, user_name) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_ws) -> 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 log_name(self) -> str: return str(self) @dcs.dataclass class WsMessage: """ websocket消息 Attributes: msg_id (int): 消息id msg_type (str): 消息类型 text (str): 文本内容 user (UserInfo_ws): 文本内容 create_time (int): 发送时间 10位时间戳 以秒为单位 """ msg_id: int = 0 msg_type: int = 0 text: str = "" user: UserInfo_ws = dcs.field(default_factory=UserInfo_ws) create_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> None: msg_id = data_proto.msgId msg_type = data_proto.msgType text = data_proto.content user = UserInfo_ws.from_proto(data_proto.userInfo) create_time = data_proto.createTime return WsMessage(msg_id, msg_type, text, user, create_time) @dcs.dataclass class WsMsgGroup: """ websocket消息组 Attributes: group_id (str): 消息组id group_type (int): 消息组类别 messages (list[WsMessage]): 消息列表 """ group_id: int = 0 group_type: int = 0 messages: list[WsMessage] = dcs.field(default_factory=list) @staticmethod def from_proto(data_proto: TypeMessage) -> WsMsgGroup: group_id = data_proto.groupInfo.groupId group_type = data_proto.groupInfo.groupType messages = [WsMessage.from_proto(p) for p in data_proto.msgList] return WsMsgGroup(group_id, group_type, messages) @dcs.dataclass class WsMsgGroups(TbErrorExt, Containers[WsMsgGroup]): """ websocket消息组列表 Attributes: objs (list[WsMsgGroup]): websocket消息组列表 err (Exception | None): 捕获的异常 """ @staticmethod def from_proto(data_proto: TypeMessage) -> WsMsgGroups: objs = [WsMsgGroup.from_proto(p) for p in data_proto.groupInfo] return WsMsgGroups(objs) ================================================ FILE: src/aiotieba/api/get_group_msg/protobuf/GetGroupMsgReqIdl.proto ================================================ // protobuf.GetGroupMsg.GetGroupMsgReqIdl syntax = "proto3"; message GetGroupMsgReqIdl { string cuid = 1; message DataReq { message GroupLastId { int64 groupId = 1; int64 lastMsgId = 2; } repeated GroupLastId groupMids = 6; string gettype = 7; } DataReq data = 2; } ================================================ FILE: src/aiotieba/api/get_group_msg/protobuf/GetGroupMsgReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x17GetGroupMsgReqIdl.proto"\xd6\x01\n\x11GetGroupMsgReqIdl\x12\x0c\n\x04\x63uid\x18\x01 \x01(\t\x12(\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x1a.GetGroupMsgReqIdl.DataReq\x1a\x88\x01\n\x07\x44\x61taReq\x12\x39\n\tgroupMids\x18\x06 \x03(\x0b\x32&.GetGroupMsgReqIdl.DataReq.GroupLastId\x12\x0f\n\x07gettype\x18\x07 \x01(\t\x1a\x31\n\x0bGroupLastId\x12\x0f\n\x07groupId\x18\x01 \x01(\x03\x12\x11\n\tlastMsgId\x18\x02 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetGroupMsgReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETGROUPMSGREQIDL"]._serialized_start = 28 _globals["_GETGROUPMSGREQIDL"]._serialized_end = 242 _globals["_GETGROUPMSGREQIDL_DATAREQ"]._serialized_start = 106 _globals["_GETGROUPMSGREQIDL_DATAREQ"]._serialized_end = 242 _globals["_GETGROUPMSGREQIDL_DATAREQ_GROUPLASTID"]._serialized_start = 193 _globals["_GETGROUPMSGREQIDL_DATAREQ_GROUPLASTID"]._serialized_end = 242 ================================================ FILE: src/aiotieba/api/get_group_msg/protobuf/GetGroupMsgResIdl.proto ================================================ // protobuf.GetGroupMsg.GetGroupMsgResIdl syntax = "proto3"; import "Error.proto"; message GetGroupMsgResIdl { Error error = 1; message DataRes { message GroupMsg { message GroupInfo { int64 groupId = 1; int32 groupType = 20; } GroupInfo groupInfo = 1; message MsgInfo { int64 msgId = 1; int32 msgType = 3; string content = 5; int32 createTime = 8; message UserInfo { int64 userId = 1; string userName = 2; string portrait = 4; // string userNameShow = 18; } UserInfo userInfo = 10; } repeated MsgInfo msgList = 2; } repeated GroupMsg groupInfo = 1; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_group_msg/protobuf/GetGroupMsgResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x17GetGroupMsgResIdl.proto\x1a\x0b\x45rror.proto"\xaf\x04\n\x11GetGroupMsgResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12(\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x1a.GetGroupMsgResIdl.DataRes\x1a\xd8\x03\n\x07\x44\x61taRes\x12\x36\n\tgroupInfo\x18\x01 \x03(\x0b\x32#.GetGroupMsgResIdl.DataRes.GroupMsg\x1a\x94\x03\n\x08GroupMsg\x12@\n\tgroupInfo\x18\x01 \x01(\x0b\x32-.GetGroupMsgResIdl.DataRes.GroupMsg.GroupInfo\x12<\n\x07msgList\x18\x02 \x03(\x0b\x32+.GetGroupMsgResIdl.DataRes.GroupMsg.MsgInfo\x1a/\n\tGroupInfo\x12\x0f\n\x07groupId\x18\x01 \x01(\x03\x12\x11\n\tgroupType\x18\x14 \x01(\x05\x1a\xd6\x01\n\x07MsgInfo\x12\r\n\x05msgId\x18\x01 \x01(\x03\x12\x0f\n\x07msgType\x18\x03 \x01(\x05\x12\x0f\n\x07\x63ontent\x18\x05 \x01(\t\x12\x12\n\ncreateTime\x18\x08 \x01(\x05\x12\x46\n\x08userInfo\x18\n \x01(\x0b\x32\x34.GetGroupMsgResIdl.DataRes.GroupMsg.MsgInfo.UserInfo\x1a>\n\x08UserInfo\x12\x0e\n\x06userId\x18\x01 \x01(\x03\x12\x10\n\x08userName\x18\x02 \x01(\t\x12\x10\n\x08portrait\x18\x04 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetGroupMsgResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETGROUPMSGRESIDL"]._serialized_start = 41 _globals["_GETGROUPMSGRESIDL"]._serialized_end = 600 _globals["_GETGROUPMSGRESIDL_DATARES"]._serialized_start = 128 _globals["_GETGROUPMSGRESIDL_DATARES"]._serialized_end = 600 _globals["_GETGROUPMSGRESIDL_DATARES_GROUPMSG"]._serialized_start = 196 _globals["_GETGROUPMSGRESIDL_DATARES_GROUPMSG"]._serialized_end = 600 _globals["_GETGROUPMSGRESIDL_DATARES_GROUPMSG_GROUPINFO"]._serialized_start = 336 _globals["_GETGROUPMSGRESIDL_DATARES_GROUPMSG_GROUPINFO"]._serialized_end = 383 _globals["_GETGROUPMSGRESIDL_DATARES_GROUPMSG_MSGINFO"]._serialized_start = 386 _globals["_GETGROUPMSGRESIDL_DATARES_GROUPMSG_MSGINFO"]._serialized_end = 600 _globals["_GETGROUPMSGRESIDL_DATARES_GROUPMSG_MSGINFO_USERINFO"]._serialized_start = 538 _globals["_GETGROUPMSGRESIDL_DATARES_GROUPMSG_MSGINFO_USERINFO"]._serialized_end = 600 ================================================ FILE: src/aiotieba/api/get_images/__init__.py ================================================ from ._api import parse_body, request, request_bytes from ._classdef import Image, ImageBytes ================================================ FILE: src/aiotieba/api/get_images/_api.py ================================================ import aiohttp import yarl from ...core import HttpCore from ...exception import ContentTypeError, HTTPStatusError from ._classdef import Image, ImageBytes def _headers_checker(response: aiohttp.ClientResponse) -> None: if response.status != 200: raise HTTPStatusError(response.status, response.reason) if not response.content_type.endswith(("jpeg", "png", "bmp"), 6): raise ContentTypeError(f"Expect jpeg, png or bmp, got {response.content_type}") def parse_body(body: bytes) -> Image: import cv2 as cv import numpy as np image = cv.imdecode(np.frombuffer(body, np.uint8), cv.IMREAD_COLOR) if image is None: raise RuntimeError("Error in cv2.imdecode") return Image(image) async def _request_bytes(http_core: HttpCore, url: yarl.URL) -> bytes: request = http_core.pack_web_get_request( url, [], extra_headers=[(aiohttp.hdrs.REFERER, "tieba.baidu.com")], ) body = await http_core.net_core.send_request(request, read_bufsize=256 * 1024, headers_checker=_headers_checker) return body async def request_bytes(http_core: HttpCore, url: yarl.URL) -> ImageBytes: body = await _request_bytes(http_core, url) return ImageBytes(body) async def request(http_core: HttpCore, url: yarl.URL) -> Image: body = await _request_bytes(http_core, url) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_images/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt if TYPE_CHECKING: import numpy as np def _null_factory() -> np.ndarray: import numpy as np return np.empty(0, dtype=np.uint8) @dcs.dataclass class Image(TbErrorExt): """ 图像 Attributes: err (Exception | None): 捕获的异常 img (np.ndarray): 图像 """ img: np.ndarray = dcs.field(default_factory=_null_factory) @dcs.dataclass class ImageBytes(TbErrorExt): """ 图像原始字节流 Attributes: err (Exception | None): 捕获的异常 data (bytes): 图像原始字节流 """ data: bytes = b"" ================================================ FILE: src/aiotieba/api/get_last_replyers/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import Thread_lp, Threads_lp, UserInfo_lp ================================================ FILE: src/aiotieba/api/get_last_replyers/_api.py ================================================ 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 = int(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_proto(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()) ================================================ FILE: src/aiotieba/api/get_last_replyers/_classdef.py ================================================ 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_proto(data_proto: TypeMessage) -> Page_lp: page_size = data_proto.page_size current_page = data_proto.current_page if current_page == 0 and page_size != 0: current_page = 1 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_proto(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_proto(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): 最后回复者的用户信息 is_good (bool): 是否精品帖 is_top (bool): 是否置顶帖 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) is_good: bool = False is_top: bool = False create_time: int = 0 last_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> None: title = data_proto.title tid = data_proto.id pid = data_proto.first_post_id user = UserInfo_lp.from_proto(data_proto.author) last_replyer = LastReplyer.from_proto(data_proto.last_replyer) is_good = bool(data_proto.is_good) is_top = bool(data_proto.is_top) create_time = data_proto.create_time last_time = data_proto.last_time_int return Thread_lp(title, 0, "", tid, pid, user, last_replyer, is_good, is_top, 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_proto(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_proto(data_proto: TypeMessage) -> Threads_lp: page = Page_lp.from_proto(data_proto.page) forum = Forum_lp.from_proto(data_proto) objs = [Thread_lp.from_proto(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 ================================================ FILE: src/aiotieba/api/get_last_replyers/protobuf/FrsPageReqIdl4lp.proto ================================================ // 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; } ================================================ FILE: src/aiotieba/api/get_last_replyers/protobuf/FrsPageReqIdl4lp_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b"\n\x16\x46rsPageReqIdl4lp.proto\x1a\x0f\x43ommonReq.proto\"\xc9\x01\n\x10\x46rsPageReqIdl4lp\x12'\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x19.FrsPageReqIdl4lp.DataReq\x1a\x8b\x01\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18' \x01(\x0b\x32\n.CommonReq\x12\n\n\x02kw\x18\x01 \x01(\t\x12\n\n\x02rn\x18\x02 \x01(\x05\x12\x0f\n\x07rn_need\x18\x03 \x01(\x05\x12\x0f\n\x07is_good\x18\x04 \x01(\x05\x12\x0b\n\x03\x63id\x18\x05 \x01(\x05\x12\n\n\x02pn\x18\x0f \x01(\x05\x12\x11\n\tsort_type\x18/ \x01(\x05\x62\x06proto3" ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "FrsPageReqIdl4lp_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_FRSPAGEREQIDL4LP"]._serialized_start = 44 _globals["_FRSPAGEREQIDL4LP"]._serialized_end = 245 _globals["_FRSPAGEREQIDL4LP_DATAREQ"]._serialized_start = 106 _globals["_FRSPAGEREQIDL4LP_DATAREQ"]._serialized_end = 245 ================================================ FILE: src/aiotieba/api/get_last_replyers/protobuf/FrsPageResIdl4lp.proto ================================================ // tbclient.FrsPage.FrsPageResIdl syntax = "proto3"; import "Error.proto"; import "Page.proto"; import "ThreadInfo.proto"; message FrsPageResIdl4lp { Error error = 1; message DataRes { message ForumInfo { int64 id = 1; string name = 2; } ForumInfo forum = 2; Page page = 4; repeated ThreadInfo thread_list = 7; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_last_replyers/protobuf/FrsPageResIdl4lp_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import Page_pb2 as Page__pb2 from ..._protobuf import ThreadInfo_pb2 as ThreadInfo__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b"\n\x16\x46rsPageResIdl4lp.proto\x1a\x0b\x45rror.proto\x1a\nPage.proto\x1a\x10ThreadInfo.proto\"\xf0\x01\n\x10\x46rsPageResIdl4lp\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12'\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x19.FrsPageResIdl4lp.DataRes\x1a\x9b\x01\n\x07\x44\x61taRes\x12\x32\n\x05\x66orum\x18\x02 \x01(\x0b\x32#.FrsPageResIdl4lp.DataRes.ForumInfo\x12\x13\n\x04page\x18\x04 \x01(\x0b\x32\x05.Page\x12 \n\x0bthread_list\x18\x07 \x03(\x0b\x32\x0b.ThreadInfo\x1a%\n\tForumInfo\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04name\x18\x02 \x01(\tb\x06proto3" ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "FrsPageResIdl4lp_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_FRSPAGERESIDL4LP"]._serialized_start = 70 _globals["_FRSPAGERESIDL4LP"]._serialized_end = 310 _globals["_FRSPAGERESIDL4LP_DATARES"]._serialized_start = 155 _globals["_FRSPAGERESIDL4LP_DATARES"]._serialized_end = 310 _globals["_FRSPAGERESIDL4LP_DATARES_FORUMINFO"]._serialized_start = 273 _globals["_FRSPAGERESIDL4LP_DATARES_FORUMINFO"]._serialized_end = 310 ================================================ FILE: src/aiotieba/api/get_member_users/__init__.py ================================================ from ._api import parse_body, request from ._classdef import MemberUser, MemberUsers ================================================ FILE: src/aiotieba/api/get_member_users/_api.py ================================================ import bs4 import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ._classdef import MemberUsers def parse_body(body: bytes) -> MemberUsers: soup = bs4.BeautifulSoup(body, "lxml") member_users = MemberUsers.from_xml(soup) return member_users async def request(http_core: HttpCore, fname: str, pn: int) -> MemberUsers: params = [ ("word", fname), ("pn", pn), ("ie", "utf-8"), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/bawu2/platform/listMemberInfo"), params ) body = await http_core.net_core.send_request(request, read_bufsize=32 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_member_users/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: import bs4 @dcs.dataclass class MemberUser: """ 最新关注用户信息 Attributes: user_name (str): 用户名 portrait (str): portrait level (int): 等级 """ user_name: str = "" portrait: str = "" level: int = 0 @staticmethod def from_xml(data_tag: bs4.element.Tag) -> MemberUser: user_item = data_tag.a user_name = user_item["title"] portrait = user_item["href"][14:] level_item = data_tag.span level = int(level_item["class"][1][12:]) return MemberUser(user_name, portrait, level) @dcs.dataclass class Page_member: """ 页信息 Attributes: current_page (int): 当前页码 total_page (int): 总页码 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 total_page: int = 0 has_more: bool = False has_prev: int = False @staticmethod def from_xml(data_tag: bs4.element.Tag) -> Page_member: current_page = int(data_tag.text) total_page_item = data_tag.parent.next_sibling total_page = int(total_page_item.text[1:-1]) has_more = current_page < total_page has_prev = current_page > 1 return Page_member(current_page, total_page, has_more, has_prev) @dcs.dataclass class MemberUsers(TbErrorExt, Containers[MemberUser]): """ 最新关注用户列表 Attributes: objs (list[MemberUser]): 最新关注用户列表 err (Exception | None): 捕获的异常 page (Page_member): 页信息 has_more (bool): 是否还有下一页 """ page: Page_member = dcs.field(default_factory=Page_member) @staticmethod def from_xml(data_soup: bs4.BeautifulSoup) -> MemberUsers: objs = [MemberUser.from_xml(t) for t in data_soup("div", class_="name_wrap")] page = Page_member.from_xml(data_soup.find("div", class_="tbui_pagination").find("li", class_="active")) return MemberUsers(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_posts/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import Comment_p, Post, Posts, Thread_p, UserInfo_p, UserInfo_pt ================================================ FILE: src/aiotieba/api/get_posts/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, STABLE_VERSION from ...core import Account, HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import Posts from .protobuf import PbPageReqIdl_pb2, PbPageResIdl_pb2 CMD = 302001 def pack_proto( account: Account, tid: int, pn: int, rn: int, sort: int, only_thread_author: bool, with_comments: bool, comment_sort_by_agree: bool, comment_rn: int, ) -> bytes: req_proto = PbPageReqIdl_pb2.PbPageReqIdl() req_proto.data.common._client_type = 2 req_proto.data.common._client_version = STABLE_VERSION req_proto.data.kz = tid req_proto.data.pn = pn req_proto.data.rn = rn if rn > 1 else 2 req_proto.data.r = sort req_proto.data.lz = int(only_thread_author) if with_comments: req_proto.data.common.BDUSS = account.BDUSS req_proto.data.with_floor = int(with_comments) req_proto.data.floor_sort_type = int(comment_sort_by_agree) req_proto.data.floor_rn = comment_rn return req_proto.SerializeToString() def parse_body(body: bytes) -> Posts: res_proto = PbPageResIdl_pb2.PbPageResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data posts = Posts.from_proto(data_proto) return posts async def request_http( http_core: HttpCore, tid: int, pn: int, rn: int, sort: int, only_thread_author: bool, with_comments: bool, comment_sort_by_agree: bool, comment_rn: int, ) -> Posts: data = pack_proto( http_core.account, tid, pn, rn, sort, only_thread_author, with_comments, comment_sort_by_agree, comment_rn, ) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/f/pb/page", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=128 * 1024) return parse_body(body) async def request_ws( ws_core: WsCore, tid: int, pn: int, rn: int, sort: int, only_thread_author: bool, with_comments: bool, comment_sort_by_agree: bool, comment_rn: int, ) -> Posts: data = pack_proto( ws_core.account, tid, pn, rn, sort, only_thread_author, with_comments, comment_sort_by_agree, comment_rn, ) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_posts/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from ...enums import Gender, PrivLike, PrivReply, ThreadType from ...exception import TbErrorExt from ...helper import deprecated from ...logging import get_logger as LOG from .._classdef import Containers, TypeMessage, VoteInfo from .._classdef.contents import ( _IMAGEHASH_EXP, FragAt, FragEmoji, FragLink, FragText, FragTiebaPlus, FragUnknown, FragVideo, FragVoice, TypeFragment, TypeFragText, ) FragText_p = FragText_pt = FragText_pc = FragText FragEmoji_p = FragEmoji_pt = FragEmoji_pc = FragEmoji FragAt_p = FragAt_pt = FragAt_pc = FragAt FragLink_p = FragLink_pt = FragLink_pc = FragLink FragTiebaPlus_p = FragTiebaPlus_pt = FragTiebaPlus_pc = FragTiebaPlus FragVideo_pt = FragVideo FragVoice_p = FragVoice_pt = FragVoice_pc = FragVoice @dcs.dataclass class FragImage_p: """ 图像碎片 Attributes: src (str): 小图链接 big_src (str): 大图链接 origin_src (str): 原图链接 origin_size (int): 原图大小 show_width (int): 图像在客户端预览显示的宽度 show_height (int): 图像在客户端预览显示的高度 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) big_src: str = dcs.field(default="", repr=False) origin_src: str = dcs.field(default="", repr=False) origin_size: int = 0 show_width: int = 0 show_height: int = 0 hash: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragImage_p: src = data_proto.cdn_src big_src = data_proto.big_cdn_src origin_src = data_proto.origin_src origin_size = data_proto.origin_size show_width, _, show_height = data_proto.bsize.partition(",") show_width = int(show_width) show_height = int(show_height) if hash_obj := _IMAGEHASH_EXP.search(src): hash_ = hash_obj.group(1) else: hash_ = "" return FragImage_p(src, big_src, origin_src, origin_size, show_width, show_height, hash_) @dcs.dataclass class FragVideo_p: """ 视频碎片 Attributes: src (str): 视频链接 cover_src (str): 封面链接 duration (int): 视频长度 以秒为单位 width (int): 视频宽度 height (int): 视频高度 view_num (int): 浏览次数 """ src: str = "" cover_src: str = "" duration: int = 0 width: int = 0 height: int = 0 view_num: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> FragVideo_p: src = data_proto.link cover_src = data_proto.src duration = data_proto.during_time width = data_proto.width height = data_proto.height view_num = data_proto.count return FragVideo_p(src, cover_src, duration, width, height, view_num) def __bool__(self) -> bool: return bool(self.width) @dcs.dataclass class Contents_p(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 emojis (list[FragEmoji_p]): 表情碎片列表 imgs (list[FragImage_p]): 图像碎片列表 ats (list[FragAt_p]): @碎片列表 links (list[FragLink_p]): 链接碎片列表 tiebapluses (list[FragTiebaPlus_p]): 贴吧plus碎片列表 video (FragVideo_p): 视频碎片 voice (FragVoice_p): 音频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) emojis: list[FragEmoji_p] = dcs.field(default_factory=list, repr=False) imgs: list[FragImage_p] = dcs.field(default_factory=list, repr=False) ats: list[FragAt_p] = dcs.field(default_factory=list, repr=False) links: list[FragLink_p] = dcs.field(default_factory=list, repr=False) tiebapluses: list[FragTiebaPlus_p] = dcs.field(default_factory=list, repr=False) video: FragVideo_p = dcs.field(default_factory=FragVideo_p, repr=False) voice: FragVoice_p = dcs.field(default_factory=FragVoice_p, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_p: content_protos = data_proto.content texts = [] emojis = [] imgs = [] ats = [] links = [] tiebapluses = [] video = FragVideo_p() voice = FragVoice_p() def _frags(): for proto in content_protos: _type = proto.type # 0纯文本 9电话号 18话题 27百科词条 40梗百科 if _type in [0, 9, 18, 27, 40]: frag = FragText_p.from_proto(proto) texts.append(frag) yield frag # 11:tid=5047676428 elif _type in [2, 11]: frag = FragEmoji_p.from_proto(proto) emojis.append(frag) yield frag # 20:tid=5470214675 elif _type in [3, 20]: frag = FragImage_p.from_proto(proto) imgs.append(frag) yield frag elif _type == 4: frag = FragAt_p.from_proto(proto) ats.append(frag) texts.append(frag) yield frag elif _type == 1: frag = FragLink_p.from_proto(proto) links.append(frag) texts.append(frag) yield frag elif _type == 10: # voice frag = FragVoice_p.from_proto(proto) nonlocal voice voice = frag yield frag elif _type == 5: # video frag = FragVideo_p.from_proto(proto) nonlocal video video = frag yield frag # 35|36:tid=7769728331 / 37:tid=7760184147 elif _type in [35, 36, 37]: frag = FragTiebaPlus_p.from_proto(proto) tiebapluses.append(frag) texts.append(frag) yield frag # outdated tiebaplus / vote elif _type in [34, 52]: continue else: yield FragUnknown.from_proto(proto) objs = list(_frags()) return Contents_p(objs, texts, emojis, imgs, ats, links, tiebapluses, video, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class Contents_pc(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 emojis (list[FragEmoji_pc]): 表情碎片列表 ats (list[FragAt_pc]): @碎片列表 links (list[FragLink_pc]): 链接碎片列表 tiebapluses (list[FragTiebaPlus_pc]): 贴吧plus碎片列表 voice (FragVoice_pc): 音频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) emojis: list[FragEmoji_pc] = dcs.field(default_factory=list, repr=False) ats: list[FragAt_pc] = dcs.field(default_factory=list, repr=False) links: list[FragLink_pc] = dcs.field(default_factory=list, repr=False) tiebapluses: list[FragTiebaPlus_pc] = dcs.field(default_factory=list, repr=False) voice: FragVoice_pc = dcs.field(default_factory=FragVoice_pc, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_pc: content_protos = data_proto.content texts = [] emojis = [] ats = [] links = [] tiebapluses = [] voice = FragVoice_pc() def _frags(): for proto in content_protos: _type = proto.type # 0纯文本 9电话号 18话题 27百科词条 if _type in [0, 9, 18, 27]: frag = FragText_pc.from_proto(proto) texts.append(frag) yield frag # 11:tid=5047676428 elif _type in [2, 11]: frag = FragEmoji_pc.from_proto(proto) emojis.append(frag) yield frag elif _type == 4: frag = FragAt_pc.from_proto(proto) ats.append(frag) texts.append(frag) yield frag elif _type == 1: frag = FragLink_pc.from_proto(proto) links.append(frag) texts.append(frag) yield frag elif _type == 10: # voice frag = FragVoice_pc.from_proto(proto) nonlocal voice voice = frag yield frag # 35|36:tid=7769728331 / 37:tid=7760184147 elif _type in [35, 36, 37]: frag = FragTiebaPlus_pc.from_proto(proto) tiebapluses.append(frag) texts.append(frag) yield frag # outdated tiebaplus elif _type == 34: continue else: yield FragUnknown.from_proto(proto) objs = list(_frags()) return Contents_pc(objs, texts, emojis, ats, links, tiebapluses, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class UserInfo_p: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 level (int): 等级 glevel (int): 贴吧成长等级 gender (int): 性别 ip (str): ip归属地 icons (list[str]): 印记信息 is_bawu (bool): 是否吧务 is_vip (bool): 是否超级会员 is_god (bool): 是否大神 priv_like (PrivLike): 关注吧列表的公开状态 priv_reply (PrivReply): 帖子评论权限 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" level: int = 0 glevel: int = 0 gender: Gender = Gender.UNKNOWN ip: str = "" icons: list[str] = dcs.field(default_factory=list) is_bawu: bool = False is_vip: bool = False is_god: bool = False priv_like: PrivLike = PrivLike.PUBLIC priv_reply: PrivReply = PrivReply.ALL @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_p: user_id = data_proto.id portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.name nick_name_new = data_proto.name_show level = data_proto.level_id glevel = data_proto.user_growth.level_id gender = Gender(data_proto.gender) ip = data_proto.ip_address icons = [name for i in data_proto.iconinfo if (name := i.name)] is_bawu = bool(data_proto.is_bawu) is_vip = bool(data_proto.new_tshow_icon) is_god = bool(data_proto.new_god_data.status) priv_like = PrivLike(priv_like) if (priv_like := data_proto.priv_sets.like) else PrivLike.PUBLIC priv_reply = PrivReply(priv_reply) if (priv_reply := data_proto.priv_sets.reply) else PrivReply.ALL return UserInfo_p( user_id, portrait, user_name, nick_name_new, level, glevel, gender, ip, icons, is_bawu, is_vip, is_god, priv_like, priv_reply, ) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_p) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class Comment_p: """ 楼中楼信息 Attributes: text (str): 文本内容 contents (Contents_pc): 正文内容碎片列表 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 所在主题帖id ppid (int): 所在楼层id pid (int): 楼中楼id user (UserInfo_p): 发布者的用户信息 author_id (int): 发布者的user_id reply_to_id (int): 被回复者的user_id floor (int): 所在楼层数 agree (int): 点赞数 disagree (int): 点踩数 create_time (int): 创建时间 10位时间戳 以秒为单位 is_thread_author (bool): 是否楼主 """ contents: Contents_pc = dcs.field(default_factory=Contents_pc) fid: int = 0 fname: str = "" tid: int = 0 ppid: int = 0 pid: int = 0 user: UserInfo_p = dcs.field(default_factory=UserInfo_p) author_id: int = 0 reply_to_id: int = 0 floor: int = 0 agree: int = 0 disagree: int = 0 create_time: int = 0 is_thread_author: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> Comment_p: contents = Contents_pc.from_proto(data_proto) reply_to_id = 0 if contents: first_frag = contents[0] if ( isinstance(first_frag, FragText_p) and first_frag.text == "回复 " and (reply_to_id := data_proto.content[1].uid) ): reply_to_id = reply_to_id if isinstance(contents[1], FragAt_p): del contents.ats[0] contents.objs = contents.objs[2:] contents.texts = contents.texts[2:] if contents.texts: first_text_frag = contents.texts[0] first_text_frag.text = first_text_frag.text.removeprefix(" :") contents = contents pid = data_proto.id author_id = data_proto.author_id agree = data_proto.agree.agree_num disagree = data_proto.agree.disagree_num create_time = data_proto.time return Comment_p( contents, 0, "", 0, 0, pid, None, author_id, reply_to_id, 0, agree, disagree, create_time, False ) def __eq__(self, obj: Comment_p) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @property def text(self) -> str: return self.contents.text @dcs.dataclass class Post: """ 楼层信息 Attributes: text (str): 文本内容 contents (Contents_p): 正文内容碎片列表 sign (str): 小尾巴文本内容 comments (list[Comment_p]): 楼中楼列表 is_aimeme (bool): 是否是AI生成的表情包 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 所在主题帖id pid (int): 回复id user (UserInfo_p): 发布者的用户信息 author_id (int): 发布者的user_id floor (int): 楼层数 reply_num (int): 楼中楼数 agree (int): 点赞数 disagree (int): 点踩数 create_time (int): 创建时间 is_thread_author (bool): 是否楼主 """ contents: Contents_p = dcs.field(default_factory=Contents_p) sign: str = "" comments: list[Comment_p] = dcs.field(default_factory=list) is_aimeme: bool = False fid: int = 0 fname: str = "" tid: int = 0 pid: int = 0 user: UserInfo_p = dcs.field(default_factory=UserInfo_p) author_id: int = 0 floor: int = 0 reply_num: int = 0 agree: int = 0 disagree: int = 0 create_time: int = 0 is_thread_author: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> Post: contents = Contents_p.from_proto(data_proto) sign = "".join(p.text for p in data_proto.signature.content if p.type == 0) comments = [Comment_p.from_proto(p) for p in data_proto.sub_post_list.sub_post_list] is_aimeme = bool(data_proto.sprite_meme_info.meme_id) pid = data_proto.id author_id = data_proto.author_id floor = data_proto.floor reply_num = data_proto.sub_post_number agree = data_proto.agree.agree_num disagree = data_proto.agree.disagree_num create_time = data_proto.time return Post( contents, sign, comments, is_aimeme, 0, "", 0, pid, None, author_id, floor, reply_num, agree, disagree, create_time, False, ) def __eq__(self, obj: Post) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @cached_property def text(self) -> str: if self.sign: text = f"{self.contents.text}\n{self.sign}" else: text = self.contents.text return text @dcs.dataclass class Page_p: """ 页信息 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_proto(data_proto: TypeMessage) -> Page_p: 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_p(page_size, current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class Forum_p: """ 吧信息 Attributes: fid (int): 贴吧id fname (str): 贴吧名 category (str): 一级分类 subcategory (str): 二级分类 member_num (int): 吧会员数 post_num (int): 发帖量 """ fid: int = 0 fname: str = "" category: str = "" subcategory: str = "" member_num: int = 0 post_num: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> Forum_p: fid = data_proto.id fname = data_proto.name category = data_proto.first_class subcategory = data_proto.second_class member_num = data_proto.member_num post_num = data_proto.post_num return Forum_p(fid, fname, category, subcategory, member_num, post_num) @dcs.dataclass class FragImage_pt: """ 图像碎片 Attributes: src (str): 小图链接 宽580px big_src (str): 大图链接 宽720px origin_src (str): 原图链接 show_width (int): 图像在客户端预览显示的宽度 show_height (int): 图像在客户端预览显示的高度 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) big_src: str = dcs.field(default="", repr=False) origin_src: str = dcs.field(default="", repr=False) show_width: int = 0 show_height: int = 0 hash: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragImage_pt: src = data_proto.water_pic big_src = data_proto.small_pic origin_src = data_proto.big_pic show_width = data_proto.width show_height = data_proto.height if hash_obj := _IMAGEHASH_EXP.search(src): hash_ = hash_obj.group(1) else: hash_ = "" return FragImage_pt(src, big_src, origin_src, show_width, show_height, hash_) @dcs.dataclass class Contents_pt(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 emojis (list[FragEmoji_pt]): 表情碎片列表 imgs (list[FragImage_pt]): 图像碎片列表 ats (list[FragAt_pt]): @碎片列表 links (list[FragLink_pt]): 链接碎片列表 tiebapluses (list[FragTiebaPlus_pt]): 贴吧plus碎片列表 video (FragVideo_pt): 视频碎片 voice (FragVoice_pt): 音频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) emojis: list[FragEmoji_pt] = dcs.field(default_factory=list, repr=False) imgs: list[FragImage_pt] = dcs.field(default_factory=list, repr=False) ats: list[FragAt_pt] = dcs.field(default_factory=list, repr=False) links: list[FragLink_pt] = dcs.field(default_factory=list, repr=False) tiebapluses: list[FragTiebaPlus_pt] = dcs.field(default_factory=list, repr=False) video: FragVideo_pt = dcs.field(default_factory=FragVideo_pt, repr=False) voice: FragVoice_pt = dcs.field(default_factory=FragVoice_pt, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_pt: content_protos = data_proto.content texts = [] emojis = [] imgs = [FragImage_pt.from_proto(p) for p in data_proto.media] ats = [] links = [] tiebapluses = [] def _frags(): for proto in content_protos: _type = proto.type # 0纯文本 9电话号 18话题 27百科词条 if _type in [0, 9, 18, 27]: frag = FragText_pt.from_proto(proto) texts.append(frag) yield frag # 11:tid=5047676428 elif _type in [2, 11]: frag = FragEmoji_pt.from_proto(proto) emojis.append(frag) yield frag elif _type == 4: frag = FragAt_pt.from_proto(proto) ats.append(frag) texts.append(frag) yield frag elif _type == 1: frag = FragLink_pt.from_proto(proto) links.append(frag) texts.append(frag) yield frag # 35|36:tid=7769728331 / 37:tid=7760184147 elif _type in [35, 36, 37]: frag = FragTiebaPlus_pt.from_proto(proto) tiebapluses.append(frag) texts.append(frag) yield frag # outdated tiebaplus elif _type == 34: continue else: yield FragUnknown.from_proto(proto) objs = list(_frags()) if ats: del ats[0] del objs[0] objs += imgs if data_proto.video_info.video_width: video = FragVideo_pt.from_proto(data_proto.video_info) objs.append(video) else: video = FragVideo_pt() if data_proto.voice_info: voice = FragVoice_pt.from_proto(data_proto.voice_info[0]) objs.append(voice) else: voice = FragVoice_pt() return Contents_pt(objs, texts, emojis, imgs, ats, links, tiebapluses, video, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class UserInfo_pt: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 level (int): 等级 glevel (int): 贴吧成长等级 ip (str): ip归属地 icons (list[str]): 印记信息 is_bawu (bool): 是否吧务 is_vip (bool): 是否超级会员 is_god (bool): 是否大神 priv_like (PrivLike): 关注吧列表的公开状态 priv_reply (PrivReply): 帖子评论权限 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" level: int = 0 glevel: int = 0 ip: str = "" icons: list[str] = dcs.field(default_factory=list) is_bawu: bool = False is_vip: bool = False is_god: bool = False priv_like: PrivLike = PrivLike.PUBLIC priv_reply: PrivReply = PrivReply.ALL @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_pt: user_id = data_proto.id portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.name nick_name_new = data_proto.name_show level = data_proto.level_id glevel = data_proto.user_growth.level_id ip = data_proto.ip_address icons = [name for i in data_proto.iconinfo if (name := i.name)] is_bawu = bool(data_proto.is_bawu) is_vip = bool(data_proto.new_tshow_icon) is_god = bool(data_proto.new_god_data.status) priv_like = PrivLike(priv_like) if (priv_like := data_proto.priv_sets.like) else PrivLike.PUBLIC priv_reply = PrivReply(priv_reply) if (priv_reply := data_proto.priv_sets.reply) else PrivReply.ALL return UserInfo_pt( user_id, portrait, user_name, nick_name_new, level, glevel, ip, icons, is_bawu, is_vip, is_god, priv_like, priv_reply, ) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_pt) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class ShareThread_pt: """ 被分享的主题帖信息 Attributes: text (str): 文本内容 contents (Contents_pt): 正文内容碎片列表 title (str): 标题内容 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 主题帖tid author_id (int): 发布者的user_id vote_info (VoteInfo): 投票内容 """ contents: Contents_pt = dcs.field(default_factory=Contents_pt) title: str = "" fid: int = 0 fname: str = "" tid: int = 0 author_id: int = 0 vote_info: VoteInfo = dcs.field(default_factory=VoteInfo) @staticmethod def from_proto(data_proto: TypeMessage) -> ShareThread_pt: contents = Contents_pt.from_proto(data_proto) title = data_proto.title fid = data_proto.fid fname = data_proto.fname tid = int(tid) if (tid := data_proto.tid) else 0 author_id = data_proto.content[0].uid if data_proto.content else 0 vote_info = VoteInfo.from_proto(data_proto.poll_info) return ShareThread_pt(contents, title, fid, fname, tid, author_id, vote_info) def __eq__(self, obj: ShareThread_pt) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @cached_property def text(self) -> str: if self.title: text = f"{self.title}\n{self.contents.text}" else: text = self.contents.text return text @dcs.dataclass class Thread_p: """ 主题帖信息 Attributes: text (str): 文本内容 contents (Contents_pt): 正文内容碎片列表 title (str): 标题内容 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 主题帖tid pid (int): 首楼回复pid user (UserInfo_pt): 发布者的用户信息 author_id (int): 发布者的user_id type (ThreadType): 帖子类型 is_share (bool): 是否分享帖 is_help (bool): 是否为求助帖 vote_info (VoteInfo): 投票信息 share_origin (ShareThread_pt): 转发来的原帖内容 view_num (int): 浏览量 reply_num (int): 回复数 share_num (int): 分享数 agree (int): 点赞数 disagree (int): 点踩数 create_time (int): 创建时间 10位时间戳 以秒为单位 """ contents: Contents_pt = dcs.field(default_factory=Contents_pt) title: str = "" fid: int = 0 fname: str = "" tid: int = 0 pid: int = 0 user: UserInfo_pt = dcs.field(default_factory=UserInfo_pt) type: ThreadType = ThreadType.UNKNOWN is_share: bool = False vote_info: VoteInfo = dcs.field(default_factory=VoteInfo) share_origin: ShareThread_pt = dcs.field(default_factory=ShareThread_pt) view_num: int = 0 reply_num: int = 0 share_num: int = 0 agree: int = 0 disagree: int = 0 create_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> Thread_p: thread_proto = data_proto.thread title = thread_proto.title tid = thread_proto.id pid = thread_proto.post_id user = UserInfo_pt.from_proto(thread_proto.author) type_ = ThreadType(thread_proto.thread_type) if type_ == ThreadType.UNKNOWN: LOG().debug("Unknown thread type. tid=%d, type=%s", tid, data_proto.thread_type) is_share = bool(thread_proto.is_share_thread) view_num = data_proto.thread_freq_num reply_num = thread_proto.reply_num share_num = thread_proto.share_num agree = thread_proto.agree.agree_num disagree = thread_proto.agree.disagree_num create_time = thread_proto.create_time if not is_share: real_thread_proto = thread_proto.origin_thread_info contents = Contents_pt.from_proto(real_thread_proto) vote_info = VoteInfo.from_proto(real_thread_proto.poll_info) share_origin = ShareThread_pt() else: contents = Contents_pt() vote_info = VoteInfo() share_origin = ShareThread_pt.from_proto(thread_proto.origin_thread_info) return Thread_p( contents, title, 0, "", tid, pid, user, type_, is_share, vote_info, share_origin, view_num, reply_num, share_num, agree, disagree, create_time, ) def __eq__(self, obj: Thread_p) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @property def text(self) -> str: if self.title: text = f"{self.title}\n{self.contents.text}" else: text = self.contents.text return text @property def author_id(self) -> int: return self.user.user_id @property @deprecated("使用 thread.type == ThreadType.HELP 作为替代") def is_help(self) -> bool: return self.type == ThreadType.HELP @dcs.dataclass class Posts(TbErrorExt, Containers[Post]): """ 回复列表 Attributes: objs (list[Post]): 回复列表 err (Exception | None): 捕获的异常 page (Page_p): 页信息 has_more (bool): 是否还有下一页 forum (Forum_p): 所在吧信息 thread (Thread_p): 所在主题帖信息 """ page: Page_p = dcs.field(default_factory=Page_p) forum: Forum_p = dcs.field(default_factory=Forum_p) thread: Thread_p = dcs.field(default_factory=Thread_p) @staticmethod def from_proto(data_proto: TypeMessage) -> Posts: page = Page_p.from_proto(data_proto.page) forum = Forum_p.from_proto(data_proto.forum) thread = Thread_p.from_proto(data_proto) thread.fid = forum.fid thread.fname = forum.fname objs = [Post.from_proto(p) for p in data_proto.post_list if not p.chat_content.bot_uk] users = {p.id: UserInfo_p.from_proto(p) for p in data_proto.user_list} for post in objs: post.fid = forum.fid post.fname = forum.fname post.tid = thread.tid post.user = users[post.author_id] post.is_thread_author = thread.author_id == post.author_id for comment in post.comments: comment.fid = post.fid comment.fname = post.fname comment.tid = post.tid comment.ppid = post.pid comment.floor = post.floor comment.user = users[comment.author_id] comment.is_thread_author = thread.author_id == comment.author_id return Posts(objs, page, forum, thread) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_posts/protobuf/PbPageReqIdl.proto ================================================ // tbclient.PbPage.PbPageReqIdl syntax = "proto3"; import "CommonReq.proto"; message PbPageReqIdl { message DataReq { CommonReq common = 25; int64 kz = 4; int32 lz = 5; int32 r = 6; int64 pid = 7; int32 with_floor = 8; int32 floor_rn = 9; int32 rn = 13; int32 pn = 18; int32 floor_sort_type = 74; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_posts/protobuf/PbPageReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x12PbPageReqIdl.proto\x1a\x0f\x43ommonReq.proto"\xe2\x01\n\x0cPbPageReqIdl\x12#\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x15.PbPageReqIdl.DataReq\x1a\xac\x01\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x19 \x01(\x0b\x32\n.CommonReq\x12\n\n\x02kz\x18\x04 \x01(\x03\x12\n\n\x02lz\x18\x05 \x01(\x05\x12\t\n\x01r\x18\x06 \x01(\x05\x12\x0b\n\x03pid\x18\x07 \x01(\x03\x12\x12\n\nwith_floor\x18\x08 \x01(\x05\x12\x10\n\x08\x66loor_rn\x18\t \x01(\x05\x12\n\n\x02rn\x18\r \x01(\x05\x12\n\n\x02pn\x18\x12 \x01(\x05\x12\x17\n\x0f\x66loor_sort_type\x18J \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "PbPageReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PBPAGEREQIDL"]._serialized_start = 40 _globals["_PBPAGEREQIDL"]._serialized_end = 266 _globals["_PBPAGEREQIDL_DATAREQ"]._serialized_start = 94 _globals["_PBPAGEREQIDL_DATAREQ"]._serialized_end = 266 ================================================ FILE: src/aiotieba/api/get_posts/protobuf/PbPageResIdl.proto ================================================ // tbclient.PbPage.PbPageResIdl syntax = "proto3"; import "Error.proto"; import "SimpleForum.proto"; import "Page.proto"; import "Post.proto"; import "ThreadInfo.proto"; import "User.proto"; message PbPageResIdl { Error error = 1; message DataRes { SimpleForum forum = 2; Page page = 3; repeated Post post_list = 6; ThreadInfo thread = 8; repeated User user_list = 13; int64 thread_freq_num = 37; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_posts/protobuf/PbPageResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import Page_pb2 as Page__pb2 from ..._protobuf import Post_pb2 as Post__pb2 from ..._protobuf import SimpleForum_pb2 as SimpleForum__pb2 from ..._protobuf import ThreadInfo_pb2 as ThreadInfo__pb2 from ..._protobuf import User_pb2 as User__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x12PbPageResIdl.proto\x1a\x0b\x45rror.proto\x1a\x11SimpleForum.proto\x1a\nPage.proto\x1a\nPost.proto\x1a\x10ThreadInfo.proto\x1a\nUser.proto"\xf2\x01\n\x0cPbPageResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12#\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x15.PbPageResIdl.DataRes\x1a\xa5\x01\n\x07\x44\x61taRes\x12\x1b\n\x05\x66orum\x18\x02 \x01(\x0b\x32\x0c.SimpleForum\x12\x13\n\x04page\x18\x03 \x01(\x0b\x32\x05.Page\x12\x18\n\tpost_list\x18\x06 \x03(\x0b\x32\x05.Post\x12\x1b\n\x06thread\x18\x08 \x01(\x0b\x32\x0b.ThreadInfo\x12\x18\n\tuser_list\x18\r \x03(\x0b\x32\x05.User\x12\x17\n\x0fthread_freq_num\x18% \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "PbPageResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PBPAGERESIDL"]._serialized_start = 109 _globals["_PBPAGERESIDL"]._serialized_end = 351 _globals["_PBPAGERESIDL_DATARES"]._serialized_start = 186 _globals["_PBPAGERESIDL_DATARES"]._serialized_end = 351 ================================================ FILE: src/aiotieba/api/get_rank_forums/__init__.py ================================================ from ._api import parse_body, request from ._classdef import RankForum, RankForums ================================================ FILE: src/aiotieba/api/get_rank_forums/_api.py ================================================ import bs4 import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...enums import RankForumType from ._classdef import RankForums def parse_body(body: bytes) -> RankForums: soup = bs4.BeautifulSoup(body, "lxml") rank_forums = RankForums.from_xml(soup) return rank_forums async def request(http_core: HttpCore, fname: str, pn: int, rank_type: RankForumType) -> RankForums: params = [ ("kw", fname), ("type", int(rank_type)), ("pn", pn), ("ie", "utf-8"), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/sign/index"), params ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_rank_forums/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: import bs4 @dcs.dataclass class RankForum: """ 吧签到排名 Attributes: fname (str): 吧名 sign_num (int): 签到用户数 member_num (int): 总用户数 has_bawu (bool): 是否有吧务 """ fname: str = "" sign_num: int = 0 member_num: int = 0 has_bawu: bool = False @staticmethod def from_xml(data_tag: bs4.element.Tag) -> RankForum: rank_idx_item = data_tag.td fname_item = rank_idx_item.find_next_sibling("td") fname = fname_item.text sign_num_item = fname_item.find_next_sibling("td") sign_num = int(sign_num_item.text) member_num_item = sign_num_item.find_next_sibling("td") member_num = int(member_num_item.text) manager_item = member_num_item.find_next_sibling("td", class_="clearfix") manager_status_item = manager_item.div has_bawu = manager_status_item["class"][0] != "no_bawu" return RankForum(fname, sign_num, member_num, has_bawu) @dcs.dataclass class Page_rankforum: """ 页信息 Attributes: current_page (int): 当前页码 total_page (int): 总页码 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 total_page: int = 0 has_more: bool = False has_prev: bool = False @staticmethod def from_xml(data_soup: bs4.BeautifulSoup) -> Page_rankforum: pages_item = data_soup.find("div", class_="pagination") current_page_item = pages_item.span current_page = int(current_page_item.text) total_page_item = pages_item.find_all("a")[-1] total_page_url: str = total_page_item["href"] total_page_str = total_page_url[total_page_url.rfind("pn=") + 3 :] total_page = int(total_page_str) has_more = current_page < total_page has_prev = current_page > 1 return Page_rankforum(current_page, total_page, has_more, has_prev) @dcs.dataclass class RankForums(TbErrorExt, Containers[RankForum]): """ 吧签到排名表 Attributes: objs (list[RankForum]): 吧签到排名表 err (Exception | None): 捕获的异常 page (Page_rankforum): 页信息 has_more (bool): 是否还有下一页 """ page: Page_rankforum = dcs.field(default_factory=Page_rankforum) @staticmethod def from_xml(data_soup: bs4.BeautifulSoup) -> RankForums: dbgtbody = data_soup.find("table") objs = [RankForum.from_xml(t) for t in dbgtbody.find_all("tr", class_="j_rank_row")] page = Page_rankforum.from_xml(data_soup) return RankForums(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_rank_users/__init__.py ================================================ from ._api import parse_body, request from ._classdef import RankUser, RankUsers ================================================ FILE: src/aiotieba/api/get_rank_users/_api.py ================================================ import bs4 import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ._classdef import RankUsers def parse_body(body: bytes) -> RankUsers: soup = bs4.BeautifulSoup(body, "lxml") rank_users = RankUsers.from_xml(soup) return rank_users async def request(http_core: HttpCore, fname: str, pn: int) -> RankUsers: params = [ ("kw", fname), ("pn", pn), ("ie", "utf-8"), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/f/like/furank"), params ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_rank_users/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt from ...helper import parse_json from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping import bs4 @dcs.dataclass class RankUser: """ 等级排行榜用户信息 Attributes: user_name (str): 用户名 level (int): 等级 exp (int): 经验值 is_vip (bool): 是否超级会员 """ user_name: str = "" level: int = 0 exp: int = 0 is_vip: bool = False @staticmethod def from_xml(data_tag: bs4.element.Tag) -> RankUser: user_name_item = data_tag.td.next_sibling user_name = user_name_item.text is_vip = "drl_item_vip" in user_name_item.div["class"] level_item = user_name_item.next_sibling # e.g. get level 16 from "bg_lv16" by slicing [5:] level = int(level_item.div["class"][0][5:]) exp_item = level_item.next_sibling exp = int(exp_item.text) return RankUser(user_name, level, exp, is_vip) @dcs.dataclass class Page_rank: """ 页信息 Attributes: current_page (int): 当前页码 total_page (int): 总页码 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 total_page: int = 0 has_more: bool = False has_prev: int = False @staticmethod def from_json(data_map: Mapping) -> Page_rank: current_page = data_map["cur_page"] total_page = data_map["total_num"] has_more = current_page < total_page has_prev = current_page > 1 return Page_rank(current_page, total_page, has_more, has_prev) @dcs.dataclass class RankUsers(TbErrorExt, Containers[RankUser]): """ 等级排行榜用户列表 Attributes: objs (list[RankUser]): 等级排行榜用户列表 err (Exception | None): 捕获的异常 page (Page_rank): 页信息 has_more (bool): 是否还有下一页 """ page: Page_rank = dcs.field(default_factory=Page_rank) @staticmethod def from_xml(data_soup: bs4.BeautifulSoup) -> RankUsers: objs = [RankUser.from_xml(t) for t in data_soup("tr", class_=["drl_list_item", "drl_list_item_self"])] page_item = data_soup.find("ul", class_="p_rank_pager") page_dict = parse_json(page_item["data-field"]) page = Page_rank.from_json(page_dict) return RankUsers(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_recom_status/__init__.py ================================================ from ._api import parse_body, request from ._classdef import RecomStatus ================================================ FILE: src/aiotieba/api/get_recom_status/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import RecomStatus def parse_body(body: bytes) -> RecomStatus: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) status = RecomStatus.from_json(res_json) return status async def request(http_core: HttpCore, fid: int) -> RecomStatus: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("forum_id", fid), ("pn", 1), ("rn", 0), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/f/bawu/getRecomThreadList"), data ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_recom_status/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class RecomStatus(TbErrorExt): """ 大吧主推荐功能的月度配额状态 Attributes: err (Exception | None): 捕获的异常 total_recom_num (int): 本月总推荐配额 used_recom_num (int): 本月已使用的推荐配额 """ total_recom_num: int = 0 used_recom_num: int = 0 @staticmethod def from_json(data_map: Mapping) -> RecomStatus: total_recom_num = int(data_map["total_recommend_num"]) used_recom_num = int(data_map["used_recommend_num"]) return RecomStatus(total_recom_num, used_recom_num) ================================================ FILE: src/aiotieba/api/get_recover_info/__init__.py ================================================ from ._api import parse_body, request from ._classdef import RecoverInfo ================================================ FILE: src/aiotieba/api/get_recover_info/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import RecoverInfo def parse_body(body: bytes) -> RecoverInfo: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) data_map = res_json["data"] rec_info = RecoverInfo.from_json(data_map) return rec_info async def request(http_core: HttpCore, fid: int, tid: int, pid: int) -> RecoverInfo: params = [ ("forum_id", fid), ("thread_id", tid), ("post_id", pid), ("type", 1), ("sub_type", 2 if pid else 1), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/bawu/getRecoverInfo"), params ) body = await http_core.net_core.send_request(request, read_bufsize=16 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_recover_info/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers from .._classdef.contents import _IMAGEHASH_EXP, FragUnknown, TypeFragment, TypeFragText if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class FragText_ri: """ 纯文本碎片 Attributes: text (str): 文本内容 """ text: str = "" @staticmethod def from_json(data_map: Mapping) -> FragText_ri: text = data_map["value"] return FragText_ri(text) @dcs.dataclass class FragImage_ri: """ 图像碎片 Attributes: src (str): 小图链接 宽720px show_width (int): 图像在客户端预览显示的宽度 show_height (int): 图像在客户端预览显示的高度 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) show_width: int = 0 show_height: int = 0 hash: str = "" @staticmethod def from_json(data_map: Mapping) -> FragImage_ri: src = data_map["url"] show_width = int(data_map["width"]) show_height = int(data_map["height"]) hash_ = _IMAGEHASH_EXP.search(src).group(1) return FragImage_ri(src, show_width, show_height, hash_) @dcs.dataclass class Contents_ri(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 imgs (list[FragImage_t]): 图像碎片列表 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) imgs: list[FragImage_ri] = dcs.field(default_factory=list, repr=False) @staticmethod def from_json(data_map: Mapping) -> Contents_ri: content_maps = data_map["content_detail"] texts = [] imgs = [FragImage_ri.from_json(m) for m in data_map["all_pics"]] def _frags(): for cmap in content_maps: _type = cmap["type"] # 1纯文本 if _type == 1: frag = FragText_ri.from_json(cmap) texts.append(frag) yield frag elif _type == 3: continue else: yield FragUnknown.from_proto(cmap) objs = list(_frags()) objs += imgs return Contents_ri(objs, texts, imgs) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class UserInfo_ri: """ 用户信息 Attributes: portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ portrait: str = "" user_name: str = "" nick_name_new: str = "" @staticmethod def from_json(data_map: Mapping) -> UserInfo_ri: portrait = data_map["portrait"] if "?" in portrait: portrait = portrait[:-13] user_name = data_map["user_name"] nick_name_new = data_map["show_nickname"] return UserInfo_ri(portrait, user_name, nick_name_new) def __str__(self) -> str: return self.user_name or self.portrait def __eq__(self, obj: UserInfo_ri) -> bool: return self.portrait == obj.portrait def __hash__(self) -> int: return hash(self.portrait) def __bool__(self) -> bool: return bool(self.portrait) @property def nick_name(self) -> str: return self.nick_name_new @property def show_name(self) -> str: return self.nick_name_new or self.user_name @cached_property def log_name(self) -> str: return self.user_name or f"{self.nick_name_new}/{self.portrait}" @dcs.dataclass class RecoverInfo(TbErrorExt): """ 待恢复帖子信息 Attributes: text (str): 文本内容 contents (Contents_ri): 正文内容碎片列表 title (str): 标题内容 tid (int): 所在主题帖id pid (int): 回复id user (UserInfo_ri): 发布者的用户信息 """ contents: Contents_ri = dcs.field(default_factory=Contents_ri) title: str = "" tid: int = 0 pid: int = 0 user: UserInfo_ri = dcs.field(default_factory=UserInfo_ri) @staticmethod def from_json(data_map: Mapping) -> RecoverInfo: thread_info = data_map["thread_info"] contents = Contents_ri.from_json(thread_info) title = thread_info["title"] tid = thread_info["thread_id"] pid = thread_info["post_id"] user = UserInfo_ri.from_json(data_map["user_info"]) return RecoverInfo(contents, title, tid, pid, user) def __eq__(self, obj: RecoverInfo) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @cached_property def text(self) -> str: if self.title: text = f"{self.title}\n{self.contents.text}" else: text = self.contents.text return text ================================================ FILE: src/aiotieba/api/get_recovers/__init__.py ================================================ from ._api import parse_body, request from ._classdef import Recover, Recovers ================================================ FILE: src/aiotieba/api/get_recovers/_api.py ================================================ from __future__ import annotations from typing import TYPE_CHECKING import yarl from ...const import WEB_BASE_HOST from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import Recovers if TYPE_CHECKING: from ...core import HttpCore def parse_body(body: bytes) -> Recovers: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) recovers = Recovers.from_json(res_json) return recovers async def request(http_core: HttpCore, fid: int, user_id: int | None, pn: int, rn: int) -> Recovers: params = [ ("rn", rn), ("forum_id", fid), ("pn", pn), ("type", 1), ("sub_type", 1), ] if user_id: params.append(("uid", user_id)) request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/manage/getRecoverList"), params ) body = await http_core.net_core.send_request(request, read_bufsize=16 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_recovers/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class UserInfo_rec: """ 用户信息 Attributes: portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_name: str = "" portrait: str = "" nick_name_new: str = "" @staticmethod def from_json(data_map: Mapping) -> UserInfo_rec: portrait = data_map["portrait"] if "?" in portrait: portrait = portrait[:-13] user_name = data_map["user_name"] nick_name_new = data_map["user_nickname"] return UserInfo_rec(user_name, portrait, nick_name_new) def __str__(self) -> str: return self.user_name or self.portrait def __eq__(self, obj: UserInfo_rec) -> bool: return self.portrait == obj.portrait def __hash__(self) -> int: return hash(self.portrait) def __bool__(self) -> bool: return bool(self.portrait) @property def nick_name(self) -> str: return self.nick_name_new @property def show_name(self) -> str: return self.nick_name_new or self.user_name @cached_property def log_name(self) -> str: return self.user_name or f"{self.nick_name_new}/{self.portrait}" @dcs.dataclass class Recover: """ 待恢复帖子信息 Attributes: text (str): 文本内容 tid (int): 所在主题帖id pid (int): 回复id 若为主题帖则该字段为0 user (UserInfo_rec): 发布者的用户信息 op_show_name (str): 操作人显示名称 op_time (int): 操作时间 10位时间戳 以秒为单位 is_floor (bool): 是否为楼中楼 is_hide (bool): 是否为屏蔽 """ text: str = "" tid: int = 0 pid: int = 0 user: UserInfo_rec = dcs.field(default_factory=UserInfo_rec) op_show_name: str = "" op_time: int = 0 is_floor: bool = False is_hide: bool = False @staticmethod def from_json(data_map: Mapping) -> Recover: thread_info = data_map["thread_info"] tid = int(thread_info["tid"]) if post_info := data_map["post_info"]: text = post_info["abstract"] pid = int(post_info["pid"]) user = UserInfo_rec.from_json(post_info) else: text = thread_info["abstract"] pid = 0 user = UserInfo_rec.from_json(thread_info) is_floor = bool(data_map["is_foor"]) # 百度的Code Review主要起到一个装饰的作用 is_hide = bool(int(data_map["is_frs_mask"])) op_show_name = data_map["op_info"]["name"] op_time = int(data_map["op_info"]["time"]) return Recover(text, tid, pid, user, op_show_name, op_time, is_floor, is_hide) @dcs.dataclass class Page_recover: """ 页信息 Attributes: page_size (int): 页大小 current_page (int): 当前页码 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ page_size: int = 0 current_page: int = 0 has_more: bool = False has_prev: bool = False @staticmethod def from_json(data_map: Mapping) -> Page_recover: page_size = data_map["rn"] current_page = data_map["pn"] has_more = data_map["has_more"] has_prev = current_page > 1 return Page_recover(page_size, current_page, has_more, has_prev) @dcs.dataclass class Recovers(TbErrorExt, Containers[Recover]): """ 待恢复帖子列表 Attributes: objs (list[Recover]): 待恢复帖子列表 err (Exception | None): 捕获的异常 page (Page_recover): 页信息 has_more (bool): 是否还有下一页 """ page: Page_recover = dcs.field(default_factory=Page_recover) @staticmethod def from_json(data_map: Mapping) -> None: objs = [Recover.from_json(t) for t in data_map["data"]["thread_list"]] page = Page_recover.from_json(data_map["data"]["page"]) return Recovers(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_replys/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import Reply, Replys ================================================ FILE: src/aiotieba/api/get_replys/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import Account, HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import Replys from .protobuf import ReplyMeReqIdl_pb2, ReplyMeResIdl_pb2 CMD = 303007 def pack_proto(account: Account, pn: int) -> bytes: req_proto = ReplyMeReqIdl_pb2.ReplyMeReqIdl() req_proto.data.common.BDUSS = account.BDUSS req_proto.data.common._client_version = LATEST_VERSION req_proto.data.pn = str(pn) return req_proto.SerializeToString() def parse_body(proto: bytes) -> Replys: res_proto = ReplyMeResIdl_pb2.ReplyMeResIdl() res_proto.ParseFromString(proto) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data replys = Replys.from_proto(data_proto) return replys async def request_http(http_core: HttpCore, pn: int) -> Replys: data = pack_proto(http_core.account, pn) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/u/feed/replyme", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=16 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, pn: int) -> Replys: data = pack_proto(ws_core.account, pn) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_replys/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from ...enums import PrivLike, PrivReply from ...exception import TbErrorExt from .._classdef import Containers, TypeMessage @dcs.dataclass class UserInfo_reply: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 priv_like (PrivLike): 关注吧列表的公开状态 priv_reply (PrivReply): 帖子评论权限 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" priv_like: PrivLike = PrivLike.PUBLIC priv_reply: PrivReply = PrivReply.ALL @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_reply: user_id = data_proto.id portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.name nick_name_new = data_proto.name_show priv_like = PrivLike(priv_like) if (priv_like := data_proto.priv_sets.like) else PrivLike.PUBLIC priv_reply = PrivReply(priv_reply) if (priv_reply := data_proto.priv_sets.reply) else PrivReply.ALL return UserInfo_reply(user_id, portrait, user_name, nick_name_new, priv_like, priv_reply) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_reply) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class UserInfo_reply_p: """ 用户信息 Attributes: user_id (int): user_id user_name (str): 用户名 nick_name_new (str): 新版昵称 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 user_name: str = "" nick_name_new: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_reply_p: user_id = data_proto.id user_name = data_proto.name nick_name_new = data_proto.name_show return UserInfo_reply_p(user_id, user_name, nick_name_new) def __str__(self) -> str: return self.user_name or str(self.user_id) def __eq__(self, obj: UserInfo_reply_p) -> 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_new @property def show_name(self) -> str: return self.nick_name_new or self.user_name @cached_property def log_name(self) -> str: return self.user_name or f"{self.nick_name_new}/{self.user_id}" @dcs.dataclass class UserInfo_reply_t: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait nick_name_new (str): 新版昵称 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" nick_name_new: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_reply_t: user_id = data_proto.id portrait = data_proto.portrait nick_name_new = data_proto.name_show return UserInfo_reply_t(user_id, portrait, nick_name_new) def __str__(self) -> str: return self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_reply_t) -> 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_new @property def show_name(self) -> str: return self.nick_name_new @cached_property def log_name(self) -> str: return str(self.user_id) if not self.portrait else f"{self.nick_name_new}/{self.portrait}" @dcs.dataclass class Reply: """ 回复信息 Attributes: text (str): 文本内容 fname (str): 所在贴吧名 tid (int): 所在主题帖id ppid (int): 所在楼层pid pid (int): 回复id user (UserInfo_reply): 发布者的用户信息 author_id (int): 发布者的user_id post_user (UserInfo_reply_p): 楼层用户信息 thread_user (UserInfo_reply_t): 楼主用户信息 is_comment (bool): 是否楼中楼 create_time (int): 创建时间 10位时间戳 以秒为单位 """ text: str = "" fname: str = "" tid: int = 0 ppid: int = 0 pid: int = 0 user: UserInfo_reply = dcs.field(default_factory=UserInfo_reply) post_user: UserInfo_reply_p = dcs.field(default_factory=UserInfo_reply_p) thread_user: UserInfo_reply_t = dcs.field(default_factory=UserInfo_reply_t) is_comment: bool = False create_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> Reply: text = data_proto.content fname = data_proto.fname tid = data_proto.thread_id ppid = data_proto.quote_pid pid = data_proto.post_id user = UserInfo_reply.from_proto(data_proto.replyer) post_user = UserInfo_reply_p.from_proto(data_proto.quote_user) thread_user = UserInfo_reply_t.from_proto(data_proto.thread_author_user) is_comment = bool(data_proto.is_floor) create_time = data_proto.time return Reply(text, fname, tid, ppid, pid, user, post_user, thread_user, is_comment, create_time) def __eq__(self, obj: Reply) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @property def author_id(self) -> int: return self.user.user_id @dcs.dataclass class Page_reply: """ 页信息 Attributes: current_page (int): 当前页码 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 has_more: bool = False has_prev: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> Page_reply: current_page = data_proto.current_page has_more = bool(data_proto.has_more) has_prev = bool(data_proto.has_prev) return Page_reply(current_page, has_more, has_prev) @dcs.dataclass class Replys(TbErrorExt, Containers[Reply]): """ 收到回复列表 Attributes: objs (list[Reply]): 收到回复列表 err (Exception | None): 捕获的异常 page (Page_reply): 页信息 has_more (bool): 是否还有下一页 """ page: Page_reply = dcs.field(default_factory=Page_reply) @staticmethod def from_proto(data_proto: TypeMessage) -> Replys: objs = [Reply.from_proto(p) for p in data_proto.reply_list] page = Page_reply.from_proto(data_proto.page) return Replys(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_replys/protobuf/ReplyMeReqIdl.proto ================================================ // tbclient.ReplyMe.ReplyMeReqIdl syntax = "proto3"; import "CommonReq.proto"; message ReplyMeReqIdl { message DataReq { string pn = 1; CommonReq common = 3; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_replys/protobuf/ReplyMeReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x13ReplyMeReqIdl.proto\x1a\x0f\x43ommonReq.proto"h\n\rReplyMeReqIdl\x12$\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x16.ReplyMeReqIdl.DataReq\x1a\x31\n\x07\x44\x61taReq\x12\n\n\x02pn\x18\x01 \x01(\t\x12\x1a\n\x06\x63ommon\x18\x03 \x01(\x0b\x32\n.CommonReqb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "ReplyMeReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_REPLYMEREQIDL"]._serialized_start = 40 _globals["_REPLYMEREQIDL"]._serialized_end = 144 _globals["_REPLYMEREQIDL_DATAREQ"]._serialized_start = 95 _globals["_REPLYMEREQIDL_DATAREQ"]._serialized_end = 144 ================================================ FILE: src/aiotieba/api/get_replys/protobuf/ReplyMeResIdl.proto ================================================ // tbclient.ReplyMe.ReplyMeResIdl syntax = "proto3"; import "Error.proto"; import "Page.proto"; import "User.proto"; message ReplyMeResIdl { Error error = 1; message DataRes { Page page = 1; message ReplyList { uint64 thread_id = 1; uint64 post_id = 2; uint32 time = 3; string fname = 5; string content = 6; uint32 is_floor = 7; string quote_content = 8; User replyer = 9; uint64 quote_pid = 14; User quote_user = 15; User thread_author_user = 25; } repeated ReplyList reply_list = 2; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_replys/protobuf/ReplyMeResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import Page_pb2 as Page__pb2 from ..._protobuf import User_pb2 as User__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x13ReplyMeResIdl.proto\x1a\x0b\x45rror.proto\x1a\nPage.proto\x1a\nUser.proto"\x95\x03\n\rReplyMeResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12$\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x16.ReplyMeResIdl.DataRes\x1a\xc6\x02\n\x07\x44\x61taRes\x12\x13\n\x04page\x18\x01 \x01(\x0b\x32\x05.Page\x12\x34\n\nreply_list\x18\x02 \x03(\x0b\x32 .ReplyMeResIdl.DataRes.ReplyList\x1a\xef\x01\n\tReplyList\x12\x11\n\tthread_id\x18\x01 \x01(\x04\x12\x0f\n\x07post_id\x18\x02 \x01(\x04\x12\x0c\n\x04time\x18\x03 \x01(\r\x12\r\n\x05\x66name\x18\x05 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x06 \x01(\t\x12\x10\n\x08is_floor\x18\x07 \x01(\r\x12\x15\n\rquote_content\x18\x08 \x01(\t\x12\x16\n\x07replyer\x18\t \x01(\x0b\x32\x05.User\x12\x11\n\tquote_pid\x18\x0e \x01(\x04\x12\x19\n\nquote_user\x18\x0f \x01(\x0b\x32\x05.User\x12!\n\x12thread_author_user\x18\x19 \x01(\x0b\x32\x05.Userb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "ReplyMeResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_REPLYMERESIDL"]._serialized_start = 61 _globals["_REPLYMERESIDL"]._serialized_end = 466 _globals["_REPLYMERESIDL_DATARES"]._serialized_start = 140 _globals["_REPLYMERESIDL_DATARES"]._serialized_end = 466 _globals["_REPLYMERESIDL_DATARES_REPLYLIST"]._serialized_start = 227 _globals["_REPLYMERESIDL_DATARES_REPLYLIST"]._serialized_end = 466 ================================================ FILE: src/aiotieba/api/get_roomlist_by_fid/__init__.py ================================================ from ._api import parse_body, request from ._classdef import RoomList ================================================ FILE: src/aiotieba/api/get_roomlist_by_fid/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, CHAT_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import RoomList def parse_body(body: bytes) -> RoomList: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) roomlist = RoomList.from_json(res_json) return roomlist async def request(http_core: HttpCore, fid: int) -> RoomList: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", CHAT_VERSION), ("call_from", "frs"), ("fid", fid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/f/chat/getRoomListByFid"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_roomlist_by_fid/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs @dcs.dataclass class RoomList: """ 某吧的聊天室列表 Attributes: room_list (list[dict]): 每个聊天室的json内容 """ room_list: list @staticmethod def from_json(resjson: dict) -> RoomList: # TODO: 解析json并参数化而不是直接返回 room_list = [] for x in resjson["data"]["list"]: room_list.extend(x["room_list"]) return RoomList(room_list) ================================================ FILE: src/aiotieba/api/get_self_follow_forums/__init__.py ================================================ from ._api import parse_body, request from ._classdef import SelfFollowForum, SelfFollowForums ================================================ FILE: src/aiotieba/api/get_self_follow_forums/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import SelfFollowForums def parse_body(body: bytes) -> SelfFollowForums: res_json = parse_json(body) if code := res_json["error_code"]: raise TiebaServerError(code, res_json["error_msg"]) self_follow_forums = SelfFollowForums.from_json(res_json) return self_follow_forums async def request(http_core: HttpCore, pn: int, rn: int) -> SelfFollowForums: data = [ ("tbs", http_core.account.tbs), ("sort_type", 3), ("call_from", 3), ("page_no", pn), ("res_num", rn), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/c/f/forum/forumGuide"), data, extra_headers=[("Subapp-Type", "hybrid")], ) body = await http_core.net_core.send_request(request, read_bufsize=64 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_self_follow_forums/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class SelfFollowForum: """ 吧基本信息 Attributes: fid (int): 贴吧id fname (str): 贴吧名 level (int): 用户等级 is_signed (bool): 是否已签到 """ fid: int = 0 fname: str = "" level: int = 0 is_signed: bool = False @staticmethod def from_json(data_map: Mapping) -> SelfFollowForum: fid = data_map["forum_id"] fname = data_map["forum_name"] level = data_map["level_id"] is_signed = data_map["is_sign"] return SelfFollowForum(fid, fname, level, is_signed) @dcs.dataclass class SelfFollowForums(TbErrorExt, Containers[SelfFollowForum]): """ 本账号关注贴吧列表 Attributes: objs (list[SelfFollowForum]): 本账号关注贴吧列表 err (Exception | None): 捕获的异常 has_more (bool): 是否还有下一页 """ has_more: bool = False @staticmethod def from_json(data_map: Mapping) -> SelfFollowForums: objs = [SelfFollowForum.from_json(m) for m in data_map["like_forum"]] has_more = data_map["like_forum_has_more"] return SelfFollowForums(objs, has_more) ================================================ FILE: src/aiotieba/api/get_self_follow_forums_v1/__init__.py ================================================ from ._api import parse_body, request from ._classdef import SelfFollowForumsV1, SelfFollowForumV1 ================================================ FILE: src/aiotieba/api/get_self_follow_forums_v1/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import SelfFollowForumsV1 def parse_body(body: bytes) -> SelfFollowForumsV1: res_json = parse_json(body) if code := res_json["errno"]: raise TiebaServerError(code, res_json["errmsg"]) data_map = res_json["data"]["like_forum"] self_follow_forums = SelfFollowForumsV1.from_json(data_map) return self_follow_forums async def request(http_core: HttpCore, pn: int, rn: int) -> SelfFollowForumsV1: params = [ ("pn", pn), ("rn", rn), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mg/o/getForumHome"), params ) body = await http_core.net_core.send_request(request, read_bufsize=128 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_self_follow_forums_v1/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class SelfFollowForumV1: """ 吧基本信息 Attributes: fid (int): 贴吧id fname (str): 贴吧名 level (int): 用户等级 """ fid: int = 0 fname: str = "" level: int = 0 @staticmethod def from_json(data_map: Mapping) -> SelfFollowForumV1: fid = data_map["forum_id"] fname = data_map["forum_name"] level = data_map["level_id"] return SelfFollowForumV1(fid, fname, level) @dcs.dataclass class Page_sforumV1: """ 页信息 Attributes: current_page (int): 当前页码 total_page (int): 总页码 has_more (bool): 是否有后继页 has_prev (bool): 是否有前驱页 """ current_page: int = 0 total_page: int = 0 has_more: bool = False has_prev: bool = False @staticmethod def from_json(data_map: Mapping) -> Page_sforumV1: current_page = data_map["cur_page"] total_page = data_map["total_page"] has_more = current_page < total_page has_prev = current_page > 1 return Page_sforumV1(current_page, total_page, has_more, has_prev) @dcs.dataclass class SelfFollowForumsV1(TbErrorExt, Containers[SelfFollowForumV1]): """ 本账号关注贴吧列表 Attributes: objs (list[SelfFollowForum]): 本账号关注贴吧列表 err (Exception | None): 捕获的异常 page (Page_sforum): 页信息 has_more (bool): 是否还有下一页 """ page: Page_sforumV1 = dcs.field(default_factory=Page_sforumV1) @staticmethod def from_json(data_map: Mapping) -> SelfFollowForumsV1: objs = [SelfFollowForumV1.from_json(m) for m in data_map["list"]] page = Page_sforumV1.from_json(data_map["page"]) return SelfFollowForumsV1(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_selfinfo_initNickname/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/get_selfinfo_initNickname/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import UserInfo_selfinit def parse_body(body: bytes) -> UserInfo_selfinit: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) user_dict = res_json["user_info"] user = UserInfo_selfinit.from_json(user_dict) return user async def request(http_core: HttpCore) -> UserInfo_selfinit: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/s/initNickname"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_selfinfo_initNickname/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class UserInfo_selfinit: """ 用户信息 Attributes: user_name (str): 用户名 nick_name_old (str): 旧版昵称 tieba_uid (int): 用户个人主页uid nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_name: str = "" nick_name_old: str = "" tieba_uid: int = 0 @staticmethod def from_json(data_map: Mapping) -> UserInfo_selfinit: user_name = data_map["user_name"] nick_name_old = data_map["name_show"] tieba_uid = data_map["tieba_uid"] return UserInfo_selfinit(user_name, nick_name_old, tieba_uid) def __str__(self) -> str: return self.user_name def __eq__(self, obj: UserInfo_selfinit) -> bool: return self.tieba_uid == obj.tieba_uid def __hash__(self) -> int: return self.tieba_uid def __bool__(self) -> bool: return bool(self.tieba_uid) @property def nick_name(self) -> str: return self.nick_name_old @cached_property def log_name(self) -> str: return self.user_name or f"{self.nick_name_old}/{self.tieba_uid}" ================================================ FILE: src/aiotieba/api/get_selfinfo_moindex/__init__.py ================================================ from ._api import parse_body, request from ._classdef import UserInfo_moindex ================================================ FILE: src/aiotieba/api/get_selfinfo_moindex/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import UserInfo_moindex def parse_body(body: bytes) -> UserInfo_moindex: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) user_dict = res_json["data"] user = UserInfo_moindex.from_json(user_dict) return user async def request(http_core: HttpCore) -> UserInfo_moindex: params = [("need_user", 1)] request = http_core.pack_web_get_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/newmoindex"), params ) body = await http_core.net_core.send_request(request, read_bufsize=4 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_selfinfo_moindex/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...enums import Gender if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class UserInfo_moindex: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 gender (Gender): 性别 post_num (int): 发帖数 fan_num (int): 粉丝数 follow_num (int): 关注数 forum_num (int): 关注贴吧数 sign (str): 个性签名 is_vip (bool): 是否超级会员 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" gender: Gender = Gender.UNKNOWN post_num: int = 0 fan_num: int = 0 follow_num: int = 0 forum_num: int = 0 sign: str = "" is_vip: bool = False @staticmethod def from_json(data_map: Mapping) -> UserInfo_moindex: user_id = data_map["id"] portrait = data_map["portrait"] user_name = data_map["name"] gender = Gender(data_map["user_sex"]) post_num = data_map["post_num"] fan_num = data_map["fans_num"] follow_num = data_map["concern_num"] forum_num = data_map["like_forum_num"] sign = data_map["intro"] if vip_dict := data_map["vipInfo"]: is_vip = int(vip_dict["v_status"]) == 3 else: is_vip = False return UserInfo_moindex( user_id, portrait, user_name, gender, post_num, fan_num, follow_num, forum_num, sign, is_vip ) def __str__(self) -> str: return self.user_name or self.portrait def __eq__(self, obj: UserInfo_moindex) -> bool: return self.user_id == obj.user_id def __hash__(self) -> int: return self.user_id def __bool__(self) -> bool: return self.user_id @cached_property def log_name(self) -> str: return self.user_name or self.portrait ================================================ FILE: src/aiotieba/api/get_square_forums/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import SquareForum, SquareForums ================================================ FILE: src/aiotieba/api/get_square_forums/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import Account, HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import SquareForums from .protobuf import GetForumSquareReqIdl_pb2, GetForumSquareResIdl_pb2 CMD = 309653 def pack_proto(account: Account, cname: str, pn: int, rn: int) -> bytes: req_proto = GetForumSquareReqIdl_pb2.GetForumSquareReqIdl() req_proto.data.common.BDUSS = account.BDUSS req_proto.data.common._client_version = LATEST_VERSION req_proto.data.class_name = cname req_proto.data.pn = pn req_proto.data.rn = rn return req_proto.SerializeToString() def parse_body(body: bytes) -> SquareForums: res_proto = GetForumSquareResIdl_pb2.GetForumSquareResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data square_forums = SquareForums.from_proto(data_proto) return square_forums async def request_http(http_core: HttpCore, cname: str, pn: int, rn: int) -> SquareForums: data = pack_proto(http_core.account, cname, pn, rn) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/f/forum/getForumSquare", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=16 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, cname: str, pn: int, rn: int) -> SquareForums: data = pack_proto(ws_core.account, cname, pn, rn) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_square_forums/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from ...exception import TbErrorExt from .._classdef import Containers, TypeMessage @dcs.dataclass class SquareForum: """ 吧广场贴吧信息 Attributes: fid (int): 贴吧id fname (str): 贴吧名 member_num (int): 吧会员数 post_num (int): 发帖量 is_followed (bool): 是否已关注 """ fid: int = 0 fname: str = "" member_num: int = 0 post_num: int = 0 is_followed: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> SquareForum: fid = data_proto.forum_id fname = data_proto.forum_name member_num = data_proto.member_count post_num = data_proto.thread_count is_followed = bool(data_proto.is_like) return SquareForum(fid, fname, member_num, post_num, is_followed) def __eq__(self, obj: SquareForum) -> bool: return self.fid == obj.fid def __hash__(self) -> int: return self.fid @dcs.dataclass class Page_square: """ 页信息 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_proto(data_proto: TypeMessage) -> Page_square: 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_square(page_size, current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class SquareForums(TbErrorExt, Containers[SquareForum]): """ 吧广场列表 Attributes: objs (list[SquareForum]): 吧广场列表 err (Exception | None): 捕获的异常 page (Page_square): 页信息 has_more (bool): 是否还有下一页 """ page: Page_square = dcs.field(default_factory=Page_square) @staticmethod def from_proto(data_proto: TypeMessage | None = None) -> None: objs = [SquareForum.from_proto(p) for p in data_proto.forum_info] page = Page_square.from_proto(data_proto.page) return SquareForums(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_square_forums/protobuf/GetForumSquareReqIdl.proto ================================================ // tbclient.GetForumSquare.GetForumSquareReqIdl syntax = "proto3"; import "CommonReq.proto"; message GetForumSquareReqIdl { message DataReq { CommonReq common = 1; string class_name = 2; int32 pn = 3; int32 rn = 4; int64 user_id = 5; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_square_forums/protobuf/GetForumSquareReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1aGetForumSquareReqIdl.proto\x1a\x0f\x43ommonReq.proto"\xa7\x01\n\x14GetForumSquareReqIdl\x12+\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1d.GetForumSquareReqIdl.DataReq\x1a\x62\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\n.CommonReq\x12\x12\n\nclass_name\x18\x02 \x01(\t\x12\n\n\x02pn\x18\x03 \x01(\x05\x12\n\n\x02rn\x18\x04 \x01(\x05\x12\x0f\n\x07user_id\x18\x05 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetForumSquareReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETFORUMSQUAREREQIDL"]._serialized_start = 48 _globals["_GETFORUMSQUAREREQIDL"]._serialized_end = 215 _globals["_GETFORUMSQUAREREQIDL_DATAREQ"]._serialized_start = 117 _globals["_GETFORUMSQUAREREQIDL_DATAREQ"]._serialized_end = 215 ================================================ FILE: src/aiotieba/api/get_square_forums/protobuf/GetForumSquareResIdl.proto ================================================ // tbclient.GetForumSquare.GetForumSquareResIdl syntax = "proto3"; import "Error.proto"; import "Page.proto"; message GetForumSquareResIdl { Error error = 1; message DataRes { message RecommendForumInfo { uint64 forum_id = 2; string forum_name = 3; uint32 is_like = 4; uint32 member_count = 5; uint32 thread_count = 6; } repeated RecommendForumInfo forum_info = 2; Page page = 3; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_square_forums/protobuf/GetForumSquareResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import Page_pb2 as Page__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1aGetForumSquareResIdl.proto\x1a\x0b\x45rror.proto\x1a\nPage.proto"\xba\x02\n\x14GetForumSquareResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12+\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x1d.GetForumSquareResIdl.DataRes\x1a\xdd\x01\n\x07\x44\x61taRes\x12\x44\n\nforum_info\x18\x02 \x03(\x0b\x32\x30.GetForumSquareResIdl.DataRes.RecommendForumInfo\x12\x13\n\x04page\x18\x03 \x01(\x0b\x32\x05.Page\x1aw\n\x12RecommendForumInfo\x12\x10\n\x08\x66orum_id\x18\x02 \x01(\x04\x12\x12\n\nforum_name\x18\x03 \x01(\t\x12\x0f\n\x07is_like\x18\x04 \x01(\r\x12\x14\n\x0cmember_count\x18\x05 \x01(\r\x12\x14\n\x0cthread_count\x18\x06 \x01(\rb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetForumSquareResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETFORUMSQUARERESIDL"]._serialized_start = 56 _globals["_GETFORUMSQUARERESIDL"]._serialized_end = 370 _globals["_GETFORUMSQUARERESIDL_DATARES"]._serialized_start = 149 _globals["_GETFORUMSQUARERESIDL_DATARES"]._serialized_end = 370 _globals["_GETFORUMSQUARERESIDL_DATARES_RECOMMENDFORUMINFO"]._serialized_start = 251 _globals["_GETFORUMSQUARERESIDL_DATARES_RECOMMENDFORUMINFO"]._serialized_end = 370 ================================================ FILE: src/aiotieba/api/get_statistics/__init__.py ================================================ from ._api import parse_body, request from ._classdef import Statistics ================================================ FILE: src/aiotieba/api/get_statistics/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import Statistics def parse_body(body: bytes) -> Statistics: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) data_map = res_json["data"] stat = Statistics.from_json(data_map) return stat async def request(http_core: HttpCore, fid: int) -> Statistics: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("forum_id", fid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/f/forum/getforumdata"), data ) body = await http_core.net_core.send_request(request, read_bufsize=4 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_statistics/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Sequence @dcs.dataclass class Statistics: """ 吧务后台统计信息 时间从旧到新 Attributes: view (list[int]): 浏览量 thread (list[int]): 主题帖数 new_member (list[int]): 新增吧会员数 post (list[int]): 回复数 sign_ratio (list[int]): 签到率 avg_time (list[int]): 人均浏览时长 avg_times (list[int]): 人均进吧次数 recommend (list[int]): 首页推荐数 """ view: list[int] = dcs.field(default_factory=list) thread: list[int] = dcs.field(default_factory=list) new_member: list[int] = dcs.field(default_factory=list) post: list[int] = dcs.field(default_factory=list) sign_ratio: list[int] = dcs.field(default_factory=list) avg_time: list[int] = dcs.field(default_factory=list) avg_times: list[int] = dcs.field(default_factory=list) recommend: list[int] = dcs.field(default_factory=list) @staticmethod def from_json(data_seq: Sequence) -> Statistics: def extract(i: int) -> list[int]: seq: list = data_seq[i]["group"][1]["values"] seq = [int(item["value"]) for item in seq] return seq view = extract(0) thread = extract(1) new_member = extract(2) post = extract(3) sign_ratio = extract(4) avg_time = extract(5) avg_times = extract(6) recommend = extract(7) return Statistics(view, thread, new_member, post, sign_ratio, avg_time, avg_times, recommend) ================================================ FILE: src/aiotieba/api/get_tab_map/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import TabMap ================================================ FILE: src/aiotieba/api/get_tab_map/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import Account, HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import TabMap from .protobuf import SearchPostForumReqIdl_pb2, SearchPostForumResIdl_pb2 CMD = 309466 def pack_proto(account: Account, fname: str) -> bytes: req_proto = SearchPostForumReqIdl_pb2.SearchPostForumReqIdl() req_proto.data.common.BDUSS = account.BDUSS req_proto.data.common._client_version = LATEST_VERSION req_proto.data.fname = fname return req_proto.SerializeToString() def parse_body(body: bytes) -> TabMap: res_proto = SearchPostForumResIdl_pb2.SearchPostForumResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data tab_map = TabMap.from_proto(data_proto) return tab_map async def request_http(http_core: HttpCore, fname: str) -> TabMap: data = pack_proto(http_core.account, fname) request = http_core.pack_proto_request( yarl.URL.build( scheme="https", host=APP_BASE_HOST, path="/c/f/forum/searchPostForum", query_string=f"cmd={CMD}" ), data, ) body = await http_core.net_core.send_request(request, read_bufsize=4 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, fname: str) -> TabMap: data = pack_proto(ws_core.account, fname) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_tab_map/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt if TYPE_CHECKING: from .._classdef import TypeMessage @dcs.dataclass class TabMap(TbErrorExt): """ 分区名到分区id的映射 Attributes: err (Exception | None): 捕获的异常 map (dict[str, int]): 分区名到分区id的映射 """ map: dict[str, int] = dcs.field(default_factory=dict) @staticmethod def from_proto(data_proto: TypeMessage) -> TabMap: map_ = {tab_proto.tab_name: tab_proto.tab_id for tab_proto in data_proto.exact_match.tab_info} return TabMap(map_) def __getitem__(self, key: str) -> int: return self.map[key] ================================================ FILE: src/aiotieba/api/get_tab_map/protobuf/SearchPostForumReqIdl.proto ================================================ // tbclient.SearchPostForum.SearchPostForumReqIdl syntax = "proto3"; import "CommonReq.proto"; message SearchPostForumReqIdl { message DataReq { CommonReq common = 1; string fname = 2; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_tab_map/protobuf/SearchPostForumReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1bSearchPostForumReqIdl.proto\x1a\x0f\x43ommonReq.proto"{\n\x15SearchPostForumReqIdl\x12,\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1e.SearchPostForumReqIdl.DataReq\x1a\x34\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\n.CommonReq\x12\r\n\x05\x66name\x18\x02 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "SearchPostForumReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_SEARCHPOSTFORUMREQIDL"]._serialized_start = 48 _globals["_SEARCHPOSTFORUMREQIDL"]._serialized_end = 171 _globals["_SEARCHPOSTFORUMREQIDL_DATAREQ"]._serialized_start = 119 _globals["_SEARCHPOSTFORUMREQIDL_DATAREQ"]._serialized_end = 171 ================================================ FILE: src/aiotieba/api/get_tab_map/protobuf/SearchPostForumResIdl.proto ================================================ // tbclient.SearchPostForum.SearchPostForumResIdl syntax = "proto3"; import "Error.proto"; import "FrsTabInfo.proto"; message SearchPostForumResIdl { Error error = 1; message DataRes { message SearchForum { int64 forum_id = 1; string forum_name = 2; repeated FrsTabInfo tab_info = 9; } SearchForum exact_match = 1; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_tab_map/protobuf/SearchPostForumResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import FrsTabInfo_pb2 as FrsTabInfo__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1bSearchPostForumResIdl.proto\x1a\x0b\x45rror.proto\x1a\x10\x46rsTabInfo.proto"\xfd\x01\n\x15SearchPostForumResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12,\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x1e.SearchPostForumResIdl.DataRes\x1a\x9e\x01\n\x07\x44\x61taRes\x12?\n\x0b\x65xact_match\x18\x01 \x01(\x0b\x32*.SearchPostForumResIdl.DataRes.SearchForum\x1aR\n\x0bSearchForum\x12\x10\n\x08\x66orum_id\x18\x01 \x01(\x03\x12\x12\n\nforum_name\x18\x02 \x01(\t\x12\x1d\n\x08tab_info\x18\t \x03(\x0b\x32\x0b.FrsTabInfob\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "SearchPostForumResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_SEARCHPOSTFORUMRESIDL"]._serialized_start = 63 _globals["_SEARCHPOSTFORUMRESIDL"]._serialized_end = 316 _globals["_SEARCHPOSTFORUMRESIDL_DATARES"]._serialized_start = 158 _globals["_SEARCHPOSTFORUMRESIDL_DATARES"]._serialized_end = 316 _globals["_SEARCHPOSTFORUMRESIDL_DATARES_SEARCHFORUM"]._serialized_start = 234 _globals["_SEARCHPOSTFORUMRESIDL_DATARES_SEARCHFORUM"]._serialized_end = 316 ================================================ FILE: src/aiotieba/api/get_threads/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import ShareThread, Thread, Threads, UserInfo_t ================================================ FILE: src/aiotieba/api/get_threads/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import Threads from .protobuf import FrsPageReqIdl_pb2, FrsPageResIdl_pb2 CMD = 301001 def pack_proto(fname: str, pn: int, rn: int, sort: int, is_good: bool, version: str) -> bytes: req_proto = FrsPageReqIdl_pb2.FrsPageReqIdl() req_proto.data.common._client_type = 2 req_proto.data.common._client_version = version 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 = int(is_good) req_proto.data.sort_type = sort return req_proto.SerializeToString() def parse_body(body: bytes) -> Threads: res_proto = FrsPageResIdl_pb2.FrsPageResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data threads = Threads.from_proto(data_proto) return threads async def request_http( http_core: HttpCore, fname: str, pn: int, rn: int, sort: int, is_good: bool, version: str ) -> Threads: data = pack_proto(fname, pn, rn, sort, is_good, version) 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, version: str) -> Threads: data = pack_proto(fname, pn, rn, sort, is_good, version) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_threads/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from ...enums import Gender, PrivLike, PrivReply, ThreadType from ...exception import TbErrorExt from ...helper import deprecated from ...logging import get_logger as LOG from .._classdef import Containers, TypeMessage, VoteInfo from .._classdef.contents import ( _IMAGEHASH_EXP, FragAt, FragEmoji, FragImage, FragLink, FragText, FragTiebaPlus, FragUnknown, FragVideo, FragVoice, TypeFragment, TypeFragText, ) FragText_t = FragText_st = FragText FragEmoji_t = FragEmoji_st = FragEmoji FragImage_t = FragImage FragAt_t = FragAt_st = FragAt FragLink_t = FragLink_st = FragLink FragTiebaPlus_t = FragTiebaPlus_st = FragTiebaPlus FragVideo_t = FragVideo_st = FragVideo FragVoice_t = FragVoice_st = FragVoice @dcs.dataclass class FragImage_feed: """ 图像碎片 Attributes: src (str): 小图链接 宽720px big_src (str): 大图链接 宽960px origin_src (str): 原图链接 width (int): 图像宽度 height (int): 图像高度 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) big_src: str = dcs.field(default="", repr=False) origin_src: str = dcs.field(default="", repr=False) width: int = 0 height: int = 0 hash: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragImage_feed: src = data_proto.small_pic_url big_src = data_proto.big_pic_url origin_src = data_proto.origin_pic_url width = data_proto.width height = data_proto.height hash_ = _IMAGEHASH_EXP.search(origin_src).group(1) return FragImage_feed(src, big_src, origin_src, width, height, hash_) @dcs.dataclass class FragEmoji_feed: """ 表情碎片 Attributes: id (str): 表情图片id desc (str): 表情描述 """ id: str = "" desc: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragEmoji_feed: id_ = data_proto.name desc = data_proto.c return FragEmoji_feed(id_, desc) @dcs.dataclass class Contents_t(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 emojis (list[FragEmoji_t | FragEmoji_feed]): 表情碎片列表 imgs (list[FragImage_t | FragImage_feed]): 图像碎片列表 ats (list[FragAt_t]): @碎片列表 links (list[FragLink_t]): 链接碎片列表 tiebapluses (list[FragTiebaPlus_t]): 贴吧plus碎片列表 video (FragVideo_t): 视频碎片 voice (FragVoice_t): 音频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) emojis: list[FragEmoji_t | FragEmoji_feed] = dcs.field(default_factory=list, repr=False) imgs: list[FragImage_t | FragImage_feed] = dcs.field(default_factory=list, repr=False) ats: list[FragAt_t] = dcs.field(default_factory=list, repr=False) links: list[FragLink_t] = dcs.field(default_factory=list, repr=False) tiebapluses: list[FragTiebaPlus_t] = dcs.field(default_factory=list, repr=False) video: FragVideo_t = dcs.field(default_factory=FragVideo_t, repr=False) voice: FragVoice_t = dcs.field(default_factory=FragVoice_t, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_t: content_protos = data_proto.first_post_content texts = [] emojis = [] imgs = [] ats = [] links = [] tiebapluses = [] def _frags(): for proto in content_protos: _type = proto.type # 0纯文本 9电话号 18话题 27百科词条 if _type in [0, 9, 18, 27]: frag = FragText_t.from_proto(proto) texts.append(frag) yield frag # 11:tid=5047676428 elif _type in [2, 11]: frag = FragEmoji_t.from_proto(proto) emojis.append(frag) yield frag # 20:tid=5470214675 elif _type in [3, 20]: frag = FragImage_t.from_proto(proto) imgs.append(frag) yield frag elif _type == 4: frag = FragAt_t.from_proto(proto) ats.append(frag) texts.append(frag) yield frag elif _type == 1: frag = FragLink_t.from_proto(proto) links.append(frag) texts.append(frag) yield frag elif _type == 10: # voice continue elif _type == 5: # video continue # 35|36:tid=7769728331 / 37:tid=7760184147 elif _type in [35, 36, 37]: frag = FragTiebaPlus_t.from_proto(proto) tiebapluses.append(frag) texts.append(frag) yield frag # outdated tiebaplus elif _type == 34: continue else: yield FragUnknown.from_proto(frag) objs = list(_frags()) if data_proto.video_info.video_width: video = FragVideo_t.from_proto(data_proto.video_info) objs.append(video) else: video = FragVideo_t() if data_proto.voice_info: voice = FragVoice_t.from_proto(data_proto.voice_info[0]) objs.append(voice) else: voice = FragVoice_t() return Contents_t(objs, texts, emojis, imgs, ats, links, tiebapluses, video, voice) @staticmethod def from_feed(data_proto: TypeMessage) -> Contents_t: texts = [] emojis = [] imgs = [] def _frags(): for component in data_proto.components: _type = component.component if _type == "feed_abstract": for proto in component.feed_abstract.data: if proto.type == 1: frag = FragText_t.from_proto(proto.text_info) texts.append(frag) yield frag elif proto.type == 3: frag = FragEmoji_feed.from_proto(proto.emoji_info) emojis.append(frag) yield frag else: yield FragUnknown.from_proto(frag) elif _type == "feed_pic": for proto in component.feed_pic.pics: frag = FragImage_feed.from_proto(proto) imgs.append(frag) yield frag elif _type in ["feed_head", "feed_title", "feed_social", "feed_poll"]: continue else: LOG().debug("Unknown component type. type=%s", _type) objs = list(_frags()) video = FragVideo_t() voice = FragVoice_t() return Contents_t(objs, texts, emojis, imgs, [], [], [], video, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class Page_t: """ 页信息 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_proto(data_proto: TypeMessage) -> Page_t: page_size = data_proto.page_size current_page = data_proto.current_page if current_page == 0 and page_size != 0: current_page = 1 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_t(page_size, current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class UserInfo_t: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 level (int): 等级 glevel (int): 贴吧成长等级 gender (Gender): 性别 icons (list[str]): 印记信息 is_bawu (bool): 是否吧务 is_vip (bool): 是否超级会员 is_god (bool): 是否大神 priv_like (PrivLike): 关注吧列表的公开状态 priv_reply (PrivReply): 帖子评论权限 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" level: int = 0 glevel: int = 0 gender: Gender = Gender.UNKNOWN icons: list[str] = dcs.field(default_factory=list) is_bawu: bool = False is_vip: bool = False is_god: bool = False priv_like: PrivLike = PrivLike.PUBLIC priv_reply: PrivReply = PrivReply.ALL @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_t: user_id = data_proto.id portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.name nick_name_new = data_proto.name_show level = data_proto.level_id glevel = data_proto.user_growth.level_id gender = Gender(data_proto.gender) icons = [name for i in data_proto.iconinfo if (name := i.name)] is_bawu = bool(data_proto.is_bawu) is_vip = bool(data_proto.new_tshow_icon) is_god = bool(data_proto.new_god_data.status) priv_like = PrivLike(priv_like) if (priv_like := data_proto.priv_sets.like) else PrivLike.PUBLIC priv_reply = PrivReply(priv_reply) if (priv_reply := data_proto.priv_sets.reply) else PrivReply.ALL return UserInfo_t( user_id, portrait, user_name, nick_name_new, level, glevel, gender, icons, is_bawu, is_vip, is_god, priv_like, priv_reply, ) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_t) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class FragImage_st: """ 图像碎片 Attributes: src (str): 小图链接 宽580px big_src (str): 大图链接 宽720px origin_src (str): 原图链接 show_width (int): 图像在客户端预览显示的宽度 show_height (int): 图像在客户端预览显示的高度 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) big_src: str = dcs.field(default="", repr=False) origin_src: str = dcs.field(default="", repr=False) show_width: int = 0 show_height: int = 0 hash: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragImage_st: src = data_proto.water_pic big_src = data_proto.small_pic origin_src = data_proto.big_pic show_width = data_proto.width show_height = data_proto.height if hash_obj := _IMAGEHASH_EXP.search(src): hash_ = hash_obj.group(1) else: hash_ = "" return FragImage_st(src, big_src, origin_src, show_width, show_height, hash_) @dcs.dataclass class Contents_st(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 emojis (list[FragEmoji_st]): 表情碎片列表 imgs (list[FragImage_st]): 图像碎片列表 ats (list[FragAt_st]): @碎片列表 links (list[FragLink_st]): 链接碎片列表 tiebapluses (list[FragTiebaPlus_st]): 贴吧plus碎片列表 video (FragVideo_st): 视频碎片 voice (FragVoice_st): 视频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) emojis: list[FragEmoji_st] = dcs.field(default_factory=list, repr=False) imgs: list[FragImage_st] = dcs.field(default_factory=list, repr=False) ats: list[FragAt_st] = dcs.field(default_factory=list, repr=False) links: list[FragLink_st] = dcs.field(default_factory=list, repr=False) tiebapluses: list[FragTiebaPlus_st] = dcs.field(default_factory=list, repr=False) video: FragVideo_st = dcs.field(default_factory=FragVideo_st, repr=False) voice: FragVoice_st = dcs.field(default_factory=FragVoice_st, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_st: content_protos = data_proto.content texts = [] emojis = [] imgs = [FragImage_st.from_proto(p) for p in data_proto.media] ats = [] links = [] tiebapluses = [] def _frags(): for proto in content_protos: _type = proto.type # 0纯文本 9电话号 18话题 27百科词条 if _type in [0, 9, 18, 27]: frag = FragText_st.from_proto(proto) texts.append(frag) yield frag # 11:tid=5047676428 elif _type in [2, 11]: frag = FragEmoji_st.from_proto(proto) emojis.append(frag) yield frag elif _type == 4: frag = FragAt_st.from_proto(proto) ats.append(frag) texts.append(frag) yield frag elif _type == 1: frag = FragLink_st.from_proto(proto) links.append(frag) texts.append(frag) yield frag elif _type == 5: # video continue # 35|36:tid=7769728331 / 37:tid=7760184147 elif _type in [35, 36, 37]: frag = FragTiebaPlus_st.from_proto(proto) tiebapluses.append(frag) texts.append(frag) yield frag # outdated tiebaplus elif _type == 34: continue else: yield FragUnknown.from_proto(frag) objs = list(_frags()) if ats: del ats[0] del objs[0] objs += imgs if data_proto.video_info.video_width: video = FragVideo_st.from_proto(data_proto.video_info) objs.append(video) else: video = FragVideo_st() if data_proto.voice_info: voice = FragVoice_st.from_proto(data_proto.voice_info[0]) objs.append(voice) else: voice = FragVoice_st() return Contents_st(objs, texts, emojis, imgs, ats, links, tiebapluses, video, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class ShareThread: """ 被分享的主题帖信息 Attributes: text (str): 文本内容 contents (Contents_st): 正文内容碎片列表 title (str): 标题内容 author_id (int): 发布者的user_id fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 主题帖tid pid (int): 首楼的回复id vote_info (VoteInfo): 投票内容 """ contents: Contents_st = dcs.field(default_factory=Contents_st) title: str = "" author_id: int = 0 fid: int = 0 fname: str = "" tid: int = 0 pid: int = 0 vote_info: VoteInfo = dcs.field(default_factory=VoteInfo) @staticmethod def from_proto(data_proto: TypeMessage) -> ShareThread: contents = Contents_st.from_proto(data_proto) author_id = data_proto.content[0].uid if data_proto.content else 0 title = data_proto.title fid = data_proto.fid fname = data_proto.fname tid = int(tid) if (tid := data_proto.tid) else 0 pid = data_proto.pid vote_info = VoteInfo.from_proto(data_proto.poll_info) return ShareThread(contents, title, author_id, fid, fname, tid, pid, vote_info) def __eq__(self, obj: ShareThread) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @cached_property def text(self) -> str: if self.title: text = f"{self.title}\n{self.contents.text}" else: text = self.contents.text return text @dcs.dataclass class Thread: """ 主题帖信息 Attributes: text (str): 文本内容 contents (Contents_t): 正文内容碎片列表 title (str): 标题内容 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 主题帖tid pid (int): 首楼回复pid user (UserInfo_t): 发布者的用户信息 author_id (int): 发布者的user_id type (ThreadType): 帖子类型 tab_id (int): 帖子所在分区id is_good (bool): 是否精品帖 is_top (bool): 是否置顶帖 is_share (bool): 是否分享帖 is_hide (bool): 是否被屏蔽 is_livepost (bool): 是否为置顶话题 is_help (bool): 是否为求助帖 vote_info (VoteInfo): 投票信息 share_origin (ShareThread): 转发来的原帖内容 view_num (int): 浏览量 reply_num (int): 回复数 share_num (int): 分享数 agree (int): 点赞数 disagree (int): 点踩数 create_time (int): 创建时间 10位时间戳 以秒为单位 last_time (int): 最后回复时间 10位时间戳 以秒为单位 """ contents: Contents_t = dcs.field(default_factory=Contents_t) title: str = "" fid: int = 0 fname: str = "" tid: int = 0 pid: int = 0 user: UserInfo_t = dcs.field(default_factory=UserInfo_t) author_id: int = 0 type: ThreadType = ThreadType.UNKNOWN tab_id: int = 0 is_good: bool = False is_top: bool = False is_share: bool = False is_hide: bool = False is_livepost: bool = False vote_info: VoteInfo = dcs.field(default_factory=VoteInfo) share_origin: ShareThread = dcs.field(default_factory=ShareThread) view_num: int = 0 reply_num: int = 0 share_num: int = 0 agree: int = 0 disagree: int = 0 create_time: int = 0 last_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> None: contents = Contents_t.from_proto(data_proto) title = data_proto.title tid = data_proto.id pid = data_proto.first_post_id author_id = data_proto.author_id type_ = ThreadType(data_proto.thread_type) if type_ == ThreadType.UNKNOWN: LOG().debug("Unknown thread type. tid=%d, type=%s", tid, data_proto.thread_type) tab_id = data_proto.tab_id is_good = bool(data_proto.is_good) is_top = bool(data_proto.is_top) is_share = bool(data_proto.is_share_thread) is_hide = bool(data_proto.is_frs_mask) is_livepost = bool(data_proto.is_livepost) vote_info = VoteInfo.from_proto(data_proto.poll_info) if is_share: if data_proto.origin_thread_info.pid: share_origin = ShareThread.from_proto(data_proto.origin_thread_info) else: is_share = False share_origin = ShareThread() else: share_origin = ShareThread() view_num = data_proto.view_num reply_num = data_proto.reply_num share_num = data_proto.share_num agree = data_proto.agree.agree_num disagree = data_proto.agree.disagree_num create_time = data_proto.create_time last_time = data_proto.last_time_int return Thread( contents, title, 0, "", tid, pid, None, author_id, type_, tab_id, is_good, is_top, is_share, is_hide, is_livepost, vote_info, share_origin, view_num, reply_num, share_num, agree, disagree, create_time, last_time, ) @staticmethod def from_feed(data_proto: TypeMessage) -> None: contents = Contents_t.from_feed(data_proto) business_info_map = {it.key: it.value for it in data_proto.business_info} title = business_info_map["title"] tid = int(business_info_map["thread_id"]) pid = 0 author_id = int(business_info_map["user_id"]) type_ = ThreadType(int(business_info_map["thread_type"])) tab_id = int(business_info_map["inner_tab_id"]) is_good = False is_top = False is_share = False is_hide = False is_livepost = False vote_info = None for component in data_proto.components: _type = component.component if _type != "feed_poll": # TODO: 不确定,需要找个抽奖帖测试 continue vote_info = VoteInfo.from_proto(component.feed_poll) if vote_info is None: vote_info = VoteInfo() share_origin = ShareThread() # TODO: 找个转发帖测试 view_num = int(business_info_map["view_num"]) reply_num = 0 share_num = 0 agree = 0 disagree = 0 create_time = int(business_info_map["create_time"]) last_time = 0 return Thread( contents, title, 0, "", tid, pid, None, author_id, type_, tab_id, is_good, is_top, is_share, is_hide, is_livepost, vote_info, share_origin, view_num, reply_num, share_num, agree, disagree, create_time, last_time, ) def __eq__(self, obj: Thread) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @cached_property def text(self) -> str: if self.title: text = f"{self.title}\n{self.contents.text}" else: text = self.contents.text return text @property @deprecated("使用 thread.type == ThreadType.HELP 作为替代") def is_help(self) -> bool: return self.type == ThreadType.HELP @dcs.dataclass class Forum_t: """ 吧信息 Attributes: fid (int): 贴吧id fname (str): 贴吧名 category (str): 一级分类 subcategory (str): 二级分类 member_num (int): 吧会员数 post_num (int): 发帖量 thread_num (int): 主题帖数 has_bawu (bool): 是否有吧务 has_rule (bool): 是否有吧规 """ fid: int = 0 fname: str = "" category: str = "" subcategory: str = "" member_num: int = 0 post_num: int = 0 thread_num: int = 0 has_bawu: bool = False has_rule: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> Forum_t: forum_proto = data_proto.forum fid = forum_proto.id fname = forum_proto.name category = forum_proto.first_class subcategory = forum_proto.second_class member_num = forum_proto.member_num post_num = forum_proto.post_num thread_num = forum_proto.thread_num has_bawu = bool(forum_proto.managers) has_rule = bool(data_proto.forum_rule.has_forum_rule) return Forum_t(fid, fname, category, subcategory, member_num, post_num, thread_num, has_bawu, has_rule) @dcs.dataclass class Threads(TbErrorExt, Containers[Thread]): """ 主题帖列表 Attributes: objs (list[Thread]): 主题帖列表 err (Exception | None): 捕获的异常 page (Page_t): 页信息 has_more (bool): 是否还有下一页 forum (Forum_t): 所在吧信息 tab_map (dict[str, int]): 分区名到分区id的映射表 """ page: Page_t = dcs.field(default_factory=Page_t) forum: Forum_t = dcs.field(default_factory=Forum_t) tab_map: dict[str, int] = dcs.field(default_factory=dict) @staticmethod def from_proto(data_proto: TypeMessage) -> Threads: page = Page_t.from_proto(data_proto.page) forum = Forum_t.from_proto(data_proto) tab_map = {p.tab_name: p.tab_id for p in data_proto.nav_tab_info.tab} objs = [Thread.from_proto(p) for p in data_proto.thread_list] users = {p.id: UserInfo_t.from_proto(p) for p in data_proto.user_list} for thread in objs: thread.fname = forum.fname thread.fid = forum.fid thread.user = users[thread.author_id] return Threads(objs, page, forum, tab_map) @staticmethod def from_feed(data_proto: TypeMessage) -> Threads: # 从12.65版本开始部分热门吧的主题帖列表采用feed形式推送 page = Page_t.from_proto(data_proto.page) forum = Forum_t.from_proto(data_proto) tab_map = {p.tab_name: p.tab_id for p in data_proto.nav_tab_info.tab} objs = [Thread.from_feed(p.feed) for p in data_proto.page_data.feed_list if p.layout == "feed"] for thread in objs: thread.fname = forum.fname thread.fid = forum.fid return Threads(objs, page, forum, tab_map) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/get_threads/protobuf/FrsPageReqIdl.proto ================================================ // tbclient.FrsPage.FrsPageReqIdl syntax = "proto3"; import "CommonReq.proto"; message FrsPageReqIdl { 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; } ================================================ FILE: src/aiotieba/api/get_threads/protobuf/FrsPageReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b"\n\x13\x46rsPageReqIdl.proto\x1a\x0f\x43ommonReq.proto\"\xc3\x01\n\rFrsPageReqIdl\x12$\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x16.FrsPageReqIdl.DataReq\x1a\x8b\x01\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18' \x01(\x0b\x32\n.CommonReq\x12\n\n\x02kw\x18\x01 \x01(\t\x12\n\n\x02rn\x18\x02 \x01(\x05\x12\x0f\n\x07rn_need\x18\x03 \x01(\x05\x12\x0f\n\x07is_good\x18\x04 \x01(\x05\x12\x0b\n\x03\x63id\x18\x05 \x01(\x05\x12\n\n\x02pn\x18\x0f \x01(\x05\x12\x11\n\tsort_type\x18/ \x01(\x05\x62\x06proto3" ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "FrsPageReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_FRSPAGEREQIDL"]._serialized_start = 41 _globals["_FRSPAGEREQIDL"]._serialized_end = 236 _globals["_FRSPAGEREQIDL_DATAREQ"]._serialized_start = 97 _globals["_FRSPAGEREQIDL_DATAREQ"]._serialized_end = 236 ================================================ FILE: src/aiotieba/api/get_threads/protobuf/FrsPageResIdl.proto ================================================ // tbclient.FrsPage.FrsPageResIdl syntax = "proto3"; import "Error.proto"; import "Page.proto"; import "ThreadInfo.proto"; import "User.proto"; import "FrsTabInfo.proto"; import "PollInfo.proto"; message PageData { message LayoutFactory { string layout = 1; message FeedLayout { message ComponentFactory { string component = 1; message FeedContentResource { int32 type = 1; message FeedContentText { string text = 1; } FeedContentText text_info = 8; message FeedContentEmoji { string name = 1; string c = 2; } FeedContentEmoji emoji_info = 10; } message PicInfo { string small_pic_url = 1; string big_pic_url = 2; string origin_pic_url = 3; uint32 width = 4; uint32 height = 5; } message TitleComponent { repeated FeedContentResource data = 1; } TitleComponent feed_title = 3; message AbstractComponent { repeated FeedContentResource data = 1; } AbstractComponent feed_abstract = 4; message FeedPicComponent { repeated PicInfo pics = 1; } FeedPicComponent feed_pic = 7; PollInfo feed_poll = 22; } repeated ComponentFactory components = 1; message FeedKV { string key = 1; string value = 2; } repeated FeedKV business_info = 5; } FeedLayout feed = 2; } repeated LayoutFactory feed_list = 2; } message FrsPageResIdl { Error error = 1; message DataRes { message ForumInfo { int64 id = 1; string name = 2; string first_class = 3; string second_class = 4; int32 member_num = 9; int32 thread_num = 10; int32 post_num = 11; message Manager {} repeated Manager managers = 17; } ForumInfo forum = 2; Page page = 4; repeated ThreadInfo thread_list = 7; repeated User user_list = 17; message NavTabInfo { repeated FrsTabInfo tab = 1; } NavTabInfo nav_tab_info = 37; message ForumRuleStatus { int32 has_forum_rule = 4; } ForumRuleStatus forum_rule = 105; PageData page_data = 126; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_threads/protobuf/FrsPageResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import FrsTabInfo_pb2 as FrsTabInfo__pb2 from ..._protobuf import Page_pb2 as Page__pb2 from ..._protobuf import PollInfo_pb2 as PollInfo__pb2 from ..._protobuf import ThreadInfo_pb2 as ThreadInfo__pb2 from ..._protobuf import User_pb2 as User__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x13\x46rsPageResIdl.proto\x1a\x0b\x45rror.proto\x1a\nPage.proto\x1a\x10ThreadInfo.proto\x1a\nUser.proto\x1a\x10\x46rsTabInfo.proto\x1a\x0ePollInfo.proto"\x8e\x0b\n\x08PageData\x12*\n\tfeed_list\x18\x02 \x03(\x0b\x32\x17.PageData.LayoutFactory\x1a\xd5\n\n\rLayoutFactory\x12\x0e\n\x06layout\x18\x01 \x01(\t\x12\x30\n\x04\x66\x65\x65\x64\x18\x02 \x01(\x0b\x32".PageData.LayoutFactory.FeedLayout\x1a\x81\n\n\nFeedLayout\x12G\n\ncomponents\x18\x01 \x03(\x0b\x32\x33.PageData.LayoutFactory.FeedLayout.ComponentFactory\x12@\n\rbusiness_info\x18\x05 \x03(\x0b\x32).PageData.LayoutFactory.FeedLayout.FeedKV\x1a\xc1\x08\n\x10\x43omponentFactory\x12\x11\n\tcomponent\x18\x01 \x01(\t\x12V\n\nfeed_title\x18\x03 \x01(\x0b\x32\x42.PageData.LayoutFactory.FeedLayout.ComponentFactory.TitleComponent\x12\\\n\rfeed_abstract\x18\x04 \x01(\x0b\x32\x45.PageData.LayoutFactory.FeedLayout.ComponentFactory.AbstractComponent\x12V\n\x08\x66\x65\x65\x64_pic\x18\x07 \x01(\x0b\x32\x44.PageData.LayoutFactory.FeedLayout.ComponentFactory.FeedPicComponent\x12\x1c\n\tfeed_poll\x18\x16 \x01(\x0b\x32\t.PollInfo\x1a\xcb\x02\n\x13\x46\x65\x65\x64\x43ontentResource\x12\x0c\n\x04type\x18\x01 \x01(\x05\x12j\n\ttext_info\x18\x08 \x01(\x0b\x32W.PageData.LayoutFactory.FeedLayout.ComponentFactory.FeedContentResource.FeedContentText\x12l\n\nemoji_info\x18\n \x01(\x0b\x32X.PageData.LayoutFactory.FeedLayout.ComponentFactory.FeedContentResource.FeedContentEmoji\x1a\x1f\n\x0f\x46\x65\x65\x64\x43ontentText\x12\x0c\n\x04text\x18\x01 \x01(\t\x1a+\n\x10\x46\x65\x65\x64\x43ontentEmoji\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\t\n\x01\x63\x18\x02 \x01(\t\x1al\n\x07PicInfo\x12\x15\n\rsmall_pic_url\x18\x01 \x01(\t\x12\x13\n\x0b\x62ig_pic_url\x18\x02 \x01(\t\x12\x16\n\x0eorigin_pic_url\x18\x03 \x01(\t\x12\r\n\x05width\x18\x04 \x01(\r\x12\x0e\n\x06height\x18\x05 \x01(\r\x1ag\n\x0eTitleComponent\x12U\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32G.PageData.LayoutFactory.FeedLayout.ComponentFactory.FeedContentResource\x1aj\n\x11\x41\x62stractComponent\x12U\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32G.PageData.LayoutFactory.FeedLayout.ComponentFactory.FeedContentResource\x1a]\n\x10\x46\x65\x65\x64PicComponent\x12I\n\x04pics\x18\x01 \x03(\x0b\x32;.PageData.LayoutFactory.FeedLayout.ComponentFactory.PicInfo\x1a$\n\x06\x46\x65\x65\x64KV\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t"\x94\x05\n\rFrsPageResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12$\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x16.FrsPageResIdl.DataRes\x1a\xc5\x04\n\x07\x44\x61taRes\x12/\n\x05\x66orum\x18\x02 \x01(\x0b\x32 .FrsPageResIdl.DataRes.ForumInfo\x12\x13\n\x04page\x18\x04 \x01(\x0b\x32\x05.Page\x12 \n\x0bthread_list\x18\x07 \x03(\x0b\x32\x0b.ThreadInfo\x12\x18\n\tuser_list\x18\x11 \x03(\x0b\x32\x05.User\x12\x37\n\x0cnav_tab_info\x18% \x01(\x0b\x32!.FrsPageResIdl.DataRes.NavTabInfo\x12:\n\nforum_rule\x18i \x01(\x0b\x32&.FrsPageResIdl.DataRes.ForumRuleStatus\x12\x1c\n\tpage_data\x18~ \x01(\x0b\x32\t.PageData\x1a\xd1\x01\n\tForumInfo\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x66irst_class\x18\x03 \x01(\t\x12\x14\n\x0csecond_class\x18\x04 \x01(\t\x12\x12\n\nmember_num\x18\t \x01(\x05\x12\x12\n\nthread_num\x18\n \x01(\x05\x12\x10\n\x08post_num\x18\x0b \x01(\x05\x12:\n\x08managers\x18\x11 \x03(\x0b\x32(.FrsPageResIdl.DataRes.ForumInfo.Manager\x1a\t\n\x07Manager\x1a&\n\nNavTabInfo\x12\x18\n\x03tab\x18\x01 \x03(\x0b\x32\x0b.FrsTabInfo\x1a)\n\x0f\x46orumRuleStatus\x12\x16\n\x0ehas_forum_rule\x18\x04 \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "FrsPageResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PAGEDATA"]._serialized_start = 113 _globals["_PAGEDATA"]._serialized_end = 1535 _globals["_PAGEDATA_LAYOUTFACTORY"]._serialized_start = 170 _globals["_PAGEDATA_LAYOUTFACTORY"]._serialized_end = 1535 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT"]._serialized_start = 254 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT"]._serialized_end = 1535 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY"]._serialized_start = 408 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY"]._serialized_end = 1497 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_FEEDCONTENTRESOURCE"]._serialized_start = 748 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_FEEDCONTENTRESOURCE"]._serialized_end = 1079 _globals[ "_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_FEEDCONTENTRESOURCE_FEEDCONTENTTEXT" ]._serialized_start = 1003 _globals[ "_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_FEEDCONTENTRESOURCE_FEEDCONTENTTEXT" ]._serialized_end = 1034 _globals[ "_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_FEEDCONTENTRESOURCE_FEEDCONTENTEMOJI" ]._serialized_start = 1036 _globals[ "_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_FEEDCONTENTRESOURCE_FEEDCONTENTEMOJI" ]._serialized_end = 1079 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_PICINFO"]._serialized_start = 1081 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_PICINFO"]._serialized_end = 1189 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_TITLECOMPONENT"]._serialized_start = 1191 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_TITLECOMPONENT"]._serialized_end = 1294 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_ABSTRACTCOMPONENT"]._serialized_start = 1296 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_ABSTRACTCOMPONENT"]._serialized_end = 1402 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_FEEDPICCOMPONENT"]._serialized_start = 1404 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_COMPONENTFACTORY_FEEDPICCOMPONENT"]._serialized_end = 1497 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_FEEDKV"]._serialized_start = 1499 _globals["_PAGEDATA_LAYOUTFACTORY_FEEDLAYOUT_FEEDKV"]._serialized_end = 1535 _globals["_FRSPAGERESIDL"]._serialized_start = 1538 _globals["_FRSPAGERESIDL"]._serialized_end = 2198 _globals["_FRSPAGERESIDL_DATARES"]._serialized_start = 1617 _globals["_FRSPAGERESIDL_DATARES"]._serialized_end = 2198 _globals["_FRSPAGERESIDL_DATARES_FORUMINFO"]._serialized_start = 1906 _globals["_FRSPAGERESIDL_DATARES_FORUMINFO"]._serialized_end = 2115 _globals["_FRSPAGERESIDL_DATARES_FORUMINFO_MANAGER"]._serialized_start = 2106 _globals["_FRSPAGERESIDL_DATARES_FORUMINFO_MANAGER"]._serialized_end = 2115 _globals["_FRSPAGERESIDL_DATARES_NAVTABINFO"]._serialized_start = 2117 _globals["_FRSPAGERESIDL_DATARES_NAVTABINFO"]._serialized_end = 2155 _globals["_FRSPAGERESIDL_DATARES_FORUMRULESTATUS"]._serialized_start = 2157 _globals["_FRSPAGERESIDL_DATARES_FORUMRULESTATUS"]._serialized_end = 2198 ================================================ FILE: src/aiotieba/api/get_uinfo_getUserInfo_web/__init__.py ================================================ from ._api import parse_body, request from ._classdef import UserInfo_guinfo_web ================================================ FILE: src/aiotieba/api/get_uinfo_getUserInfo_web/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import UserInfo_guinfo_web def parse_body(body: bytes) -> UserInfo_guinfo_web: res_json = parse_json(body) if code := res_json["errno"]: raise TiebaServerError(code, res_json["errmsg"]) user_dict = res_json["chatUser"] user = UserInfo_guinfo_web.from_json(user_dict) return user async def request(http_core: HttpCore, user_id: int) -> UserInfo_guinfo_web: params = [("chatUid", user_id)] request = http_core.pack_web_get_request( yarl.URL.build(scheme="http", host=WEB_BASE_HOST, path="/im/pcmsg/query/getUserInfo"), params ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_uinfo_getUserInfo_web/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...exception import TbErrorExt if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class UserInfo_guinfo_web(TbErrorExt): """ 用户信息 Attributes: err (Exception | None): 捕获的异常 user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" @staticmethod def from_json(data_map: Mapping) -> UserInfo_guinfo_web: user_id = data_map["uid"] portrait = data_map["portrait"] user_name = user_name if (user_name := data_map["uname"]) != user_id else "" nick_name_new = data_map["show_nickname"] return UserInfo_guinfo_web(user_id, portrait, user_name, nick_name_new) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_guinfo_web) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) ================================================ FILE: src/aiotieba/api/get_uinfo_getuserinfo_app/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import UserInfo_guinfo_app ================================================ FILE: src/aiotieba/api/get_uinfo_getuserinfo_app/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import UserInfo_guinfo_app from .protobuf import GetUserInfoReqIdl_pb2, GetUserInfoResIdl_pb2 CMD = 303024 def pack_proto(user_id: int) -> bytes: req_proto = GetUserInfoReqIdl_pb2.GetUserInfoReqIdl() req_proto.data.user_id = user_id return req_proto.SerializeToString() def parse_body(body: bytes) -> UserInfo_guinfo_app: res_proto = GetUserInfoResIdl_pb2.GetUserInfoResIdl() res_proto.ParseFromString(body) if error_code := res_proto.error.errorno: raise TiebaServerError(error_code, res_proto.error.errmsg) user_proto = res_proto.data.user user = UserInfo_guinfo_app.from_proto(user_proto) return user async def request_http(http_core: HttpCore, user_id: int) -> UserInfo_guinfo_app: data = pack_proto(user_id) request = http_core.pack_proto_request( yarl.URL.build(scheme="http", host=APP_BASE_HOST, path="/c/u/user/getuserinfo", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=1024) return parse_body(body) async def request_ws(ws_core: WsCore, user_id: int) -> UserInfo_guinfo_app: data = pack_proto(user_id) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_uinfo_getuserinfo_app/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...enums import Gender from ...exception import TbErrorExt if TYPE_CHECKING: from .._classdef import TypeMessage @dcs.dataclass class UserInfo_guinfo_app(TbErrorExt): """ 用户信息 Attributes: err (Exception | None): 捕获的异常 user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_old (str): 旧版昵称 gender (Gender): 性别 is_vip (bool): 是否超级会员 is_god (bool): 是否大神 nick_name (str): 用户昵称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_old: str = "" gender: Gender = Gender.UNKNOWN is_vip: bool = False is_god: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_guinfo_app: 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 gender = Gender(data_proto.sex) is_vip = bool(data_proto.vipInfo.v_status) is_god = bool(data_proto.new_god_data.status) return UserInfo_guinfo_app(user_id, portrait, user_name, nick_name_old, gender, is_vip, is_god) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_guinfo_app) -> 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 @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) ================================================ FILE: src/aiotieba/api/get_uinfo_getuserinfo_app/protobuf/GetUserInfoReqIdl.proto ================================================ // tbclient.GetUserInfo.GetUserInfoReqIdl syntax = "proto3"; message GetUserInfoReqIdl { message DataReq { int64 user_id = 2; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_uinfo_getuserinfo_app/protobuf/GetUserInfoReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x17GetUserInfoReqIdl.proto"Y\n\x11GetUserInfoReqIdl\x12(\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1a.GetUserInfoReqIdl.DataReq\x1a\x1a\n\x07\x44\x61taReq\x12\x0f\n\x07user_id\x18\x02 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetUserInfoReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETUSERINFOREQIDL"]._serialized_start = 27 _globals["_GETUSERINFOREQIDL"]._serialized_end = 116 _globals["_GETUSERINFOREQIDL_DATAREQ"]._serialized_start = 90 _globals["_GETUSERINFOREQIDL_DATAREQ"]._serialized_end = 116 ================================================ FILE: src/aiotieba/api/get_uinfo_getuserinfo_app/protobuf/GetUserInfoResIdl.proto ================================================ // tbclient.GetUserInfo.GetUserInfoResIdl syntax = "proto3"; import "Error.proto"; import "User.proto"; message GetUserInfoResIdl { Error error = 1; message DataRes { User user = 1; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_uinfo_getuserinfo_app/protobuf/GetUserInfoResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import User_pb2 as User__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x17GetUserInfoResIdl.proto\x1a\x0b\x45rror.proto\x1a\nUser.proto"t\n\x11GetUserInfoResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12(\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x1a.GetUserInfoResIdl.DataRes\x1a\x1e\n\x07\x44\x61taRes\x12\x13\n\x04user\x18\x01 \x01(\x0b\x32\x05.Userb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetUserInfoResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETUSERINFORESIDL"]._serialized_start = 52 _globals["_GETUSERINFORESIDL"]._serialized_end = 168 _globals["_GETUSERINFORESIDL_DATARES"]._serialized_start = 138 _globals["_GETUSERINFORESIDL_DATARES"]._serialized_end = 168 ================================================ FILE: src/aiotieba/api/get_uinfo_panel/__init__.py ================================================ from ._api import parse_body, request from ._classdef import UserInfo_panel ================================================ FILE: src/aiotieba/api/get_uinfo_panel/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import is_portrait, parse_json from ._classdef import UserInfo_panel def parse_body(body: bytes) -> UserInfo_panel: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) data_map = res_json["data"] user = UserInfo_panel.from_json(data_map) return user async def request(http_core: HttpCore, name_or_portrait: str) -> UserInfo_panel: if is_portrait(name_or_portrait): params = [("id", name_or_portrait)] else: params = [("un", name_or_portrait)] request = http_core.pack_web_get_request( yarl.URL.build(scheme="http", host=WEB_BASE_HOST, path="/home/get/panel"), params ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_uinfo_panel/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...enums import Gender from ...exception import TbErrorExt if TYPE_CHECKING: from collections.abc import Mapping def _tbnum2int(tb_num: str) -> int: if isinstance(tb_num, str): return int(float(tb_num.removesuffix("万")) * 1e4) else: return tb_num @dcs.dataclass class UserInfo_panel(TbErrorExt): """ 用户信息 Attributes: err (Exception | None): 捕获的异常 portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 nick_name_old (str): 旧版昵称 gender (Gender): 性别 age (float): 吧龄 post_num (int): 发帖数 fan_num (int): 粉丝数 is_vip (bool): 是否超级会员 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ portrait: str = "" user_name: str = "" nick_name_new: str = "" nick_name_old: str = "" gender: Gender = Gender.UNKNOWN age: int = 0.0 post_num: int = 0 fan_num: int = 0 is_vip: bool = False @staticmethod def from_json(data_map: Mapping) -> UserInfo_panel: portrait = data_map["portrait"] user_name = data_map["name"] nick_name_new = data_map["show_nickname"] nick_name_old = data_map["name_show"] sex = data_map["sex"] if sex == "male": gender = Gender.MALE elif sex == "female": gender = Gender.FEMALE else: gender = Gender.UNKNOWN if (tb_age := data_map["tb_age"]) != "-": age = float(tb_age) else: age = 0.0 post_num = _tbnum2int(data_map["post_num"]) fan_num = _tbnum2int(data_map["followed_count"]) if vip_dict := data_map["vipInfo"]: is_vip = int(vip_dict["v_status"]) == 3 else: is_vip = False return UserInfo_panel(portrait, user_name, nick_name_new, nick_name_old, gender, age, post_num, fan_num, is_vip) def __str__(self) -> str: return self.user_name or self.portrait def __eq__(self, obj: UserInfo_panel) -> bool: return self.portrait == obj.portrait def __hash__(self) -> int: return hash(self.portrait) def __bool__(self) -> bool: return hash(self.portrait) @property def nick_name(self) -> str: return self.nick_name_new @property def show_name(self) -> str: return self.nick_name_new or self.user_name @cached_property def log_name(self) -> str: return self.user_name or f"{self.nick_name_new}/{self.portrait}" ================================================ FILE: src/aiotieba/api/get_uinfo_user_json/__init__.py ================================================ from ._api import parse_body, request from ._classdef import UserInfo_json ================================================ FILE: src/aiotieba/api/get_uinfo_user_json/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaValueError from ...helper import parse_json from ._classdef import UserInfo_json def parse_body(body: bytes) -> UserInfo_json: if not body: raise TiebaValueError("Empty body") text = body.decode("utf-8", errors="ignore") res_json = parse_json(text) user_dict = res_json["creator"] user = UserInfo_json.from_json(user_dict) return user async def request(http_core: HttpCore, user_name: str) -> UserInfo_json: params = [ ("un", user_name), ("ie", "utf-8"), ] request = http_core.pack_web_get_request( yarl.URL.build(scheme="http", host=WEB_BASE_HOST, path="/i/sys/user_json"), params ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_uinfo_user_json/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class UserInfo_json(TbErrorExt): """ 用户信息 Attributes: err (Exception | None): 捕获的异常 user_id (int): user_id portrait (str): portrait user_name (str): 用户名 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" @staticmethod def from_json(data_map: Mapping) -> UserInfo_json: user_id = data_map["id"] portrait = data_map["portrait"] user_name = "" return UserInfo_json(user_id, portrait, user_name) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_json) -> 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 log_name(self) -> str: return str(self) ================================================ FILE: src/aiotieba/api/get_unblock_appeals/__init__.py ================================================ from ._api import parse_body, request from ._classdef import Appeal, Appeals ================================================ FILE: src/aiotieba/api/get_unblock_appeals/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import Appeals def parse_body(body: bytes) -> Appeals: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) appeals = Appeals.from_json(res_json) return appeals async def request(http_core: HttpCore, fid: int, pn: int, rn: int) -> Appeals: data = [ ("fn", "-"), ("fid", fid), ("pn", pn), ("rn", rn), ("tbs", http_core.account.tbs), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/getBawuAppealList"), data ) body = await http_core.net_core.send_request(request, read_bufsize=32 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_unblock_appeals/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class Appeal: """ 申诉请求信息 Attributes: user_id (int): 申诉用户id portrait (str): 申诉用户portrait user_name (str): 申诉用户名 nick_name (str): 申诉用户昵称 appeal_id (int): 申诉id appeal_reason (str): 申诉理由 appeal_time (int): 申诉时间 10位时间戳 以秒为单位 punish_reason (str): 封禁理由 punish_time (int): 封禁开始时间 10位时间戳 以秒为单位 punish_day (int): 封禁天数 op_name (str): 操作人用户名 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name: str = "" appeal_id: int = 0 appeal_reason: str = "" appeal_time: int = 0 punish_reason: str = "" punish_time: int = 0 punish_day: int = 0 op_name: str = "" @staticmethod def from_json(data_map: Mapping) -> Appeal: user_map = data_map["user"] user_id = user_map["id"] portrait = user_map["portrait"] if "?" in portrait: portrait = portrait[:-13] user_name = user_map["name"] nick_name = user_map["name_show"] appeal_id = int(data_map["appeal_id"]) appeal_reason = data_map["appeal_reason"] appeal_time = int(data_map["appeal_time"]) punish_reason = data_map["punish_reason"] punish_time = int(data_map["punish_start_time"]) punish_day = data_map["punish_day_num"] op_name = data_map["operate_man"] return Appeal( user_id, portrait, user_name, nick_name, appeal_id, appeal_reason, appeal_time, punish_reason, punish_time, punish_day, op_name, ) @dcs.dataclass class Appeals(TbErrorExt, Containers[Appeal]): """ 申诉请求列表 Attributes: objs (list[Appeal]): 申诉请求列表 err (Exception | None): 捕获的异常 has_more (bool): 是否还有下一页 """ has_more: bool = False @staticmethod def from_json(data_map: Mapping) -> Appeals: objs = [Appeal.from_json(m) for m in data_map["data"].get("appeal_list", [])] has_more = data_map["data"].get("has_more", False) return Appeals(objs, has_more) ================================================ FILE: src/aiotieba/api/get_user_contents/__init__.py ================================================ from . import get_posts, get_posts_form, get_threads from ._classdef import UserPost, UserPosts, UserPostss, UserThread, UserThreads from ._const import CMD ================================================ FILE: src/aiotieba/api/get_user_contents/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from typing import TYPE_CHECKING from ...enums import ThreadType from ...exception import TbErrorExt from ...helper import deprecated from ...logging import get_logger as LOG from .._classdef import Containers, TypeMessage, VoteInfo from .._classdef.contents import ( _IMAGEHASH_EXP, FragAt, FragEmoji, FragLink, FragText, FragUnknown, FragVideo, FragVoice, TypeFragment, TypeFragText, ) if TYPE_CHECKING: from collections.abc import Mapping FragText_up = FragText_ut = FragText FragEmoji_ut = FragEmoji FragAt_ut = FragAt FragLink_up = FragLink_ut = FragLink FragVideo_ut = FragVideo FragVoice_ut = FragVoice @dcs.dataclass class FragVoice_up: """ 音频碎片 Attributes: md5 (str): 音频md5 duration (int): 音频长度 以秒为单位 """ md5: str = "" duration: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> FragVoice_up: md5 = data_proto.voice_md5 duration = int(data_proto.during_time) / 1000 return FragVoice_up(md5, duration) @staticmethod def from_json(data_map: Mapping) -> FragVoice_up: md5 = data_map["voice_md5"] duration = int(data_map["during_time"]) / 1000 return FragVoice_up(md5, duration) def __bool__(self) -> bool: return bool(self.md5) @dcs.dataclass class Contents_up(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 links (list[FragLink_up]): 链接碎片列表 voice (FragVoice_up): 音频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) links: list[FragLink_up] = dcs.field(default_factory=list, repr=False) voice: FragVoice_up = dcs.field(default_factory=FragVoice_up, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_up: content_protos = data_proto.post_content texts = [] links = [] voice = FragVoice_up() def _frags(): for proto in content_protos: _type = proto.type if _type in [0, 4]: frag = FragText_up.from_proto(proto) texts.append(frag) yield frag elif _type == 1: frag = FragLink_up.from_proto(proto) links.append(frag) texts.append(frag) yield frag elif _type == 10: # voice nonlocal voice voice = FragVoice_up.from_proto(proto) continue else: yield FragUnknown.from_proto(frag) objs = list(_frags()) return Contents_up(objs, texts, links, voice) @staticmethod def from_json(data_map: Mapping) -> Contents_up: content_maps = data_map["post_content"] texts = [] links = [] voice = FragVoice_up() def _frags(): for content_map in content_maps: _type = int(content_map["type"]) if _type in [0, 4]: frag = FragText_up.from_json(content_map) texts.append(frag) yield frag elif _type == 1: frag = FragLink_up.from_json(content_map) links.append(frag) texts.append(frag) yield frag elif _type == 10: # voice nonlocal voice voice = FragVoice_up.from_json(content_map) continue else: yield FragUnknown.from_json(content_map) objs = list(_frags()) return Contents_up(objs, texts, links, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class UserInfo_u: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_u: user_id = data_proto.user_id portrait = data_proto.user_portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.user_name nick_name_new = data_proto.name_show return UserInfo_u(user_id, portrait, user_name, nick_name_new) @staticmethod def from_json(data_map: Mapping) -> UserInfo_u: user_id = int(data_map["user_id"]) portrait = data_map["user_portrait"] if "?" in portrait: portrait = portrait[:-13] user_name = data_map["user_name"] nick_name_new = data_map["name_show"] return UserInfo_u(user_id, portrait, user_name, nick_name_new) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_u) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class UserPost: """ 用户历史回复信息 Attributes: text (str): 文本内容 contents (Contents_up): 正文内容碎片列表 fid (int): 所在吧id tid (int): 所在主题帖id pid (int): 回复id user (UserInfo_u): 发布者的用户信息 author_id (int): 发布者的user_id is_comment (bool): 是否为楼中楼 create_time (int): 创建时间 10位时间戳 以秒为单位 """ contents: Contents_up = dcs.field(default_factory=Contents_up) fid: int = 0 tid: int = 0 pid: int = 0 user: UserInfo_u = dcs.field(default_factory=UserInfo_u) is_comment: bool = False create_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> UserPost: contents = Contents_up.from_proto(data_proto) pid = data_proto.post_id is_comment = bool(data_proto.post_type) create_time = data_proto.create_time return UserPost(contents, 0, 0, pid, None, is_comment, create_time) @staticmethod def from_json(data_map: Mapping) -> UserPost: contents = Contents_up.from_json(data_map) pid = int(data_map["post_id"]) is_comment = bool(int(data_map["post_type"])) create_time = int(data_map["create_time"]) return UserPost(contents, 0, 0, pid, None, is_comment, create_time) def __eq__(self, obj: UserPost) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @property def text(self) -> str: return self.contents.text @property def author_id(self) -> int: return self.user.user_id @dcs.dataclass class UserPosts(Containers[UserPost]): """ 用户历史回复信息列表 Attributes: objs (list[UserPost]): 用户历史回复信息列表 fid (int): 所在吧id tid (int): 所在主题帖id """ fid: int = 0 tid: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> UserPosts: fid = data_proto.forum_id tid = data_proto.thread_id objs = [UserPost.from_proto(p) for p in data_proto.content] for upost in objs: upost.fid = fid upost.tid = tid return UserPosts(objs, fid, tid) @staticmethod def from_json(data_map: Mapping) -> UserPosts: fid = int(data_map["forum_id"]) tid = int(data_map["thread_id"]) objs = [UserPost.from_json(m) for m in data_map["content"]] for upost in objs: upost.fid = fid upost.tid = tid return UserPosts(objs, fid, tid) @dcs.dataclass class UserPostss(TbErrorExt, Containers[UserPosts]): """ 用户历史回复信息列表的列表 Attributes: objs (list[UserPosts]): 用户历史回复信息列表的列表 err (Exception | None): 捕获的异常 """ @staticmethod def from_proto(data_proto: TypeMessage) -> UserPostss: objs = [UserPosts.from_proto(p) for p in data_proto.post_list] if objs: user = UserInfo_u.from_proto(data_proto.post_list[0]) for uposts in objs: for upost in uposts: upost.user = user return UserPostss(objs) @staticmethod def from_json(data_map: Mapping) -> UserPostss: objs = [UserPosts.from_json(m) for m in data_map["post_list"]] if objs: user = UserInfo_u.from_json(data_map["post_list"][0]) for uposts in objs: for upost in uposts: upost.user = user return UserPostss(objs) @dcs.dataclass class FragImage_ut: """ 图像碎片 Attributes: src (str): 小图链接 宽580px 一定是静态图 big_src (str): 大图链接 宽960px origin_src (str): 原图链接 origin_size (int): 原图大小 width (int): 图像宽度 height (int): 图像高度 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) big_src: str = dcs.field(default="", repr=False) origin_src: str = dcs.field(default="", repr=False) origin_size: int = 0 width: int = 0 height: int = 0 hash: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragImage_ut: src = data_proto.small_pic big_src = data_proto.big_pic origin_src = data_proto.origin_pic origin_size = data_proto.origin_size width = data_proto.width height = data_proto.height hash_ = _IMAGEHASH_EXP.search(src).group(1) return FragImage_ut(src, big_src, origin_src, origin_size, width, height, hash_) @dcs.dataclass class Contents_ut(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 emojis (list[FragEmoji_ut]): 表情碎片列表 imgs (list[FragImage_ut]): 图像碎片列表 ats (list[FragAt_ut]): @碎片列表 links (list[FragLink_ut]): 链接碎片列表 video (FragVideo_ut): 视频碎片 voice (FragVoice_ut): 音频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) emojis: list[FragEmoji_ut] = dcs.field(default_factory=list, repr=False) imgs: list[FragImage_ut] = dcs.field(default_factory=list, repr=False) ats: list[FragAt_ut] = dcs.field(default_factory=list, repr=False) links: list[FragLink_ut] = dcs.field(default_factory=list, repr=False) video: FragVideo_ut = dcs.field(default_factory=FragVideo_ut, repr=False) voice: FragVoice_ut = dcs.field(default_factory=FragVoice_ut, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_ut: content_protos = data_proto.first_post_content texts = [] emojis = [] imgs = [FragImage_ut.from_proto(p) for p in data_proto.media if p.type != 5] ats = [] links = [] def _frags(): for proto in content_protos: _type = proto.type # 0纯文本 9电话号 18话题 27百科词条 if _type in [0, 9, 18, 27]: frag = FragText_ut.from_proto(proto) texts.append(frag) yield frag # 11:tid=5047676428 elif _type in [2, 11]: frag = FragEmoji_ut.from_proto(proto) emojis.append(frag) yield frag # img will be init elsewhere elif _type in [3, 20]: continue elif _type == 4: frag = FragAt_ut.from_proto(proto) ats.append(frag) texts.append(frag) yield frag elif _type == 1: frag = FragLink_ut.from_proto(proto) links.append(frag) texts.append(frag) yield frag elif _type == 5: # video continue elif _type == 10: # voice continue else: yield FragUnknown.from_proto(frag) objs = list(_frags()) objs += imgs if data_proto.video_info.video_width: video = FragVideo_ut.from_proto(data_proto.video_info) objs.append(video) else: video = FragVideo_ut() if data_proto.voice_info: voice = FragVoice_ut.from_proto(data_proto.voice_info[0]) objs.append(voice) else: voice = FragVoice_ut() return Contents_ut(objs, texts, emojis, imgs, ats, links, video, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class UserThread: """ 主题帖信息 Attributes: text (str): 文本内容 contents (Contents_ut): 正文内容碎片列表 title (str): 标题内容 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 主题帖tid pid (int): 首楼回复pid user (UserInfo_u): 发布者的用户信息 author_id (int): 发布者的user_id type (ThreadType): 帖子类型 is_help (bool): 是否为求助帖 vote_info (VoteInfo): 投票信息 view_num (int): 浏览量 reply_num (int): 回复数 share_num (int): 分享数 agree (int): 点赞数 disagree (int): 点踩数 create_time (int): 创建时间 10位时间戳 以秒为单位 """ contents: Contents_ut = dcs.field(default_factory=Contents_ut) title: str = "" fid: int = 0 fname: str = "" tid: int = 0 pid: int = 0 user: UserInfo_u = dcs.field(default_factory=UserInfo_u) type: ThreadType = ThreadType.UNKNOWN vote_info: VoteInfo = dcs.field(default_factory=VoteInfo) view_num: int = 0 reply_num: int = 0 share_num: int = 0 agree: int = 0 disagree: int = 0 create_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> UserThread: contents = Contents_ut.from_proto(data_proto) title = data_proto.title fid = data_proto.forum_id fname = data_proto.forum_name tid = data_proto.thread_id pid = data_proto.post_id type_ = ThreadType(data_proto.thread_type) if type_ == ThreadType.UNKNOWN: LOG().debug("Unknown thread type. tid=%d, type=%s", tid, data_proto.thread_type) vote_info = VoteInfo.from_proto(data_proto.poll_info) view_num = data_proto.freq_num reply_num = data_proto.reply_num share_num = data_proto.share_num agree = data_proto.agree.agree_num disagree = data_proto.agree.disagree_num create_time = data_proto.create_time return UserThread( contents, title, fid, fname, tid, pid, None, type_, vote_info, view_num, reply_num, share_num, agree, disagree, create_time, ) def __eq__(self, obj: UserThread) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @cached_property def text(self) -> str: if self.title: text = f"{self.title}\n{self.contents.text}" else: text = self.contents.text return text @property @deprecated("使用 thread.type == ThreadType.HELP 作为替代") def is_help(self) -> bool: return self.type == ThreadType.HELP @dcs.dataclass class UserThreads(TbErrorExt, Containers[UserThread]): """ 用户发布主题帖列表 Attributes: objs (list[UserThread]): 用户发布主题帖列表 err (Exception | None): 捕获的异常 """ @staticmethod def from_proto(data_proto: TypeMessage) -> UserThreads: objs = [UserThread.from_proto(p) for p in data_proto.post_list] if objs: user = UserInfo_u.from_proto(data_proto.post_list[0]) for uthread in objs: uthread.user = user return UserThreads(objs) ================================================ FILE: src/aiotieba/api/get_user_contents/_const.py ================================================ CMD = 303002 ================================================ FILE: src/aiotieba/api/get_user_contents/get_posts/__init__.py ================================================ from ._api import pack_proto, parse_body, request_http, request_ws ================================================ FILE: src/aiotieba/api/get_user_contents/get_posts/_api.py ================================================ import yarl from ....const import APP_BASE_HOST from ....core import Account, HttpCore, WsCore from ....exception import TiebaServerError from .._classdef import UserPostss from .._const import CMD from ..protobuf import UserPostReqIdl_pb2, UserPostResIdl_pb2 def pack_proto(account: Account, user_id: int, pn: int, rn: int, version: str) -> bytes: req_proto = UserPostReqIdl_pb2.UserPostReqIdl() req_proto.data.common.BDUSS = account.BDUSS req_proto.data.common._client_version = version req_proto.data.uid = user_id req_proto.data.need_content = 1 req_proto.data.pn = pn req_proto.data.rn = rn return req_proto.SerializeToString() def parse_body(body: bytes) -> UserPostss: res_proto = UserPostResIdl_pb2.UserPostResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data upostss = UserPostss.from_proto(data_proto) return upostss async def request_http(http_core: HttpCore, user_id: int, pn: int, rn: int, version: str) -> UserPostss: data = pack_proto(http_core.account, user_id, pn, rn, version) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/u/feed/userpost", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=64 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, user_id: int, pn: int, rn: int, version: str) -> UserPostss: data = pack_proto(ws_core.account, user_id, pn, rn, version) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_user_contents/get_posts_form/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/get_user_contents/get_posts_form/_api.py ================================================ import yarl from ....const import LATEST_VERSION, WEB_BASE_HOST from ....core import HttpCore from ....exception import TiebaServerError from ....helper import parse_json from .._classdef import UserPostss def parse_body(body: bytes) -> UserPostss: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) upostss = UserPostss.from_json(res_json) return upostss async def request(http_core: HttpCore, user_id: int, pn: int, rn: int) -> UserPostss: data = [ ("_client_version", LATEST_VERSION), ("res_num", rn), ("is_thread", 1), ("need_content", 1), ("is_view_card", 1), # ("forum_id", 0), ("uid", user_id), ("pn", pn), ("subapp_type", "hybrid"), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/c/u/feed/userpost"), data, extra_headers=[("Subapp-Type", "hybrid")], ) body = await http_core.net_core.send_request(request, read_bufsize=64 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_user_contents/get_threads/__init__.py ================================================ from ._api import pack_proto, parse_body, request_http, request_ws ================================================ FILE: src/aiotieba/api/get_user_contents/get_threads/_api.py ================================================ import yarl from ....const import APP_BASE_HOST, LATEST_VERSION from ....core import HttpCore, WsCore from ....exception import TiebaServerError from .._classdef import UserThreads from .._const import CMD from ..protobuf import UserPostReqIdl_pb2, UserPostResIdl_pb2 def pack_proto(user_id: int, pn: int, public_only: bool) -> bytes: req_proto = UserPostReqIdl_pb2.UserPostReqIdl() req_proto.data.common._client_version = LATEST_VERSION req_proto.data.uid = user_id req_proto.data.is_thread = 1 req_proto.data.need_content = 1 req_proto.data.pn = pn req_proto.data.is_view_card = 2 if public_only else 1 return req_proto.SerializeToString() def parse_body(body: bytes) -> UserThreads: res_proto = UserPostResIdl_pb2.UserPostResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data uthreads = UserThreads.from_proto(data_proto) return uthreads async def request_http(http_core: HttpCore, user_id: int, pn: int, public_only: bool) -> UserThreads: data = pack_proto(user_id, pn, public_only) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/u/feed/userpost", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=64 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, user_id: int, pn: int, public_only: bool) -> UserThreads: data = pack_proto(user_id, pn, public_only) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/get_user_contents/protobuf/UserPostReqIdl.proto ================================================ // tbclient.UserPost.UserPostReqIdl syntax = "proto3"; import "CommonReq.proto"; message UserPostReqIdl { message DataReq { int64 uid = 1; uint32 rn = 2; uint32 is_thread = 4; uint32 need_content = 5; uint32 pn = 26; int32 is_view_card = 33; CommonReq common = 27; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/get_user_contents/protobuf/UserPostReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x14UserPostReqIdl.proto\x1a\x0f\x43ommonReq.proto"\xc3\x01\n\x0eUserPostReqIdl\x12%\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x17.UserPostReqIdl.DataReq\x1a\x89\x01\n\x07\x44\x61taReq\x12\x0b\n\x03uid\x18\x01 \x01(\x03\x12\n\n\x02rn\x18\x02 \x01(\r\x12\x11\n\tis_thread\x18\x04 \x01(\r\x12\x14\n\x0cneed_content\x18\x05 \x01(\r\x12\n\n\x02pn\x18\x1a \x01(\r\x12\x14\n\x0cis_view_card\x18! \x01(\x05\x12\x1a\n\x06\x63ommon\x18\x1b \x01(\x0b\x32\n.CommonReqb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "UserPostReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_USERPOSTREQIDL"]._serialized_start = 42 _globals["_USERPOSTREQIDL"]._serialized_end = 237 _globals["_USERPOSTREQIDL_DATAREQ"]._serialized_start = 100 _globals["_USERPOSTREQIDL_DATAREQ"]._serialized_end = 237 ================================================ FILE: src/aiotieba/api/get_user_contents/protobuf/UserPostResIdl.proto ================================================ // tbclient.UserPost.UserPostResIdl syntax = "proto3"; import "Error.proto"; import "PostInfoList.proto"; message UserPostResIdl { Error error = 1; message DataRes { repeated PostInfoList post_list = 1; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/get_user_contents/protobuf/UserPostResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import PostInfoList_pb2 as PostInfoList__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x14UserPostResIdl.proto\x1a\x0b\x45rror.proto\x1a\x12PostInfoList.proto"{\n\x0eUserPostResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12%\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x17.UserPostResIdl.DataRes\x1a+\n\x07\x44\x61taRes\x12 \n\tpost_list\x18\x01 \x03(\x0b\x32\r.PostInfoListb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "UserPostResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_USERPOSTRESIDL"]._serialized_start = 57 _globals["_USERPOSTRESIDL"]._serialized_end = 180 _globals["_USERPOSTRESIDL_DATARES"]._serialized_start = 137 _globals["_USERPOSTRESIDL_DATARES"]._serialized_end = 180 ================================================ FILE: src/aiotieba/api/get_user_forum_info/__init__.py ================================================ from ._api import parse_body, request from ._classdef import UserForumInfo, UserInfo_uf ================================================ FILE: src/aiotieba/api/get_user_forum_info/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import UserForumInfo def parse_body(body: bytes) -> UserForumInfo: res_json = parse_json(body) if code := int(res_json.get("error_code", 0) or 0): err_msg = res_json.get("error_msg") or res_json.get("error") or res_json.get("errmsg") or "" raise TiebaServerError(code, err_msg) data_map = res_json.get("data", {}) return UserForumInfo.from_json(data_map) async def request(http_core: HttpCore, forum_id: int, friend_portrait: str) -> UserForumInfo: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("forum_id", forum_id), ("friend_portrait", friend_portrait), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/f/forum/getUserForumLevelInfo"), data ) body = await http_core.net_core.send_request(request, read_bufsize=4 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/get_user_forum_info/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class UserInfo_uf: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait show_name (str): 显示名称 is_like (bool): 是否已关注该用户 """ user_id: int = 0 portrait: str = "" show_name: str = "" is_like: bool = False @staticmethod def from_json(data_map: Mapping) -> UserInfo_uf: show_name = data_map.get("name", "") user_id = int(data_map.get("id", 0) or 0) portrait = data_map.get("portrait", "") if "?" in portrait: portrait = portrait.split("?", 1)[0] is_like = bool(int(data_map.get("is_like", 0) or 0)) return UserInfo_uf(user_id, portrait, show_name, is_like) def __str__(self) -> str: return self.show_name or self.portrait or str(self.user_id) def __eq__(self, obj: object) -> bool: return isinstance(obj, UserInfo_uf) and self.user_id == obj.user_id def __hash__(self) -> int: return self.user_id def __bool__(self) -> bool: return bool(self.user_id) @dcs.dataclass class UserForumInfo(TbErrorExt): """ 用户在吧内的信息 Attributes: err (Exception | None): 捕获的异常 user (UserInfo_uf): 用户信息 fname (str): 贴吧名 small_avatar (str): 吧头像(小) is_follow (bool): 是否已关注该吧 follow_days (int): 关注天数 sign_days (int): 签到天数 thread_num (int): 本吧发帖数 day_post_num (int): 今日发帖数 member_rank (int): 吧内排名 day_sign_rank (int): 今日签到排名 level (int): 等级 level_name (str): 本吧头衔名称 exp (int): 当前经验 levelup_exp (int): 升级经验 role_name (str): 吧务名称 identify (str): 身份标识 high_light_sign_days (int): 连续签到天数 """ user: UserInfo_uf = dcs.field(default_factory=UserInfo_uf) fname: str = "" small_avatar: str = "" is_follow: bool = False follow_days: int = 0 sign_days: int = 0 thread_num: int = 0 day_post_num: int = 0 member_rank: int = 0 day_sign_rank: int = 0 level: int = 0 level_name: str = "" exp: int = 0 levelup_exp: int = 0 role_name: str = "" identify: str = "" high_light_sign_days: int = 0 @staticmethod def from_json(data_map: Mapping) -> UserForumInfo: user = UserInfo_uf.from_json(data_map.get("user_info", {})) user_forum = data_map.get("user_forum_info", {}) forum = data_map.get("forum_info", {}) return UserForumInfo( user=user, is_follow=bool(int(user_forum.get("is_follow", 0) or 0)), follow_days=int(user_forum.get("follow_days", 0) or 0), sign_days=int(user_forum.get("sign_days", 0) or 0), thread_num=int(user_forum.get("thread_num", 0) or 0), day_post_num=int(user_forum.get("day_post_num", 0) or 0), member_rank=int(user_forum.get("member_no", 0) or 0), day_sign_rank=int(user_forum.get("day_sign_no", 0) or 0), level=int(user_forum.get("level_id", 0) or 0), level_name=user_forum.get("level_name", ""), exp=int(user_forum.get("cur_score", 0) or 0), levelup_exp=int(user_forum.get("levelup_score", 0) or 0), role_name=user_forum.get("role_name", ""), identify=user_forum.get("identify", ""), high_light_sign_days=int(user_forum.get("high_light_sign_days", 0) or 0), fname=forum.get("forum_name", ""), small_avatar=forum.get("forum_avatar", ""), ) ================================================ FILE: src/aiotieba/api/good/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/good/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fname: str, fid: int, tid: int, cid: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("cid", cid), ("fid", fid), ("ntn", "set"), ("tbs", http_core.account.tbs), ("word", fname), ("z", tid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/commitgood"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/handle_unblock_appeals/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/handle_unblock_appeals/_api.py ================================================ from __future__ import annotations from typing import TYPE_CHECKING import yarl from ...const import WEB_BASE_HOST from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json if TYPE_CHECKING: from ...core import HttpCore def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) async def request(http_core: HttpCore, fid: int, appeal_ids: list[int], refuse: bool) -> BoolResponse: data = ( [ ("fn", "-"), ("fid", fid), ] + [(f"appeal_list[{i}]", appeal_id) for i, appeal_id in enumerate(appeal_ids)] + [ ("refuse_reason", "_"), ("status", 2 if refuse else 1), ("tbs", http_core.account.tbs), ] ) request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/multiAppealhandle"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/init_websocket/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request from ._classdef import WsMsgGroupInfo ================================================ FILE: src/aiotieba/api/init_websocket/_api.py ================================================ from __future__ import annotations import binascii import time from typing import TYPE_CHECKING from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.serialization import load_der_public_key from ...const import STABLE_VERSION from ...exception import TiebaServerError from ...helper import pack_json from ._classdef import WsMsgGroupInfo from .protobuf import UpdateClientInfoReqIdl_pb2, UpdateClientInfoResIdl_pb2 if TYPE_CHECKING: from ...core import Account, WsCore CMD = 1001 PUBLIC_KEY = binascii.a2b_base64( b"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQpwBZxXJV/JVRF/uNfyMSdu7YWwRNLM8+2xbniGp2iIQHOikPpTYQjlQgMi1uvq1kZpJ32rHo3hkwjy2l0lFwr3u4Hk2Wk7vnsqYQjAlYlK0TCzjpmiI+OiPOUNVtbWHQiLiVqFtzvpvi4AU7C1iKGvc/4IS45WjHxeScHhnZZ7njS4S1UgNP/GflRIbzgbBhyZ9kEW5/OO5YfG1fy6r4KSlDJw4o/mw5XhftyIpL+5ZBVBC6E1EIiP/dd9AbK62VV1PByfPMHMixpxI3GM2qwcmFsXcCcgvUXJBa9k6zP8dDQ3csCM2QNT+CQAOxthjtp/TFWaD7MzOdsIYb3THwIDAQAB" ) def pack_proto(account: Account) -> bytes: req_proto = UpdateClientInfoReqIdl_pb2.UpdateClientInfoReqIdl() req_proto.data.bduss = account.BDUSS device = { "cuid": account.cuid, "_client_version": STABLE_VERSION, "_msg_status": "1", "cuid_galaxy2": account.cuid_galaxy2, "_client_type": "2", "timestamp": str(int(time.time() * 1000)), } req_proto.data.device = pack_json(device) rsa_chiper = load_der_public_key(PUBLIC_KEY) secret_key = rsa_chiper.encrypt(account.aes_ecb_sec_key, PKCS1v15()) req_proto.data.secretKey = secret_key req_proto.data.stoken = account.STOKEN req_proto.cuid = f"{account.cuid}|com.baidu.tieba{STABLE_VERSION}" return req_proto.SerializeToString() def parse_body(body: bytes) -> list[WsMsgGroupInfo]: res_proto = UpdateClientInfoResIdl_pb2.UpdateClientInfoResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) groups = [WsMsgGroupInfo.from_proto(p) for p in res_proto.data.groupInfo] return groups async def request(ws_core: WsCore) -> list[WsMsgGroupInfo]: data = pack_proto(ws_core.account) resp = await ws_core.send(data, CMD, compress=False, encrypt=False) groups = parse_body(await resp.read()) return groups ================================================ FILE: src/aiotieba/api/init_websocket/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING if TYPE_CHECKING: from .._classdef import TypeMessage @dcs.dataclass class WsMsgGroupInfo: """ websocket消息组的相关信息 Attributes: group_id (int): 消息组id group_type (int): 消息组类别 last_msg_id (int): 最新消息的id """ group_id: int = 0 group_type: int = 0 last_msg_id: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> WsMsgGroupInfo: group_id = data_proto.groupId group_type = data_proto.groupType last_msg_id = data_proto.lastMsgId return WsMsgGroupInfo(group_id, group_type, last_msg_id) ================================================ FILE: src/aiotieba/api/init_websocket/protobuf/UpdateClientInfoReqIdl.proto ================================================ // protobuf.UpdateClientInfo.UpdateClientInfoReqIdl syntax = "proto3"; message UpdateClientInfoReqIdl { string cuid = 1; message DataReq { string bduss = 1; string device = 2; bytes secretKey = 3; string stoken = 12; } DataReq data = 2; } ================================================ FILE: src/aiotieba/api/init_websocket/protobuf/UpdateClientInfoReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1cUpdateClientInfoReqIdl.proto"\xa2\x01\n\x16UpdateClientInfoReqIdl\x12\x0c\n\x04\x63uid\x18\x01 \x01(\t\x12-\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x1f.UpdateClientInfoReqIdl.DataReq\x1aK\n\x07\x44\x61taReq\x12\r\n\x05\x62\x64uss\x18\x01 \x01(\t\x12\x0e\n\x06\x64\x65vice\x18\x02 \x01(\t\x12\x11\n\tsecretKey\x18\x03 \x01(\x0c\x12\x0e\n\x06stoken\x18\x0c \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "UpdateClientInfoReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_UPDATECLIENTINFOREQIDL"]._serialized_start = 33 _globals["_UPDATECLIENTINFOREQIDL"]._serialized_end = 195 _globals["_UPDATECLIENTINFOREQIDL_DATAREQ"]._serialized_start = 120 _globals["_UPDATECLIENTINFOREQIDL_DATAREQ"]._serialized_end = 195 ================================================ FILE: src/aiotieba/api/init_websocket/protobuf/UpdateClientInfoResIdl.proto ================================================ // protobuf.UpdateClientInfo.UpdateClientInfoResIdl syntax = "proto3"; import "Error.proto"; message UpdateClientInfoResIdl { Error error = 1; message DataRes { message GroupInfo { int64 groupId = 1; int32 groupType = 20; int64 lastMsgId = 21; } repeated GroupInfo groupInfo = 1; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/init_websocket/protobuf/UpdateClientInfoResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1cUpdateClientInfoResIdl.proto\x1a\x0b\x45rror.proto"\xec\x01\n\x16UpdateClientInfoResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12-\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x1f.UpdateClientInfoResIdl.DataRes\x1a\x8b\x01\n\x07\x44\x61taRes\x12<\n\tgroupInfo\x18\x01 \x03(\x0b\x32).UpdateClientInfoResIdl.DataRes.GroupInfo\x1a\x42\n\tGroupInfo\x12\x0f\n\x07groupId\x18\x01 \x01(\x03\x12\x11\n\tgroupType\x18\x14 \x01(\x05\x12\x11\n\tlastMsgId\x18\x15 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "UpdateClientInfoResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_UPDATECLIENTINFORESIDL"]._serialized_start = 46 _globals["_UPDATECLIENTINFORESIDL"]._serialized_end = 282 _globals["_UPDATECLIENTINFORESIDL_DATARES"]._serialized_start = 143 _globals["_UPDATECLIENTINFORESIDL_DATARES"]._serialized_end = 282 _globals["_UPDATECLIENTINFORESIDL_DATARES_GROUPINFO"]._serialized_start = 216 _globals["_UPDATECLIENTINFORESIDL_DATARES_GROUPINFO"]._serialized_end = 282 ================================================ FILE: src/aiotieba/api/init_z_id/__init__.py ================================================ from ._api import request ================================================ FILE: src/aiotieba/api/init_z_id/_api.py ================================================ import binascii import gzip import hashlib import time import aiohttp import yarl from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from ...const import STABLE_VERSION from ...core import HttpCore from ...helper import pack_json, parse_json from ...helper.crypto import rc4_42 SOFIRE_HOST = "sofire.baidu.com" async def request(http_core: HttpCore): app_key = "200033" # get by p/5/aio sec_key = "ea737e4f435b53786043369d2e5ace4f" xyus = ( hashlib.md5((http_core.account.android_id + http_core.account.uuid).encode("ascii")).hexdigest().upper() + "|0" ) xyus_md5_str = hashlib.md5(xyus.encode("ascii")).hexdigest() current_ts = str(int(time.time())) params = {"module_section": [{"zid": xyus}]} req_body = pack_json(params) req_body = gzip.compress(req_body.encode("utf-8"), compresslevel=6, mtime=0) padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_req_body = padder.update(req_body) + padder.finalize() aes_encryptor = http_core.account.aes_cbc_chiper.encryptor() req_body_aes = aes_encryptor.update(padded_req_body) + aes_encryptor.finalize() req_body_md5 = hashlib.md5(req_body).digest() payload = aiohttp.payload.BytesPayload( req_body_aes + req_body_md5, content_type="application/x-www-form-urlencoded", ) headers = { "x-device-id": xyus_md5_str, "User-Agent": f"x6/{app_key}/{STABLE_VERSION}/4.4.1.3", "x-plu-ver": "x6/4.4.1.3", } path_combine = "".join((app_key, current_ts, sec_key)) path_combine_md5 = hashlib.md5(path_combine.encode("ascii")).hexdigest() req_query_skey = rc4_42(xyus_md5_str, http_core.account.aes_cbc_sec_key) req_query_skey = binascii.b2a_base64(req_query_skey).decode("ascii") url = yarl.URL.build( scheme="https", host=SOFIRE_HOST, path=f"/c/11/z/100/{app_key}/{current_ts}/{path_combine_md5}", query=[("skey", req_query_skey)], ) request = aiohttp.ClientRequest( aiohttp.hdrs.METH_POST, url, headers=headers, data=payload, proxy=http_core.net_core.proxy.url, proxy_auth=http_core.net_core.proxy.auth, ssl=False, ) body = await http_core.net_core.send_request(request, read_bufsize=1024) res_json = parse_json(body) res_query_skey = binascii.a2b_base64(res_json["skey"]) res_aes_sec_key = rc4_42(xyus_md5_str, res_query_skey) res_data = binascii.a2b_base64(res_json["data"]) iv = b"\x00" * 16 aes_chiper = Cipher(algorithms.AES(res_aes_sec_key), modes.CBC(iv)) aes_decryptor = aes_chiper.decryptor() decrypted_res_data = aes_decryptor.update(res_data) + aes_decryptor.finalize() decrypted_res_data = decrypted_res_data[:-16] # remove suffix md5 unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() unpadded_data = unpadder.update(decrypted_res_data) + unpadder.finalize() res_data = unpadded_data.decode("utf-8") res_data = parse_json(res_data) zid = res_data["token"] return zid ================================================ FILE: src/aiotieba/api/login/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/login/_api.py ================================================ from __future__ import annotations from typing import TYPE_CHECKING import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import UserInfo_login if TYPE_CHECKING: from ...core import HttpCore def parse_body(body: bytes) -> tuple[UserInfo_login, str]: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) user_dict = res_json["user"] user = UserInfo_login.from_json(user_dict) tbs = res_json["anti"]["tbs"] return user, tbs async def request(http_core: HttpCore) -> tuple[UserInfo_login, str]: data = [ ("_client_version", LATEST_VERSION), ("bdusstoken", http_core.account.BDUSS), ] request = http_core.pack_form_request(yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/s/login"), data) body = await http_core.net_core.send_request(request, read_bufsize=1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/login/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class UserInfo_login: """ 用户信息 Attributes: user_id (int): user_id portrait (str): portrait user_name (str): 用户名 """ user_id: int = 0 portrait: str = "" user_name: str = "" @staticmethod def from_json(data_map: Mapping) -> UserInfo_login: user_id = int(data_map["id"]) portrait = data_map["portrait"] user_name = data_map["name"] return UserInfo_login(user_id, portrait, user_name) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __hash__(self) -> int: return self.user_id def __bool__(self) -> bool: return bool(self.user_id) ================================================ FILE: src/aiotieba/api/move/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/move/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import pack_json, parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int, tid: int, to_tab_id: int, from_tab_id: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("forum_id", fid), ("tbs", http_core.account.tbs), ("threads", pack_json([{"thread_id": tid, "from_tab_id": from_tab_id, "to_tab_id": to_tab_id}])), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/moveTabThread"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/profile/__init__.py ================================================ from . import get_homepage, get_uinfo_profile from ._classdef import Homepage, Thread_pf, UserInfo_pf from ._const import CMD ================================================ FILE: src/aiotieba/api/profile/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from functools import cached_property from ...enums import Gender, PrivLike, PrivReply from ...exception import TbErrorExt from .._classdef import Containers, TypeMessage, VoteInfo from .._classdef.contents import ( _IMAGEHASH_EXP, FragAt, FragEmoji, FragLink, FragText, FragUnknown, FragVideo, FragVoice, TypeFragment, TypeFragText, ) FragText_pf = FragText FragEmoji_pf = FragEmoji FragAt_pf = FragAt FragLink_pf = FragLink FragVideo_pf = FragVideo FragVoice_pf = FragVoice @dcs.dataclass class UserInfo_pf(TbErrorExt): """ 用户信息 Attributes: err (Exception | None): 捕获的异常 user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 tieba_uid (int): 用户个人主页uid glevel (int): 贴吧成长等级 gender (Gender): 性别 age (float): 吧龄 以年为单位 post_num (int): 发帖数 agree_num (int): 获赞数 fan_num (int): 粉丝数 follow_num (int): 关注数 forum_num (int): 关注贴吧数 sign (str): 个性签名 ip (str): ip归属地 icons (list[str]): 印记信息 is_vip (bool): 是否超级会员 is_god (bool): 是否大神 is_blocked (bool): 是否被永久封禁屏蔽 priv_like (PrivLike): 关注吧列表的公开状态 priv_reply (PrivReply): 帖子评论权限 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" tieba_uid: int = 0 glevel: int = 0 gender: Gender = Gender.UNKNOWN age: float = 0.0 post_num: int = 0 agree_num: int = 0 fan_num: int = 0 follow_num: int = 0 forum_num: int = 0 sign: str = "" ip: str = "" icons: list[str] = dcs.field(default_factory=list) is_vip: bool = False is_god: bool = False is_blocked: bool = False priv_like: PrivLike = PrivLike.PUBLIC priv_reply: PrivReply = PrivReply.ALL @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_pf: user_proto = data_proto.user user_id = user_proto.id portrait = user_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = user_proto.name nick_name_new = user_proto.name_show tieba_uid = int(tieba_uid) if (tieba_uid := user_proto.tieba_uid) else 0 glevel = user_proto.user_growth.level_id gender = Gender(user_proto.sex) age = float(age) if (age := user_proto.tb_age) else 0.0 post_num = user_proto.post_num agree_num = data_proto.user_agree_info.total_agree_num fan_num = user_proto.fans_num follow_num = user_proto.concern_num forum_num = user_proto.my_like_num sign = user_proto.intro ip = user_proto.ip_address icons = [name for i in user_proto.iconinfo if (name := i.name)] is_vip = bool(user_proto.new_tshow_icon) is_god = bool(user_proto.new_god_data.status) anti_proto = data_proto.anti_stat if anti_proto.block_stat and anti_proto.hide_stat and anti_proto.days_tofree > 30: is_blocked = True else: is_blocked = False priv_like = PrivLike(priv_like) if (priv_like := user_proto.priv_sets.like) else PrivLike.PUBLIC priv_reply = PrivReply(priv_reply) if (priv_reply := user_proto.priv_sets.reply) else PrivReply.ALL return UserInfo_pf( user_id, portrait, user_name, nick_name_new, tieba_uid, glevel, gender, age, post_num, agree_num, fan_num, follow_num, forum_num, sign, ip, icons, is_vip, is_god, is_blocked, priv_like, priv_reply, ) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_pf) -> 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_new @property def show_name(self) -> str: return self.nick_name_new 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_new}/{self.portrait}" else: return str(self.user_id) @dcs.dataclass class FragImage_pf: """ 图像碎片 Attributes: src (str): 大图链接 宽960px origin_src (str): 原图链接 width (int): 图像宽度 height (int): 图像高度 hash (str): 百度图床hash """ src: str = dcs.field(default="", repr=False) big_src: str = dcs.field(default="", repr=False) origin_src: str = dcs.field(default="", repr=False) width: int = 0 height: int = 0 hash: str = "" @staticmethod def from_proto(data_proto: TypeMessage) -> FragImage_pf: src = data_proto.big_pic origin_src = data_proto.origin_pic origin_size = data_proto.origin_size width = data_proto.width height = data_proto.height hash_ = _IMAGEHASH_EXP.search(src).group(1) return FragImage_pf(src, origin_src, origin_size, width, height, hash_) @dcs.dataclass class Contents_pf(Containers[TypeFragment]): """ 内容碎片列表 Attributes: objs (list[TypeFragment]): 所有内容碎片的混合列表 text (str): 文本内容 texts (list[TypeFragText]): 纯文本碎片列表 emojis (list[FragEmoji_pf]): 表情碎片列表 imgs (list[FragImage_pf]): 图像碎片列表 ats (list[FragAt_pf]): @碎片列表 links (list[FragLink_pf]): 链接碎片列表 video (FragVideo_pf): 视频碎片 voice (FragVoice_pf): 音频碎片 """ texts: list[TypeFragText] = dcs.field(default_factory=list, repr=False) emojis: list[FragEmoji_pf] = dcs.field(default_factory=list, repr=False) imgs: list[FragImage_pf] = dcs.field(default_factory=list, repr=False) ats: list[FragAt_pf] = dcs.field(default_factory=list, repr=False) links: list[FragLink_pf] = dcs.field(default_factory=list, repr=False) video: FragVideo_pf = dcs.field(default_factory=FragVideo_pf, repr=False) voice: FragVoice_pf = dcs.field(default_factory=FragVoice_pf, repr=False) @staticmethod def from_proto(data_proto: TypeMessage) -> Contents_pf: content_protos = data_proto.first_post_content texts = [] emojis = [] imgs = [FragImage_pf.from_proto(p) for p in data_proto.media if p.type != 5] ats = [] links = [] def _frags(): for proto in content_protos: _type = proto.type # 0纯文本 9电话号 18话题 27百科词条 if _type in [0, 9, 18, 27]: frag = FragText_pf.from_proto(proto) texts.append(frag) yield frag # 11:tid=5047676428 elif _type in [2, 11]: frag = FragEmoji_pf.from_proto(proto) emojis.append(frag) yield frag elif _type in [3, 20]: continue elif _type == 4: frag = FragAt_pf.from_proto(proto) ats.append(frag) texts.append(frag) yield frag elif _type == 1: frag = FragLink_pf.from_proto(proto) links.append(frag) texts.append(frag) yield frag elif _type == 5: # video continue elif _type == 10: # voice continue else: yield FragUnknown.from_proto(frag) objs = list(_frags()) objs += imgs if data_proto.video_info.video_width: video = FragVideo_pf.from_proto(data_proto.video_info) objs.append(video) else: video = FragVideo_pf() if data_proto.voice_info: voice = FragVoice_pf.from_proto(data_proto.voice_info[0]) objs.append(voice) else: voice = FragVoice_pf() return Contents_pf(objs, texts, emojis, imgs, ats, links, video, voice) @cached_property def text(self) -> str: text = "".join(frag.text for frag in self.texts) return text @dcs.dataclass class Thread_pf: """ 主题帖信息 Attributes: text (str): 文本内容 contents (Contents_pf): 正文内容碎片列表 title (str): 标题内容 fid (int): 所在吧id fname (str): 所在贴吧名 tid (int): 主题帖tid pid (int): 首楼回复pid user (UserInfo_pf): 发布者的用户信息 author_id (int): 发布者的user_id vote_info (VoteInfo): 投票信息 view_num (int): 浏览量 reply_num (int): 回复数 share_num (int): 分享数 agree (int): 点赞数 disagree (int): 点踩数 create_time (int): 创建时间 10位时间戳 以秒为单位 """ contents: Contents_pf = dcs.field(default_factory=Contents_pf) title: str = "" fid: int = 0 fname: str = "" tid: int = 0 pid: int = 0 user: UserInfo_pf = dcs.field(default_factory=UserInfo_pf) vote_info: VoteInfo = dcs.field(default_factory=VoteInfo) view_num: int = 0 reply_num: int = 0 share_num: int = 0 agree: int = 0 disagree: int = 0 create_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> Thread_pf: contents = Contents_pf.from_proto(data_proto) title = data_proto.title fid = data_proto.forum_id fname = data_proto.forum_name tid = data_proto.thread_id pid = data_proto.post_id vote_info = VoteInfo.from_proto(data_proto.poll_info) view_num = data_proto.freq_num reply_num = data_proto.reply_num share_num = data_proto.share_num agree = data_proto.agree.agree_num disagree = data_proto.agree.disagree_num create_time = data_proto.create_time return Thread_pf( contents, title, fid, fname, tid, pid, None, vote_info, view_num, reply_num, share_num, agree, disagree, create_time, ) def __eq__(self, obj: Thread_pf) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @cached_property def text(self) -> str: if self.title: text = f"{self.title}\n{self.contents.text}" else: text = self.contents.text return text @property def author_id(self) -> int: return self.user.user_id @dcs.dataclass class Homepage(TbErrorExt, Containers[Thread_pf]): """ 用户个人页信息 Attributes: objs (list[Thread_pf]): 用户发布主题帖列表 err (Exception | None): 捕获的异常 user (UserInfo_pf): 用户信息 """ user: UserInfo_pf = dcs.field(default_factory=UserInfo_pf) @staticmethod def from_proto(data_proto: TypeMessage) -> Homepage: objs = [Thread_pf.from_proto(p) for p in data_proto.post_list] user = UserInfo_pf.from_proto(data_proto) for thread in objs: thread.user = user return Homepage(objs, user) ================================================ FILE: src/aiotieba/api/profile/_const.py ================================================ CMD = 303012 ================================================ FILE: src/aiotieba/api/profile/get_homepage/__init__.py ================================================ from ._api import pack_proto, parse_body, request_http, request_ws ================================================ FILE: src/aiotieba/api/profile/get_homepage/_api.py ================================================ import yarl from ....const import APP_BASE_HOST, LATEST_VERSION from ....core import HttpCore, WsCore from ....exception import TiebaServerError from .._classdef import Homepage from .._const import CMD from ..protobuf import ProfileReqIdl_pb2, ProfileResIdl_pb2 def pack_proto(user_id: int, pn: int) -> bytes: req_proto = ProfileReqIdl_pb2.ProfileReqIdl() req_proto.data.common._client_version = LATEST_VERSION req_proto.data.common._client_type = 2 req_proto.data.uid = user_id req_proto.data.need_post_count = 1 req_proto.data.pn = pn return req_proto.SerializeToString() def parse_body(body: bytes) -> Homepage: res_proto = ProfileResIdl_pb2.ProfileResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data homepage = Homepage.from_proto(data_proto) return homepage async def request_http(http_core: HttpCore, user_id: int, pn: int) -> Homepage: data = pack_proto(user_id, pn) request = http_core.pack_proto_request( yarl.URL.build(scheme="http", host=APP_BASE_HOST, path="/c/u/user/profile", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=64 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, user_id: int, pn: int) -> Homepage: data = pack_proto(user_id, pn) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/profile/get_uinfo_profile/__init__.py ================================================ from ._api import pack_proto, parse_body, request_http, request_ws ================================================ FILE: src/aiotieba/api/profile/get_uinfo_profile/_api.py ================================================ from __future__ import annotations from typing import TYPE_CHECKING import yarl from ....const import APP_BASE_HOST, LATEST_VERSION from ....exception import TiebaServerError from .._classdef import UserInfo_pf from .._const import CMD from ..protobuf import ProfileReqIdl_pb2, ProfileResIdl_pb2 if TYPE_CHECKING: from ....core import HttpCore, WsCore def pack_proto(uid_or_portrait: str | int) -> bytes: req_proto = ProfileReqIdl_pb2.ProfileReqIdl() req_proto.data.common._client_version = LATEST_VERSION req_proto.data.common._client_type = 2 req_proto.data.need_post_count = 1 req_proto.data.page = 1 if isinstance(uid_or_portrait, int): req_proto.data.uid = uid_or_portrait else: req_proto.data.friend_uid_portrait = uid_or_portrait return req_proto.SerializeToString() def parse_body(body: bytes) -> UserInfo_pf: res_proto = ProfileResIdl_pb2.ProfileResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) data_proto = res_proto.data user = UserInfo_pf.from_proto(data_proto) return user async def request_http(http_core: HttpCore, uid_or_portrait: str | int) -> UserInfo_pf: data = pack_proto(uid_or_portrait) request = http_core.pack_proto_request( yarl.URL.build(scheme="http", host=APP_BASE_HOST, path="/c/u/user/profile", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) async def request_ws(ws_core: WsCore, uid_or_portrait: str | int) -> UserInfo_pf: data = pack_proto(uid_or_portrait) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/profile/protobuf/ProfileReqIdl.proto ================================================ // tbclient.Profile.ProfileReqIdl syntax = "proto3"; import "CommonReq.proto"; message ProfileReqIdl { message DataReq { int64 uid = 1; uint32 need_post_count = 2; uint32 pn = 6; CommonReq common = 9; int32 page = 15; string friend_uid_portrait = 16; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/profile/protobuf/ProfileReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x13ProfileReqIdl.proto\x1a\x0f\x43ommonReq.proto"\xba\x01\n\rProfileReqIdl\x12$\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x16.ProfileReqIdl.DataReq\x1a\x82\x01\n\x07\x44\x61taReq\x12\x0b\n\x03uid\x18\x01 \x01(\x03\x12\x17\n\x0fneed_post_count\x18\x02 \x01(\r\x12\n\n\x02pn\x18\x06 \x01(\r\x12\x1a\n\x06\x63ommon\x18\t \x01(\x0b\x32\n.CommonReq\x12\x0c\n\x04page\x18\x0f \x01(\x05\x12\x1b\n\x13\x66riend_uid_portrait\x18\x10 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "ProfileReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PROFILEREQIDL"]._serialized_start = 41 _globals["_PROFILEREQIDL"]._serialized_end = 227 _globals["_PROFILEREQIDL_DATAREQ"]._serialized_start = 97 _globals["_PROFILEREQIDL_DATAREQ"]._serialized_end = 227 ================================================ FILE: src/aiotieba/api/profile/protobuf/ProfileResIdl.proto ================================================ // tbclient.Profile.ProfileResIdl syntax = "proto3"; import "Error.proto"; import "User.proto"; import "PostInfoList.proto"; message ProfileResIdl { Error error = 1; message DataRes { User user = 1; message Anti { int32 block_stat = 6; int32 hide_stat = 7; int32 days_tofree = 9; } Anti anti_stat = 2; repeated PostInfoList post_list = 4; message UserAgreeInfo { int64 total_agree_num = 1; } UserAgreeInfo user_agree_info = 14; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/profile/protobuf/ProfileResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import PostInfoList_pb2 as PostInfoList__pb2 from ..._protobuf import User_pb2 as User__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x13ProfileResIdl.proto\x1a\x0b\x45rror.proto\x1a\nUser.proto\x1a\x12PostInfoList.proto"\xec\x02\n\rProfileResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12$\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x16.ProfileResIdl.DataRes\x1a\x9d\x02\n\x07\x44\x61taRes\x12\x13\n\x04user\x18\x01 \x01(\x0b\x32\x05.User\x12.\n\tanti_stat\x18\x02 \x01(\x0b\x32\x1b.ProfileResIdl.DataRes.Anti\x12 \n\tpost_list\x18\x04 \x03(\x0b\x32\r.PostInfoList\x12=\n\x0fuser_agree_info\x18\x0e \x01(\x0b\x32$.ProfileResIdl.DataRes.UserAgreeInfo\x1a\x42\n\x04\x41nti\x12\x12\n\nblock_stat\x18\x06 \x01(\x05\x12\x11\n\thide_stat\x18\x07 \x01(\x05\x12\x13\n\x0b\x64\x61ys_tofree\x18\t \x01(\x05\x1a(\n\rUserAgreeInfo\x12\x17\n\x0ftotal_agree_num\x18\x01 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "ProfileResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PROFILERESIDL"]._serialized_start = 69 _globals["_PROFILERESIDL"]._serialized_end = 433 _globals["_PROFILERESIDL_DATARES"]._serialized_start = 148 _globals["_PROFILERESIDL_DATARES"]._serialized_end = 433 _globals["_PROFILERESIDL_DATARES_ANTI"]._serialized_start = 325 _globals["_PROFILERESIDL_DATARES_ANTI"]._serialized_end = 391 _globals["_PROFILERESIDL_DATARES_USERAGREEINFO"]._serialized_start = 393 _globals["_PROFILERESIDL_DATARES_USERAGREEINFO"]._serialized_end = 433 ================================================ FILE: src/aiotieba/api/push_notify/__init__.py ================================================ from ._api import CMD, parse_body from ._classdef import WsNotify ================================================ FILE: src/aiotieba/api/push_notify/_api.py ================================================ from __future__ import annotations from ._classdef import WsNotify from .protobuf import PushNotifyResIdl_pb2 CMD = 202006 def parse_body(body: bytes) -> list[WsNotify]: res_proto = PushNotifyResIdl_pb2.PushNotifyResIdl() res_proto.ParseFromString(body) notifies = [WsNotify.from_proto(p) for p in res_proto.multiMsg] return notifies ================================================ FILE: src/aiotieba/api/push_notify/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING if TYPE_CHECKING: from .._classdef import TypeMessage @dcs.dataclass class WsNotify: """ websocket主动推送消息提醒 Attributes: note_type (int): 提醒类别 group_id (int): 消息组id group_type (int): 消息组类别 msg_id (int): 消息id create_time (int): 推送时间 10位时间戳 以秒为单位 """ note_type: int = 0 group_id: int = 0 group_type: int = 0 msg_id: int = 0 create_time: int = 0 @staticmethod def from_proto(data_proto: TypeMessage) -> WsNotify: data_proto = data_proto.data note_type = data_proto.type group_type = data_proto.groupType group_id = data_proto.groupId msg_id = data_proto.msgId create_time = str(create_time) if (create_time := data_proto.et) else 0 return WsNotify(note_type, group_id, group_type, msg_id, create_time) ================================================ FILE: src/aiotieba/api/push_notify/protobuf/PushNotifyResIdl.proto ================================================ // protobuf.PushNotify.PushNotifyResIdl syntax = "proto3"; message PushNotifyResIdl { message PusherMsg { message PusherMsgInfo { int64 groupId = 1; int64 msgId = 2; int32 type = 4; string et = 6; int32 groupType = 7; } PusherMsgInfo data = 2; } repeated PusherMsg multiMsg = 2; } ================================================ FILE: src/aiotieba/api/push_notify/protobuf/PushNotifyResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x16PushNotifyResIdl.proto"\xe6\x01\n\x10PushNotifyResIdl\x12-\n\x08multiMsg\x18\x02 \x03(\x0b\x32\x1b.PushNotifyResIdl.PusherMsg\x1a\xa2\x01\n\tPusherMsg\x12\x37\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32).PushNotifyResIdl.PusherMsg.PusherMsgInfo\x1a\\\n\rPusherMsgInfo\x12\x0f\n\x07groupId\x18\x01 \x01(\x03\x12\r\n\x05msgId\x18\x02 \x01(\x03\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\n\n\x02\x65t\x18\x06 \x01(\t\x12\x11\n\tgroupType\x18\x07 \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "PushNotifyResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_PUSHNOTIFYRESIDL"]._serialized_start = 27 _globals["_PUSHNOTIFYRESIDL"]._serialized_end = 257 _globals["_PUSHNOTIFYRESIDL_PUSHERMSG"]._serialized_start = 95 _globals["_PUSHNOTIFYRESIDL_PUSHERMSG"]._serialized_end = 257 _globals["_PUSHNOTIFYRESIDL_PUSHERMSG_PUSHERMSGINFO"]._serialized_start = 165 _globals["_PUSHNOTIFYRESIDL_PUSHERMSG_PUSHERMSGINFO"]._serialized_end = 257 ================================================ FILE: src/aiotieba/api/recommend/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/recommend/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) if code := int(res_json["data"]["is_push_success"]) != 1: raise TiebaServerError(code, res_json["data"]["msg"]) async def request(http_core: HttpCore, fid: int, tid: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("forum_id", fid), ("thread_id", tid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/pushRecomToPersonalized"), data ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/recover/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/recover/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) async def request(http_core: HttpCore, fid: int, tid: int, pid: int, is_hide: bool) -> BoolResponse: data = [ ("tbs", http_core.account.tbs), ("fn", "-"), ("fid", fid), ("tid_list[]", tid), ("pid_list[]", pid), ("type_list[]", 1 if pid else 0), ("is_frs_mask_list[]", int(is_hide)), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/bawurecoverthread"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/remove_fan/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/remove_fan/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, user_id: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("fans_uid", user_id), ("tbs", http_core.account.tbs), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/user/removeFans"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/search_exact/__init__.py ================================================ from ._api import parse_body, request from ._classdef import ExactSearches ================================================ FILE: src/aiotieba/api/search_exact/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...enums import SearchType from ...exception import TiebaServerError from ...helper import parse_json from ._classdef import ExactSearches def parse_body(body: bytes) -> ExactSearches: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) searches = ExactSearches.from_json(res_json) return searches async def request( http_core: HttpCore, fname: str, query: str, pn: int, rn: int, search_type: SearchType, only_thread: bool ) -> ExactSearches: data = [ ("_client_version", LATEST_VERSION), ("kw", fname), ("only_thread", int(only_thread)), ("pn", pn), ("rn", rn), ("sm", search_type), ("word", query), ] request = http_core.pack_form_request( yarl.URL.build(scheme="http", host=APP_BASE_HOST, path="/c/s/searchpost"), data ) body = await http_core.net_core.send_request(request, read_bufsize=8 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/search_exact/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt from .._classdef import Containers if TYPE_CHECKING: from collections.abc import Mapping @dcs.dataclass class ExactSearch: """ 搜索结果 Attributes: text (str): 文本内容 title (str): 标题内容 fname (str): 所在贴吧名 tid (int): 所在主题帖id pid (int): 回复id show_name (str): 发布者的显示名称 is_comment (bool): 是否楼中楼 create_time (int): 创建时间 """ text: str = "" title: str = "" fname: str = "" tid: int = 0 pid: int = 0 show_name: str = "" is_comment: bool = False create_time: int = 0 @staticmethod def from_json(data_map: Mapping) -> ExactSearch: text = data_map["content"] title = data_map["title"] fname = data_map["fname"] tid = int(data_map["tid"]) pid = int(data_map["pid"]) show_name = data_map["author"]["name_show"] is_comment = bool(int(data_map["is_floor"])) create_time = int(data_map["time"]) return ExactSearch(text, title, fname, tid, pid, show_name, is_comment, create_time) def __eq__(self, obj: ExactSearch) -> bool: return self.pid == obj.pid def __hash__(self) -> int: return self.pid @dcs.dataclass class Page_exsch: """ 页信息 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_json(data_map: Mapping) -> Page_exsch: page_size = int(data_map["page_size"]) current_page = int(data_map["current_page"]) total_page = int(data_map["total_page"]) total_count = int(data_map["total_count"]) has_more = bool(int(data_map["has_more"])) has_prev = bool(int(data_map["has_prev"])) return Page_exsch(page_size, current_page, total_page, total_count, has_more, has_prev) @dcs.dataclass class ExactSearches(TbErrorExt, Containers[ExactSearch]): """ 搜索结果列表 Attributes: objs (list[ExactSearch]): 搜索结果列表 err (Exception | None): 捕获的异常 page (Page_exsch): 页信息 has_more (bool): 是否还有下一页 """ page: Page_exsch = dcs.field(default_factory=Page_exsch) @staticmethod def from_json(data_map: Mapping) -> ExactSearches: objs = [ExactSearch.from_json(m) for m in data_map.get("post_list", [])] page = Page_exsch.from_json(data_map["page"]) return ExactSearches(objs, page) @property def has_more(self) -> bool: return self.page.has_more ================================================ FILE: src/aiotieba/api/send_chatroom_msg/__init__.py ================================================ from ._api import request ================================================ FILE: src/aiotieba/api/send_chatroom_msg/_api.py ================================================ import json import time from dataclasses import dataclass from ...core import BLCPCore, BLCPData from ...exception import BoolResponse @dataclass class AppConstants: appid: int = 10773430 sdk_version: int = 11250036 async def construct_request_data( blcpcore, room_id, uk, user_id, origin_id, name, portrait, text, forum_id, level, vip, glevel, atdata=None, robot=-1 ): constants = AppConstants() # 构造app_safe_ext app_safe_ext = {"haotianjing": {"zid": blcpcore.account.z_id or ""}} # 构造content content = { "text": { "room_id": str(room_id), "type": "0", "to_uid": "0", "vip": str(int(vip)), "name": name, "portrait": portrait + "?t=" + str(int(time.time())), "content_type": "0", "content_body": json.dumps({"text": text}, ensure_ascii=False), "src": "", "baidu_uk": blcpcore.getBDUKfromUserId(str(user_id)), "ext": {}, } } # 构造main_data,其主要包含名字、头像、昵称颜色、大会员标志等UI展示信息 main_data = [] if vip: main_data.append({ "icon": { "height": 75, "priority": 2, "schema": "https://tieba.baidu.com/mo/q/hybrid-business-vip/tbvip?customfullscreen=1&nonavigationbar=1", "type": "1", "url": "https://tieba-ares.cdn.bcebos.com/mis/2023-7/1689061482682/13afea50121d.png", "width": 75, }, "type": 2, }) # namedata将被包含在main_data中 namedata = { "text": { "short_enable": 1, "short_length": 5, "short_priority": 1, "priority": 1, "str": name, "suffix": "...", "type": "1", }, "type": 1, } if vip: # 开通了贴吧大会员,更新名称颜色 namedata["text"].update({"text_color": {"day": "CAM_X0301", "night": "CAM_X0301", "type": 2}}) main_data.extend(( namedata, { "icon": { "height": 15, "priority": 5, "schema": "https://tieba.baidu.com/mo/q/hybrid-main-user/taskCenter?customfullscreen=1&nonavigationbar=1", "type": "3", "url": "local://icon/icon_mask_level_usergrouth_" + str(glevel) + "?type=webp", "width": 24, }, "type": 2, }, { "icon": { "height": 12, "priority": 2, "schema": "https://tieba.baidu.com/mo/q/wise-bawu-core/forum-level?customfullscreen=1&forum_id=" + str(forum_id) + "&nonavigationbar=1&obj_locate=5&portrait=" + portrait + "?t=" + str(int(time.time())), "type": "4", "url": "local://icon/icon_level_" + f"{level:02d}" + "?type=webp", "width": 16, }, "type": 2, }, )) ext_data = { "main_data": main_data, "is_sys_msg": 0, "version": "", "portrait": portrait + "?t=" + str(int(time.time())), "robot_role": 0, "role": 0, "send_status": 0, "from": "android", "session_id": room_id, "type": 1, "user_name": name, } content["text"]["ext"].update(ext_data) # 携带文字、气泡信息,但这部分内容比较多,暂且未开发,注释之 # content['text']['ext']['bubble_info'] = {'color_info': {'at_text_color': '', 'at_text_color_dark': 'AF9EFF', 'text_color': '141414', 'text_color_dark': 'FFFFFF'}, 'end_time': 0, 'id': 1380005, 'img_info': {'android_left': 'https://tieba-ares.cdn.bcebos.com/mis/2023-3/1678971300129/4d9fd67c26e3.png', 'android_left_dark': 'https://tieba-ares.cdn.bcebos.com/mis/2023-3/1678971300129/4d9fd67c26e3.png', 'android_right': 'https://tieba-ares.cdn.bcebos.com/mis/2023-3/1678971357436/745549b2ae39.png', 'android_right_dark': 'https://tieba-ares.cdn.bcebos.com/mis/2023-3/1678971357436/745549b2ae39.png', 'ios_left': 'https://tieba-ares.cdn.bcebos.com/mis/2023-3/1678876082814/0a1741207e0d.png', 'ios_left_dark': 'https://tieba-ares.cdn.bcebos.com/mis/2023-3/1678876082814/0a1741207e0d.png', 'ios_right': 'https://tieba-ares.cdn.bcebos.com/mis/2023-3/1678876082435/c07fbaa4ad57.png', 'ios_right_dark': 'https://tieba-ares.cdn.bcebos.com/mis/2023-3/1678876082435/c07fbaa4ad57.png'}, 'jump_url': 'https://tieba.baidu.com/mo/q/hybrid/decorator?pageType=4&from_page=4&nonavigationbar=1&customfullscreen=1&decoratorId=1380005', 'type': 2} if robot == -1: # 没有机器人指令 content["text"]["ext"]["content"] = {} else: # 携带机器人指令代码 robot,如签到10005、领取福利10004等 content["text"]["ext"]["content"] = {"robot_params": {"scene": "tieba_group_chat", "type": robot}} content["text"]["ext"]["level"] = level content["text"]["ext"]["forum_id"] = forum_id if atdata: # 携带艾特@信息 content["text"]["at_data"] = atdata content["text"]["ext"] = json.dumps(content["text"]["ext"], ensure_ascii=False) content["text"] = json.dumps(content["text"], ensure_ascii=False) content = json.dumps(content, ensure_ascii=False) # 构造最终请求数据 request_data = { "method": 185, "mcast_id": room_id, "role": 3, "token": blcpcore.account.BDUSS, "appid": constants.appid, "uk": uk, "origin_id": origin_id, "type": 81, "app_safe_ext": json.dumps(app_safe_ext, ensure_ascii=False), "content": content, "msg_key": blcpcore.getmsgkey(blcpcore.getBDUKfromUserId(str(user_id))), "account_type": 1, "sdk_version": constants.sdk_version, "event_list": [ {"event": "CClickSendBegin", "timestamp_ms": 0}, {"event": "CSendBegin", "timestamp_ms": 0}, {"event": "CIMSendBegin", "timestamp_ms": int(time.time() * 1000)}, ], } return request_data async def send_request(blcpcore, request_data): loginBLCPRequest = BLCPData(serviceId=3, methodId=185) loginBLCPRequest.correlationId = int(time.time() * 1000 * 1000) loginBLCPRequest.RpcBody = blcpcore.buildRpcBody( serviceId=3, methodId=185, correlationId=loginBLCPRequest.correlationId ) request_data["client_logid"] = loginBLCPRequest.correlationId request_data["rpc"] = '{"rpc_retry_time":0}' loginBLCPRequest.LcmBody = json.dumps(request_data, ensure_ascii=False).encode("utf-8") response = blcpcore.waiter.new(loginBLCPRequest.correlationId) blcpcore.writer.write(loginBLCPRequest.toBytes()) reps = await response.read() try: err_code = json.loads(reps.LcmBody)["err_code"] except json.JSONDecodeError: raise if err_code == 0: return BoolResponse() raise async def request( blcpcore: BLCPCore, room_id: int, uk: int, user_id: int, origin_id: int, name: str, portrait: str, text: str, forum_id: int, level: int, vip: bool, glevel: int, atdata: list[dict] = None, robot=-1, ): request_data = await construct_request_data( blcpcore, room_id, uk, user_id, origin_id, name, portrait, text, forum_id, level, vip, glevel, atdata, robot ) return await send_request(blcpcore, request_data) ================================================ FILE: src/aiotieba/api/send_msg/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request ================================================ FILE: src/aiotieba/api/send_msg/_api.py ================================================ from ...core import WsCore from ...exception import TiebaServerError from .protobuf import CommitPersonalMsgReqIdl_pb2, CommitPersonalMsgResIdl_pb2 CMD = 205001 def pack_proto(user_id: int, content: str, record_id: int) -> bytes: req_proto = CommitPersonalMsgReqIdl_pb2.CommitPersonalMsgReqIdl() req_proto.data.toUid = user_id req_proto.data.content = content req_proto.data.msgType = 1 req_proto.data.recordId = record_id return req_proto.SerializeToString() def parse_body(body: bytes) -> int: res_proto = CommitPersonalMsgResIdl_pb2.CommitPersonalMsgResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) if code := res_proto.data.blockInfo.blockErrno: raise TiebaServerError(code, res_proto.data.blockInfo.blockErrmsg) msg_id = res_proto.data.msgId return msg_id async def request(ws_core: WsCore, user_id: int, content: str) -> int: data = pack_proto(user_id, content, ws_core.mid_manager.get_record_id()) resp = await ws_core.send(data, CMD) msg_id = parse_body(await resp.read()) return msg_id ================================================ FILE: src/aiotieba/api/send_msg/protobuf/CommitPersonalMsgReqIdl.proto ================================================ // protobuf.CommitPersonalMsg.CommitPersonalMsgReqIdl syntax = "proto3"; message CommitPersonalMsgReqIdl { message DataReq { int64 toUid = 2; int32 msgType = 3; string content = 4; int64 recordId = 6; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/send_msg/protobuf/CommitPersonalMsgReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1d\x43ommitPersonalMsgReqIdl.proto"\x97\x01\n\x17\x43ommitPersonalMsgReqIdl\x12.\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32 .CommitPersonalMsgReqIdl.DataReq\x1aL\n\x07\x44\x61taReq\x12\r\n\x05toUid\x18\x02 \x01(\x03\x12\x0f\n\x07msgType\x18\x03 \x01(\x05\x12\x0f\n\x07\x63ontent\x18\x04 \x01(\t\x12\x10\n\x08recordId\x18\x06 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "CommitPersonalMsgReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_COMMITPERSONALMSGREQIDL"]._serialized_start = 34 _globals["_COMMITPERSONALMSGREQIDL"]._serialized_end = 185 _globals["_COMMITPERSONALMSGREQIDL_DATAREQ"]._serialized_start = 109 _globals["_COMMITPERSONALMSGREQIDL_DATAREQ"]._serialized_end = 185 ================================================ FILE: src/aiotieba/api/send_msg/protobuf/CommitPersonalMsgResIdl.proto ================================================ // protobuf.CommitPersonalMsg.CommitPersonalMsgResIdl syntax = "proto3"; import "Error.proto"; message CommitPersonalMsgResIdl { Error error = 1; message DataRes { int64 msgId = 1; message BlockInfo { int32 blockErrno = 1; string blockErrmsg = 2; } BlockInfo blockInfo = 6; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/send_msg/protobuf/CommitPersonalMsgResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1d\x43ommitPersonalMsgResIdl.proto\x1a\x0b\x45rror.proto"\xf0\x01\n\x17\x43ommitPersonalMsgResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12.\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32 .CommitPersonalMsgResIdl.DataRes\x1a\x8d\x01\n\x07\x44\x61taRes\x12\r\n\x05msgId\x18\x01 \x01(\x03\x12=\n\tblockInfo\x18\x06 \x01(\x0b\x32*.CommitPersonalMsgResIdl.DataRes.BlockInfo\x1a\x34\n\tBlockInfo\x12\x12\n\nblockErrno\x18\x01 \x01(\x05\x12\x13\n\x0b\x62lockErrmsg\x18\x02 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "CommitPersonalMsgResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_COMMITPERSONALMSGRESIDL"]._serialized_start = 47 _globals["_COMMITPERSONALMSGRESIDL"]._serialized_end = 287 _globals["_COMMITPERSONALMSGRESIDL_DATARES"]._serialized_start = 146 _globals["_COMMITPERSONALMSGRESIDL_DATARES"]._serialized_end = 287 _globals["_COMMITPERSONALMSGRESIDL_DATARES_BLOCKINFO"]._serialized_start = 235 _globals["_COMMITPERSONALMSGRESIDL_DATARES_BLOCKINFO"]._serialized_end = 287 ================================================ FILE: src/aiotieba/api/set_bawu_perm/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/set_bawu_perm/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...enums import BawuPermType from ...exception import BoolResponse, TiebaServerError from ...helper import pack_json, parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) def pack_perm_settings(perms: BawuPermType) -> list: perm2id = [ (BawuPermType.UNBLOCK, 4), (BawuPermType.UNBLOCK_APPEAL, 5), (BawuPermType.RECOVER, 3), (BawuPermType.RECOVER_APPEAL, 2), ] perm_settings = [] for perm, id_ in perm2id: switch = 1 if perms & perm else 0 perm_setting = {"switch": switch, "perm": id_} perm_settings.append(perm_setting) return perm_settings async def request(http_core: HttpCore, fid: int, portrait: str, perms: BawuPermType) -> BoolResponse: data = [ ("forum_id", fid), ("auth_user_portrait", portrait), ("perm_setting", pack_json(pack_perm_settings(perms))), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/setAuthToolPerm"), data ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/set_blacklist/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws ================================================ FILE: src/aiotieba/api/set_blacklist/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import Account, HttpCore, WsCore from ...enums import BlacklistType from ...exception import BoolResponse, TiebaServerError from .protobuf import SetUserBlackReqIdl_pb2, SetUserBlackResIdl_pb2 CMD = 309697 def pack_proto(account: Account, user_id: int, btype: BlacklistType) -> bytes: req_proto = SetUserBlackReqIdl_pb2.SetUserBlackReqIdl() req_proto.data.common.BDUSS = account.BDUSS req_proto.data.common._client_type = 2 req_proto.data.common._client_version = LATEST_VERSION req_proto.data.black_uid = user_id req_proto.data.perm_list.follow = 1 if btype & BlacklistType.FOLLOW else 2 req_proto.data.perm_list.interact = 1 if btype & BlacklistType.INTERACT else 2 req_proto.data.perm_list.chat = 1 if btype & BlacklistType.CHAT else 2 return req_proto.SerializeToString() def parse_body(body: bytes) -> None: res_proto = SetUserBlackResIdl_pb2.SetUserBlackResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) async def request_http(http_core: HttpCore, user_id: int, btype: BlacklistType) -> BoolResponse: data = pack_proto(http_core.account, user_id, btype) request = http_core.pack_proto_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/user/setUserBlack", query_string=f"cmd={CMD}"), data, ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() async def request_ws(ws_core: WsCore, user_id: int, btype: BlacklistType) -> BoolResponse: data = pack_proto(ws_core.account, user_id, btype) response = await ws_core.send(data, CMD) parse_body(await response.read()) return BoolResponse() ================================================ FILE: src/aiotieba/api/set_blacklist/protobuf/SetUserBlackReqIdl.proto ================================================ // tbclient.SetUserBlack.SetUserBlackReqIdl syntax = "proto3"; import "CommonReq.proto"; message SetUserBlackReqIdl { message DataReq { CommonReq common = 1; int64 black_uid = 2; message PermissionList { int32 follow = 1; int32 interact = 2; int32 chat = 3; } PermissionList perm_list = 3; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/set_blacklist/protobuf/SetUserBlackReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x18SetUserBlackReqIdl.proto\x1a\x0f\x43ommonReq.proto"\xfb\x01\n\x12SetUserBlackReqIdl\x12)\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x1b.SetUserBlackReqIdl.DataReq\x1a\xb9\x01\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\n.CommonReq\x12\x11\n\tblack_uid\x18\x02 \x01(\x03\x12=\n\tperm_list\x18\x03 \x01(\x0b\x32*.SetUserBlackReqIdl.DataReq.PermissionList\x1a@\n\x0ePermissionList\x12\x0e\n\x06\x66ollow\x18\x01 \x01(\x05\x12\x10\n\x08interact\x18\x02 \x01(\x05\x12\x0c\n\x04\x63hat\x18\x03 \x01(\x05\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "SetUserBlackReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_SETUSERBLACKREQIDL"]._serialized_start = 46 _globals["_SETUSERBLACKREQIDL"]._serialized_end = 297 _globals["_SETUSERBLACKREQIDL_DATAREQ"]._serialized_start = 112 _globals["_SETUSERBLACKREQIDL_DATAREQ"]._serialized_end = 297 _globals["_SETUSERBLACKREQIDL_DATAREQ_PERMISSIONLIST"]._serialized_start = 233 _globals["_SETUSERBLACKREQIDL_DATAREQ_PERMISSIONLIST"]._serialized_end = 297 ================================================ FILE: src/aiotieba/api/set_blacklist/protobuf/SetUserBlackResIdl.proto ================================================ // tbclient.SetUserBlack.SetUserBlackResIdl syntax = "proto3"; import "Error.proto"; message SetUserBlackResIdl { Error error = 1; } ================================================ FILE: src/aiotieba/api/set_blacklist/protobuf/SetUserBlackResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x18SetUserBlackResIdl.proto\x1a\x0b\x45rror.proto"+\n\x12SetUserBlackResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Errorb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "SetUserBlackResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_SETUSERBLACKRESIDL"]._serialized_start = 41 _globals["_SETUSERBLACKRESIDL"]._serialized_end = 84 ================================================ FILE: src/aiotieba/api/set_msg_readed/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request ================================================ FILE: src/aiotieba/api/set_msg_readed/_api.py ================================================ from ...core import WsCore from ...enums import MsgType from ...exception import BoolResponse, TiebaServerError from ..get_group_msg import WsMessage from .protobuf import CommitReceivedPmsgReqIdl_pb2, CommitReceivedPmsgResIdl_pb2 CMD = 205006 def pack_proto(user_id: int, group_id: int, msg_id: int) -> bytes: req_proto = CommitReceivedPmsgReqIdl_pb2.CommitReceivedPmsgReqIdl() req_proto.data.groupId = group_id req_proto.data.toUid = user_id req_proto.data.msgType = MsgType.READED req_proto.data.msgId = msg_id return req_proto.SerializeToString() def parse_body(body: bytes) -> None: res_proto = CommitReceivedPmsgResIdl_pb2.CommitReceivedPmsgResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) async def request(ws_core: WsCore, message: WsMessage) -> BoolResponse: user_id = message.user.user_id msg_id = message.msg_id data = pack_proto(user_id, ws_core.mid_manager.priv_gid, msg_id) resp = await ws_core.send(data, CMD) parse_body(await resp.read()) return BoolResponse() ================================================ FILE: src/aiotieba/api/set_msg_readed/protobuf/CommitReceivedPmsgReqIdl.proto ================================================ // protobuf.CommitReceivedPmsg.CommitReceivedPmsgReqIdl syntax = "proto3"; message CommitReceivedPmsgReqIdl { message DataReq { int64 groupId = 1; int64 toUid = 2; int32 msgType = 3; int64 msgId = 4; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/set_msg_readed/protobuf/CommitReceivedPmsgReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1e\x43ommitReceivedPmsgReqIdl.proto"\x96\x01\n\x18\x43ommitReceivedPmsgReqIdl\x12/\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32!.CommitReceivedPmsgReqIdl.DataReq\x1aI\n\x07\x44\x61taReq\x12\x0f\n\x07groupId\x18\x01 \x01(\x03\x12\r\n\x05toUid\x18\x02 \x01(\x03\x12\x0f\n\x07msgType\x18\x03 \x01(\x05\x12\r\n\x05msgId\x18\x04 \x01(\x03\x62\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "CommitReceivedPmsgReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_COMMITRECEIVEDPMSGREQIDL"]._serialized_start = 35 _globals["_COMMITRECEIVEDPMSGREQIDL"]._serialized_end = 185 _globals["_COMMITRECEIVEDPMSGREQIDL_DATAREQ"]._serialized_start = 112 _globals["_COMMITRECEIVEDPMSGREQIDL_DATAREQ"]._serialized_end = 185 ================================================ FILE: src/aiotieba/api/set_msg_readed/protobuf/CommitReceivedPmsgResIdl.proto ================================================ // protobuf.CommitReceivedPmsg.CommitReceivedPmsgResIdl syntax = "proto3"; import "Error.proto"; message CommitReceivedPmsgResIdl { Error error = 1; } ================================================ FILE: src/aiotieba/api/set_msg_readed/protobuf/CommitReceivedPmsgResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1e\x43ommitReceivedPmsgResIdl.proto\x1a\x0b\x45rror.proto"1\n\x18\x43ommitReceivedPmsgResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Errorb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "CommitReceivedPmsgResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_COMMITRECEIVEDPMSGRESIDL"]._serialized_start = 47 _globals["_COMMITRECEIVEDPMSGRESIDL"]._serialized_end = 96 ================================================ FILE: src/aiotieba/api/set_nickname_old/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/set_nickname_old/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) async def request(http_core: HttpCore, nick_name: str) -> BoolResponse: params = [ ("nickname", nick_name), ("tbs", 1), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/submit/modifyNickname", query=params), [] ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/set_profile/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/set_profile/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...enums import Gender from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, nick_name: str, sign: str, gender: Gender) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("intro", sign), ("nick_name", nick_name), ("sex", int(gender)), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/profile/modify"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/set_thread_privacy/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/set_thread_privacy/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int, tid: int, pid: int, is_hide: bool) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("forum_id", fid), ("is_hide", str(int(is_hide))), ("post_id", pid), ("thread_id", tid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/thread/setPrivacy"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/sign_forum/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/sign_forum/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError, TiebaValueError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) if int(res_json["user_info"]["sign_bonus_point"]) == 0: raise TiebaValueError("sign_bonus_point is 0") async def request(http_core: HttpCore, fname: str) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("kw", fname), ("tbs", http_core.account.tbs), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/forum/sign"), data ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/sign_forums/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/sign_forums/_api.py ================================================ import yarl from ...const import LATEST_VERSION, WEB_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) if "error" in res_json and (code := int(res_json["error"]["errno"])): raise TiebaServerError(code, res_json["error"]["errmsg"]) async def request(http_core: HttpCore) -> BoolResponse: data = [ ("_client_version", LATEST_VERSION), ("subapp_type", "hybrid"), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/c/c/forum/msign"), data, extra_headers=[("Subapp-Type", "hybrid")], ) body = await http_core.net_core.send_request(request, read_bufsize=2 * 1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/sign_growth/__init__.py ================================================ from ._api import parse_body_app, request_app, request_web ================================================ FILE: src/aiotieba/api/sign_growth/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, WEB_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body_web(body: bytes) -> None: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) async def request_web(http_core: HttpCore, act_type: str) -> BoolResponse: data = [ ("tbs", http_core.account.tbs), ("act_type", act_type), ("cuid", "-"), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/usergrowth/commitUGTaskInfo"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body_web(body) return BoolResponse() def parse_body_app(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request_app(http_core: HttpCore, act_type: str) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("act_type", act_type), ("cuid", "-"), ("tbs", http_core.account.tbs), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/user/commitUGTaskInfo"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body_app(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/sync/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/sync/_api.py ================================================ from __future__ import annotations from typing import TYPE_CHECKING import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...exception import TiebaServerError from ...helper import parse_json if TYPE_CHECKING: from ...core import HttpCore def parse_body(body: bytes) -> tuple[str, str]: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) client_id = res_json["client"]["client_id"] sample_id = res_json["wl_config"]["sample_id"] return client_id, sample_id async def request(http_core: HttpCore) -> tuple[str, str]: data = [ ("BDUSS", http_core.account.BDUSS), ("_client_version", LATEST_VERSION), ("cuid", http_core.account.cuid_galaxy2), ] request = http_core.pack_form_request(yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/s/sync"), data) body = await http_core.net_core.send_request(request, read_bufsize=64 * 1024) return parse_body(body) ================================================ FILE: src/aiotieba/api/tieba_uid2user_info/__init__.py ================================================ from ._api import CMD, pack_proto, parse_body, request_http, request_ws from ._classdef import UserInfo_TUid ================================================ FILE: src/aiotieba/api/tieba_uid2user_info/_api.py ================================================ import yarl from ...const import APP_BASE_HOST, LATEST_VERSION from ...core import HttpCore, WsCore from ...exception import TiebaServerError from ._classdef import UserInfo_TUid from .protobuf import GetUserByTiebaUidReqIdl_pb2, GetUserByTiebaUidResIdl_pb2 CMD = 309702 def pack_proto(tieba_uid: int) -> bytes: req_proto = GetUserByTiebaUidReqIdl_pb2.GetUserByTiebaUidReqIdl() req_proto.data.common._client_version = LATEST_VERSION req_proto.data.tieba_uid = str(tieba_uid) return req_proto.SerializeToString() def parse_body(body: bytes) -> UserInfo_TUid: res_proto = GetUserByTiebaUidResIdl_pb2.GetUserByTiebaUidResIdl() res_proto.ParseFromString(body) if code := res_proto.error.errorno: raise TiebaServerError(code, res_proto.error.errmsg) user_proto = res_proto.data.user user = UserInfo_TUid.from_proto(user_proto) return user async def request_http(http_core: HttpCore, tieba_uid: int) -> UserInfo_TUid: data = pack_proto(tieba_uid) request = http_core.pack_proto_request( yarl.URL.build( scheme="http", host=APP_BASE_HOST, path="/c/u/user/getUserByTiebaUid", query_string=f"cmd={CMD}", ), data, ) body = await http_core.net_core.send_request(request, read_bufsize=1024) return parse_body(body) async def request_ws(ws_core: WsCore, tieba_uid: int) -> UserInfo_TUid: data = pack_proto(tieba_uid) response = await ws_core.send(data, CMD) return parse_body(await response.read()) ================================================ FILE: src/aiotieba/api/tieba_uid2user_info/_classdef.py ================================================ from __future__ import annotations import dataclasses as dcs from typing import TYPE_CHECKING from ...exception import TbErrorExt if TYPE_CHECKING: from .._classdef import TypeMessage @dcs.dataclass class UserInfo_TUid(TbErrorExt): """ 用户信息 Attributes: err (Exception | None): 捕获的异常 user_id (int): user_id portrait (str): portrait user_name (str): 用户名 nick_name_new (str): 新版昵称 tieba_uid (int): 用户个人主页uid age (float): 吧龄 sign (str): 个性签名 is_god (bool): 是否大神 nick_name (str): 用户昵称 show_name (str): 显示名称 log_name (str): 用于在日志中记录用户信息 """ user_id: int = 0 portrait: str = "" user_name: str = "" nick_name_new: str = "" tieba_uid: int = 0 age: float = 0.0 sign: str = "" is_god: bool = False @staticmethod def from_proto(data_proto: TypeMessage) -> UserInfo_TUid: user_id = data_proto.id portrait = data_proto.portrait if "?" in portrait: portrait = portrait[:-13] user_name = data_proto.name nick_name_new = data_proto.name_show tieba_uid = int(data_proto.tieba_uid) age = float(data_proto.tb_age) sign = data_proto.intro is_god = bool(data_proto.new_god_data.status) return UserInfo_TUid(user_id, portrait, user_name, nick_name_new, tieba_uid, age, sign, is_god) def __str__(self) -> str: return self.user_name or self.portrait or str(self.user_id) def __eq__(self, obj: UserInfo_TUid) -> 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_new @property def show_name(self) -> str: return self.nick_name_new or self.user_name @property def log_name(self) -> str: if self.user_name: return self.user_name elif self.portrait: return f"{self.nick_name_new}/{self.portrait}" else: return str(self.user_id) ================================================ FILE: src/aiotieba/api/tieba_uid2user_info/protobuf/GetUserByTiebaUidReqIdl.proto ================================================ // tbclient.GetUserByTiebaUid.GetUserByTiebaUidReqIdl syntax = "proto3"; import "CommonReq.proto"; message GetUserByTiebaUidReqIdl { message DataReq { CommonReq common = 1; string tieba_uid = 2; } DataReq data = 1; } ================================================ FILE: src/aiotieba/api/tieba_uid2user_info/protobuf/GetUserByTiebaUidReqIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import CommonReq_pb2 as CommonReq__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1dGetUserByTiebaUidReqIdl.proto\x1a\x0f\x43ommonReq.proto"\x83\x01\n\x17GetUserByTiebaUidReqIdl\x12.\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32 .GetUserByTiebaUidReqIdl.DataReq\x1a\x38\n\x07\x44\x61taReq\x12\x1a\n\x06\x63ommon\x18\x01 \x01(\x0b\x32\n.CommonReq\x12\x11\n\ttieba_uid\x18\x02 \x01(\tb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetUserByTiebaUidReqIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETUSERBYTIEBAUIDREQIDL"]._serialized_start = 51 _globals["_GETUSERBYTIEBAUIDREQIDL"]._serialized_end = 182 _globals["_GETUSERBYTIEBAUIDREQIDL_DATAREQ"]._serialized_start = 126 _globals["_GETUSERBYTIEBAUIDREQIDL_DATAREQ"]._serialized_end = 182 ================================================ FILE: src/aiotieba/api/tieba_uid2user_info/protobuf/GetUserByTiebaUidResIdl.proto ================================================ // tbclient.GetUserByTiebaUid.GetUserByTiebaUidResIdl syntax = "proto3"; import "Error.proto"; import "User.proto"; message GetUserByTiebaUidResIdl { Error error = 1; message DataRes { User user = 1; } DataRes data = 2; } ================================================ FILE: src/aiotieba/api/tieba_uid2user_info/protobuf/GetUserByTiebaUidResIdl_pb2.py ================================================ """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _sym_db = _symbol_database.Default() from ..._protobuf import Error_pb2 as Error__pb2 from ..._protobuf import User_pb2 as User__pb2 DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( b'\n\x1dGetUserByTiebaUidResIdl.proto\x1a\x0b\x45rror.proto\x1a\nUser.proto"\x80\x01\n\x17GetUserByTiebaUidResIdl\x12\x15\n\x05\x65rror\x18\x01 \x01(\x0b\x32\x06.Error\x12.\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32 .GetUserByTiebaUidResIdl.DataRes\x1a\x1e\n\x07\x44\x61taRes\x12\x13\n\x04user\x18\x01 \x01(\x0b\x32\x05.Userb\x06proto3' ) _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "GetUserByTiebaUidResIdl_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None _globals["_GETUSERBYTIEBAUIDRESIDL"]._serialized_start = 59 _globals["_GETUSERBYTIEBAUIDRESIDL"]._serialized_end = 187 _globals["_GETUSERBYTIEBAUIDRESIDL_DATARES"]._serialized_start = 157 _globals["_GETUSERBYTIEBAUIDRESIDL_DATARES"]._serialized_end = 187 ================================================ FILE: src/aiotieba/api/top/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/top/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fname: str, fid: int, tid: int, is_vip: bool, is_set: bool) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("fid", fid), ("is_member_top", int(is_vip)), ("ntn", "set" if is_set else ""), ("tbs", http_core.account.tbs), ("word", fname), ("z", tid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/committop"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/unblock/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/unblock/_api.py ================================================ import yarl from ...const import WEB_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := res_json["no"]: raise TiebaServerError(code, res_json["error"]) async def request(http_core: HttpCore, fid: int, user_id: int) -> BoolResponse: data = [ ("fn", "-"), ("fid", fid), ("block_un", "-"), ("block_uid", user_id), ("tbs", http_core.account.tbs), ] request = http_core.pack_web_form_request( yarl.URL.build(scheme="https", host=WEB_BASE_HOST, path="/mo/q/bawublockclear"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/undislike_forum/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/undislike_forum/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("cuid", http_core.account.cuid), ("forum_id", fid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/excellent/submitCancelDislike"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/unfollow_forum/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/unfollow_forum/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fid: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("fid", fid), ("tbs", http_core.account.tbs), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/forum/unfavolike"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/unfollow_user/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/unfollow_user/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, portrait: str) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("portrait", portrait), ("tbs", http_core.account.tbs), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/user/unfollow"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/api/ungood/__init__.py ================================================ from ._api import parse_body, request ================================================ FILE: src/aiotieba/api/ungood/_api.py ================================================ import yarl from ...const import APP_BASE_HOST from ...core import HttpCore from ...exception import BoolResponse, TiebaServerError from ...helper import parse_json def parse_body(body: bytes) -> None: res_json = parse_json(body) if code := int(res_json["error_code"]): raise TiebaServerError(code, res_json["error_msg"]) async def request(http_core: HttpCore, fname: str, fid: int, tid: int) -> BoolResponse: data = [ ("BDUSS", http_core.account.BDUSS), ("fid", fid), ("tbs", http_core.account.tbs), ("word", fname), ("z", tid), ] request = http_core.pack_form_request( yarl.URL.build(scheme="https", host=APP_BASE_HOST, path="/c/c/bawu/commitgood"), data ) body = await http_core.net_core.send_request(request, read_bufsize=1024) parse_body(body) return BoolResponse() ================================================ FILE: src/aiotieba/client.py ================================================ from __future__ import annotations import logging import socket from typing import TYPE_CHECKING, Literal import aiohttp import yarl from .api import ( add_bawu, add_bawu_blacklist, add_blacklist_old, add_post, agree, block, del_bawu, del_bawu_blacklist, del_blacklist_old, del_post, del_posts, del_thread, del_threads, dislike_forum, follow_forum, follow_user, get_ats, get_bawu_blacklist, get_bawu_info, get_bawu_perm, get_bawu_postlogs, get_bawu_userlogs, get_blacklist, get_blacklist_old, get_blocks, get_cid, get_comments, get_dislike_forums, get_fans, get_fid, get_follow_forums, get_follows, get_forum, get_forum_detail, get_forum_level, get_group_msg, get_images, get_last_replyers, get_member_users, get_posts, get_rank_forums, get_rank_users, get_recom_status, get_recovers, get_replys, get_roomlist_by_fid, get_self_follow_forums, get_selfinfo_initNickname, get_selfinfo_moindex, get_square_forums, get_statistics, get_tab_map, get_threads, get_uinfo_getuserinfo_app, get_uinfo_getUserInfo_web, get_uinfo_panel, get_uinfo_user_json, get_unblock_appeals, get_user_contents, get_user_forum_info, good, handle_unblock_appeals, init_z_id, login, move, profile, recommend, recover, remove_fan, search_exact, send_chatroom_msg, send_msg, set_bawu_perm, set_blacklist, set_msg_readed, set_nickname_old, set_profile, set_thread_privacy, sign_forum, sign_forums, sign_growth, sync, tieba_uid2user_info, top, unblock, undislike_forum, unfollow_forum, unfollow_user, ungood, ) from .api._classdef import UserInfo from .config import ProxyConfig, TimeoutConfig from .const import LATEST_VERSION, STABLE_VERSION from .core import Account, BLCPCore, HttpCore, NetCore, WsCore from .enums import ( BawuPermType, BawuSearchType, BawuType, BlacklistType, Gender, GroupType, PostSortType, RankForumType, ReqUInfo, SearchType, ThreadSortType, WsStatus, ) from .exception import BoolResponse, IntResponse, StrResponse from .helper import deprecated from .helper.cache import ForumInfoCache from .helper.utils import handle_exception, is_portrait, is_user_name from .logging import get_logger as LOG if TYPE_CHECKING: import asyncio import datetime def _try_websocket(func): async def awrapper(self: Client, *args, **kwargs): if self._try_ws: await self.init_websocket() return await func(self, *args, **kwargs) awrapper.__name__ = func.__name__ return awrapper def _force_websocket(func): async def awrapper(self: Client, *args, **kwargs): await self.init_websocket() return await func(self, *args, **kwargs) awrapper.__name__ = func.__name__ return awrapper class Client: """ 贴吧客户端 Args: BDUSS (str, optional): BDUSS. Defaults to ''. STOKEN (str, optional): STOKEN. Defaults to ''. account (Account, optional): Account实例 该字段会覆盖前两个参数. Defaults to None. try_ws (bool, optional): 尝试使用websocket接口. Defaults to False. proxy (bool | ProxyConfig, optional): True则使用环境变量代理 False则禁用代理 输入ProxyConfig实例以手动配置代理. Defaults to False. timeout (TimeoutConfig, optional): 超时配置. Defaults to None. """ __slots__ = [ "_account", "_timeout", "_proxy", "_try_ws", "_connector", "_http_core", "_ws_core", "_user", "_blcp_core", ] def __init__( self, BDUSS: str = "", STOKEN: str = "", *, account: Account | None = None, try_ws: bool = False, proxy: bool | ProxyConfig = False, timeout: TimeoutConfig | None = None, ) -> None: if not isinstance(account, Account): account = Account(BDUSS, STOKEN) self._account = account if not isinstance(timeout, TimeoutConfig): timeout = TimeoutConfig() self._timeout = timeout if proxy is True: proxy = ProxyConfig.from_env() elif not proxy: proxy = ProxyConfig() self._proxy = proxy self._try_ws = try_ws self._user = UserInfo() async def __aenter__(self) -> Client: connector = aiohttp.TCPConnector( ttl_dns_cache=self._timeout.dns_ttl, family=socket.AF_INET, keepalive_timeout=self._timeout.http_keepalive, limit=0, ssl=False, ) self._connector = connector net_core = NetCore(connector, self._proxy, self._timeout) self._http_core = HttpCore(self._account, net_core) self._ws_core = WsCore(self._account, net_core) self._blcp_core = BLCPCore(account=self._account, net_core=net_core, user=self._user) return self async def __aexit__(self, exc_type=None, exc_val=None, exc_tb=None) -> None: await self._ws_core.close() await self._connector.close() def __hash__(self) -> int: return hash(self.account) def __eq__(self, obj: Client) -> bool: return self.account == obj.account @property def account(self) -> Account: return self._http_core.account @account.setter def account(self, new_account: Account) -> None: self._http_core.set_account(new_account) self._ws_core.set_account(new_account) @handle_exception(BoolResponse) async def init_websocket(self) -> BoolResponse: """ 初始化websocket Returns: BoolResponse: True无须执行 False失败 """ if self._ws_core.status == WsStatus.CLOSED: try: await self._ws_core.connect() await self.__upload_sec_key() except BaseException: self._ws_core._status = WsStatus.CLOSED raise return BoolResponse() async def __upload_sec_key(self) -> None: from .api import init_websocket from .core.websocket import MsgIDPair groups = await init_websocket.request(self._ws_core) mid_manager = self._ws_core.mid_manager for group in groups: if group.group_type == GroupType.PRIVATE_MSG: mid_manager.priv_gid = group.group_id mid_manager.gid2mid |= {g.group_id: MsgIDPair(g.last_msg_id, g.last_msg_id) for g in groups} self._ws_core._status = WsStatus.OPEN async def __init_tbs(self) -> None: if self.account.tbs: return await self.__login() @handle_exception(UserInfo) async def get_self_info(self, require: ReqUInfo = ReqUInfo.ALL) -> UserInfo: """ 获取本账号信息 Args: require (ReqUInfo): 指示需要获取的字段 Returns: TypeUserInfo: 用户信息 """ if not self._user.user_id: if require & ReqUInfo.BASIC: await self.__login() if not self._user.tieba_uid: if require == ReqUInfo.ALL: user = await self._get_uinfo_profile(self._user.user_id) self._user |= user elif require & (ReqUInfo.TIEBA_UID | ReqUInfo.NICK_NAME): await self.__get_selfinfo_initNickname() return self._user async def __login(self) -> None: user, tbs = await login.request(self._http_core) self._user |= user self.account.tbs = tbs async def __init_client_id(self) -> None: if self.account.client_id: return await self.__sync() async def __init_sample_id(self) -> None: if self.account.sample_id: return await self.__sync() async def __sync(self) -> None: client_id, sample_id = await sync.request(self._http_core) self.account.client_id = client_id self.account.sample_id = sample_id async def __init_z_id(self) -> None: if self.account.z_id: return z_id = await init_z_id.request(self._http_core) self.account.z_id = z_id @handle_exception(get_forum.Forum) async def get_forum(self, fname_or_fid: str | int) -> get_forum.Forum: """ 通过forum_id获取贴吧信息 此接口较`get_forum_detail`更强大 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先贴吧名 Returns: Forum: 贴吧信息 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) return await get_forum.request(self._http_core, fname) @handle_exception(get_forum_detail.Forum_detail) @_try_websocket async def get_forum_detail(self, fname_or_fid: str | int) -> get_forum_detail.Forum_detail: """ 通过forum_id获取贴吧信息 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先fid Returns: Forum_detail: 贴吧信息 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if self._ws_core.status == WsStatus.OPEN: return await get_forum_detail.request_ws(self._ws_core, fid) return await get_forum_detail.request_http(self._http_core, fid) async def __get_fid(self, fname: str) -> int: if fid := ForumInfoCache.get_fid(fname): return fid fid = await get_fid.request(self._http_core, fname) ForumInfoCache.add_forum(fname, fid) return fid @handle_exception(IntResponse) async def get_fid(self, fname: str) -> IntResponse: """ 通过贴吧名获取forum_id Args: fname (str): 贴吧名 Returns: IntResponse: forum_id """ fid = await self.__get_fid(fname) return IntResponse(fid) async def __get_fname(self, fid: int) -> str: if fname := ForumInfoCache.get_fname(fid): return fname fdetail = await self.get_forum_detail(fid) fname = fdetail.fname if fname: ForumInfoCache.add_forum(fname, fid) return fname @handle_exception(StrResponse) async def get_fname(self, fid: int) -> StrResponse: """ 通过forum_id获取贴吧名 Args: fid (int): forum_id Returns: StrResponse: 贴吧名 """ fname = await self.__get_fname(fid) return StrResponse(fname) @handle_exception(get_threads.Threads) @_try_websocket async def get_threads( self, fname_or_fid: str | int, /, pn: int = 1, *, rn: int = 30, sort: ThreadSortType = ThreadSortType.REPLY, is_good: bool = False, ) -> get_threads.Threads: """ 获取首页帖子 Args: fname_or_fid (str | int): 贴吧名或fid 优先贴吧名 pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 30. Max to 100. sort (ThreadSortType, optional): HOT热门排序 REPLY按回复时间 CREATE按发布时间 FOLLOW关注的人. Defaults to ThreadSortType.REPLY. is_good (bool, optional): True则获取精品区帖子 False则获取普通区帖子. Defaults to False. Returns: Threads: 帖子列表 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) if self._ws_core.status == WsStatus.OPEN: return await get_threads.request_ws(self._ws_core, fname, pn, rn, sort, is_good, STABLE_VERSION) return await get_threads.request_http(self._http_core, fname, pn, rn, sort, is_good, STABLE_VERSION) @handle_exception(get_posts.Posts) @_try_websocket async def get_posts( self, tid: int, /, pn: int = 1, *, rn: int = 30, sort: PostSortType = PostSortType.ASC, only_thread_author: bool = False, with_comments: bool = False, comment_sort_by_agree: bool = True, comment_rn: int = 4, ) -> get_posts.Posts: """ 获取主题帖内回复 Args: tid (int): 所在主题帖tid pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 30. sort (PostSortType, optional): ASC时间顺序 DESC时间倒序 HOT热门序. Defaults to PostSortType.ASC. only_thread_author (bool, optional): True则只看楼主 False则请求全部. Defaults to False. with_comments (bool, optional): True则同时请求高赞楼中楼 False则返回的Post.comments字段为空. Defaults to False. comment_sort_by_agree (bool, optional): True则楼中楼按点赞数顺序 False则楼中楼按时间顺序. Defaults to True. comment_rn (int, optional): 请求的楼中楼数量. Defaults to 4. Max to 50. Returns: Posts: 回复列表 """ if self._ws_core.status == WsStatus.OPEN: return await get_posts.request_ws( self._ws_core, tid, pn, rn, sort, only_thread_author, with_comments, comment_sort_by_agree, comment_rn ) return await get_posts.request_http( self._http_core, tid, pn, rn, sort, only_thread_author, with_comments, comment_sort_by_agree, comment_rn ) @handle_exception(get_comments.Comments) @_try_websocket async def get_comments( self, tid: int, pid: int, /, pn: int = 1, *, is_comment: bool = False ) -> get_comments.Comments: """ 获取楼中楼回复 Args: tid (int): 所在主题帖tid pid (int): 所在楼层的pid或楼中楼的pid pn (int, optional): 页码. Defaults to 1. is_comment (bool, optional): pid是否指向楼中楼 若指向楼中楼则获取其附近的楼中楼列表. Defaults to False. Returns: Comments: 楼中楼列表 """ if self._ws_core.status == WsStatus.OPEN: return await get_comments.request_ws(self._ws_core, tid, pid, pn, is_comment) return await get_comments.request_http(self._http_core, tid, pid, pn, is_comment) @handle_exception(get_last_replyers.Threads_lp) @_try_websocket async def get_last_replyers( self, fname_or_fid: str | int, /, pn: int = 1, *, rn: int = 30, sort: ThreadSortType = ThreadSortType.REPLY, is_good: bool = False, ) -> get_last_replyers.Threads_lp: """ 通过旧版接口获取带最后回复人的首页帖子 Args: fname_or_fid (str | int): 贴吧名或fid 优先贴吧名 pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 30. Max to 100. sort (ThreadSortType, optional): HOT热门排序 REPLY按回复时间 CREATE按发布时间 FOLLOW关注的人. Defaults to ThreadSortType.REPLY. is_good (bool, optional): True则获取精品区帖子 False则获取普通区帖子. Defaults to False. Returns: Threads_lp: 带最后回复人的帖子列表 Note: 该接口主要用于反挖坟 目前未封装完整的返回信息 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) if self._ws_core.status == WsStatus.OPEN: return await get_last_replyers.request_ws(self._ws_core, fname, pn, rn, sort, is_good) return await get_last_replyers.request_http(self._http_core, fname, pn, rn, sort, is_good) @handle_exception(search_exact.ExactSearches) async def search_exact( self, fname_or_fid: str | int, query: str, /, pn: int = 1, *, rn: int = 30, search_type: SearchType = SearchType.ALL, only_thread: bool = False, ) -> search_exact.ExactSearches: """ 贴吧搜索 Args: fname_or_fid (str | int): 查询的贴吧名或fid 优先贴吧名 query (str): 查询文本 pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 30. search_type (SearchType, optional): 查询模式 默认查询全部. Defaults to SearchType.ALL. only_thread (bool, optional): 是否仅查询主题帖. Defaults to False. Returns: ExactSearches: 搜索结果列表 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) return await search_exact.request(self._http_core, fname, query, pn, rn, search_type, only_thread) @handle_exception(profile.UserInfo_pf) @_try_websocket async def _get_uinfo_profile(self, uid_or_portrait: str | int) -> profile.UserInfo_pf: """ 接口 https://tiebac.baidu.com/c/u/user/profile Args: uid_or_portrait (str | int): 用户id user_id / portrait Returns: UserInfo_pf: 包含最全面的用户信息 """ if self._ws_core.status == WsStatus.OPEN: return await profile.get_uinfo_profile.request_ws(self._ws_core, uid_or_portrait) return await profile.get_uinfo_profile.request_http(self._http_core, uid_or_portrait) @handle_exception(get_uinfo_getuserinfo_app.UserInfo_guinfo_app) @_try_websocket async def _get_uinfo_getuserinfo(self, user_id: int) -> get_uinfo_getuserinfo_app.UserInfo_guinfo_app: """ 接口 https://tiebac.baidu.com/c/u/user/getuserinfo Args: user_id (int): 用户id user_id Returns: UserInfo_guinfo_app: 包含 user_id / portrait / user_name / nick_name_old / 性别 / 是否大神 / 是否超级会员 """ if self._ws_core.status == WsStatus.OPEN: user = await get_uinfo_getuserinfo_app.request_ws(self._ws_core, user_id) else: user = await get_uinfo_getuserinfo_app.request_http(self._http_core, user_id) if (user_id := user.user_id) < 0: user.user_id = 0xFFFFFFFF + user_id return user @handle_exception(get_uinfo_getUserInfo_web.UserInfo_guinfo_web) async def _get_uinfo_getUserInfo(self, user_id: int) -> get_uinfo_getUserInfo_web.UserInfo_guinfo_web: """ 接口 http://tieba.baidu.com/im/pcmsg/query/getUserInfo Args: user_id (int): 用户id user_id Returns: UserInfo_guinfo_web: 包含 user_id / portrait / user_name / nick_name_new Note: 该接口需要BDUSS """ user = await get_uinfo_getUserInfo_web.request(self._http_core, user_id) user.user_id = user_id return user @handle_exception(get_uinfo_user_json.UserInfo_json) async def _get_uinfo_user_json(self, user_name: str) -> get_uinfo_user_json.UserInfo_json: """ 接口 http://tieba.baidu.com/i/sys/user_json Args: user_name (str): 用户id user_name Returns: UserInfo_json: 包含 user_id / portrait / user_name """ user = await get_uinfo_user_json.request(self._http_core, user_name) user.user_name = user_name return user @handle_exception(get_uinfo_panel.UserInfo_panel) async def _get_uinfo_panel(self, name_or_portrait: str) -> get_uinfo_panel.UserInfo_panel: """ 接口 https://tieba.baidu.com/home/get/panel Args: name_or_portrait (str): 用户id user_name / portrait Returns: UserInfo_panel: 包含较全面的用户信息 Note: 从2022.08.30开始服务端不再返回user_id字段 请谨慎使用\n 该接口可判断用户是否被屏蔽\n 该接口rps阈值较低 """ return await get_uinfo_panel.request(self._http_core, name_or_portrait) async def get_user_info(self, id_: str | int, /, require: ReqUInfo = ReqUInfo.ALL) -> UserInfo: """ 获取用户信息 Args: id_ (str | int): 用户id user_id / portrait / user_name require (ReqUInfo): 指示需要获取的字段 Returns: UserInfo: 用户信息 """ if not id_: LOG().warning("Null input") return UserInfo() if isinstance(id_, int): if (require | ReqUInfo.BASIC) == ReqUInfo.BASIC: # 仅有BASIC需求 return await self._get_uinfo_getuserinfo(id_) else: return await self._get_uinfo_profile(id_) elif is_portrait(id_): if (require | ReqUInfo.BASIC) == ReqUInfo.BASIC: # 仅有BASIC需求 if not require & ReqUInfo.USER_ID: # 无USER_ID需求 return await self._get_uinfo_panel(id_) return await self._get_uinfo_profile(id_) else: if (require | ReqUInfo.BASIC) == ReqUInfo.BASIC: return await self._get_uinfo_user_json(id_) elif require & ReqUInfo.NICK_NAME and not require & ReqUInfo.USER_ID: # 有NICK_NAME需求但无USER_ID需求 return await self._get_uinfo_panel(id_) else: user = await self._get_uinfo_user_json(id_) return await self._get_uinfo_profile(user.portrait) @handle_exception(tieba_uid2user_info.UserInfo_TUid) @_try_websocket async def tieba_uid2user_info(self, tieba_uid: int) -> tieba_uid2user_info.UserInfo_TUid: """ 通过tieba_uid获取用户信息 Args: tieba_uid (int): 用户id tieba_uid Returns: UserInfo_TUid: 包含较全面的用户信息 Note: 请注意tieba_uid与旧版user_id的区别 """ if self._ws_core.status == WsStatus.OPEN: return await tieba_uid2user_info.request_ws(self._ws_core, tieba_uid) return await tieba_uid2user_info.request_http(self._http_core, tieba_uid) @handle_exception(profile.Homepage) @_try_websocket async def get_homepage(self, id_: str | int, /, pn: int = 1) -> profile.Homepage: """ 获取用户个人页信息 Args: id_ (str | int): 用户id user_id / user_name / portrait 优先user_id pn (int, optional): 页码. Defaults to 1. Returns: Homepage: 用户个人页信息 """ if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ if self._ws_core.status == WsStatus.OPEN: return await profile.get_homepage.request_ws(self._ws_core, user_id, pn) return await profile.get_homepage.request_http(self._http_core, user_id, pn) @handle_exception(get_follows.Follows) async def get_follows(self, id_: str | int | None = None, /, pn: int = 1) -> get_follows.Follows: """ 获取关注列表 Args: pn (int, optional): 页码. Defaults to 1. id_ (str | int | None): 用户id user_id / user_name / portrait 优先user_id 默认为None即获取本账号信息. Defaults to None. Returns: Follows: 关注列表 """ if id_ is None: user = await self.get_self_info(ReqUInfo.USER_ID) user_id = user.user_id elif not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ return await get_follows.request(self._http_core, user_id, pn) @handle_exception(get_fans.Fans) async def get_fans(self, id_: str | int | None = None, /, pn: int = 1) -> get_fans.Fans: """ 获取粉丝列表 Args: pn (int, optional): 页码. Defaults to 1. id_ (str | int | None): 用户id user_id / user_name / portrait 优先user_id 默认为None即获取本账号信息. Defaults to None. Returns: Fans: 粉丝列表 """ if id_ is None: user = await self.get_self_info(ReqUInfo.USER_ID) user_id = user.user_id elif not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ return await get_fans.request(self._http_core, user_id, pn) @handle_exception(get_blacklist.BlacklistUsers) async def get_blacklist(self) -> get_blacklist.BlacklistUsers: """ 获取完整的新版用户黑名单列表 Returns: BlacklistUsers: 新版用户黑名单列表 """ return await get_blacklist.request(self._http_core) @handle_exception(get_blacklist_old.BlacklistOldUsers) @_try_websocket async def get_blacklist_old(self, pn: int = 1, /, *, rn: int = 10) -> get_blacklist_old.BlacklistOldUsers: """ 获取旧版用户黑名单列表 Args: pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 10. Max to Inf. Returns: BlacklistOldUsers: 旧版用户黑名单列表 """ if self._ws_core.status == WsStatus.OPEN: return await get_blacklist_old.request_ws(self._ws_core, pn, rn) return await get_blacklist_old.request_http(self._http_core, pn, rn) @handle_exception(get_follow_forums.FollowForums) async def get_follow_forums( self, id_: str | int, /, pn: int = 1, *, rn: int = 50 ) -> get_follow_forums.FollowForums: """ 获取用户关注贴吧列表 Args: id_ (str | int): 用户id user_id / user_name / portrait 优先user_id pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 50. Max to Inf. Returns: FollowForums: 用户关注贴吧列表 """ if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ return await get_follow_forums.request(self._http_core, user_id, pn, rn) @handle_exception(get_user_forum_info.UserForumInfo) async def get_user_forum_info( self, fname_or_fid: str | int, id_: str | int, / ) -> get_user_forum_info.UserForumInfo: """ 获取用户在某吧内的信息 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先fid id_ (str | int): 用户id user_id / user_name / portrait 优先portrait Returns: UserForumInfo: 用户在吧内的信息 """ if not fname_or_fid or not id_: LOG().warning("Null input") return get_user_forum_info.UserForumInfo() fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if not is_portrait(id_): user = await self.get_user_info(id_, ReqUInfo.PORTRAIT) portrait = user.portrait else: portrait = id_ if not portrait: return get_user_forum_info.UserForumInfo() return await get_user_forum_info.request(self._http_core, fid, portrait) @handle_exception(get_self_follow_forums.SelfFollowForums) async def get_self_follow_forums(self, pn: int = 1, *, rn: int = 200) -> get_self_follow_forums.SelfFollowForums: """ 获取本账号关注贴吧列表 Args: pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 200. Max to 200. Returns: SelfFollowForums: 本账号关注贴吧列表 Note: 本接口需要STOKEN """ return await get_self_follow_forums.request(self._http_core, pn, rn) @handle_exception(get_dislike_forums.DislikeForums) @_try_websocket async def get_dislike_forums(self, pn: int = 1, /, *, rn: int = 20) -> get_dislike_forums.DislikeForums: """ 获取首页推荐屏蔽的贴吧列表 Args: pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 20. Max to 20. Returns: DislikeForums: 首页推荐屏蔽的贴吧列表 """ if self._ws_core.status == WsStatus.OPEN: return await get_dislike_forums.request_ws(self._ws_core, pn, rn) return await get_dislike_forums.request_http(self._http_core, pn, rn) @handle_exception(get_user_contents.UserPostss) @_try_websocket async def get_self_posts(self, pn: int = 1, *, rn: int = 20): """ 获取当前用户发布的回复列表 Args: pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 20. Max to 74. Returns: UserPostss: 回复列表 """ user = await self.get_self_info(ReqUInfo.USER_ID) user_id = user.user_id if self._ws_core.status == WsStatus.OPEN: return await get_user_contents.get_posts.request_ws(self._ws_core, user_id, pn, rn, LATEST_VERSION) return await get_user_contents.get_posts.request_http(self._http_core, user_id, pn, rn, LATEST_VERSION) @handle_exception(get_user_contents.UserPostss) async def get_user_posts(self, id_: str | int, pn: int = 1, *, rn: int = 20) -> get_user_contents.UserPostss: """ 获取用户发布的回复列表 Args: id_ (str | int): 用户id user_id / user_name / portrait 优先user_id pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 20. Max to 74. Returns: UserPostss: 回复列表 """ if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ USER_POSTS_VERSION = "8" if self._ws_core.status == WsStatus.OPEN: return await get_user_contents.get_posts.request_ws(self._ws_core, user_id, pn, rn, USER_POSTS_VERSION) return await get_user_contents.get_posts.request_http(self._http_core, user_id, pn, rn, USER_POSTS_VERSION) @handle_exception(get_user_contents.UserThreads) @_try_websocket async def get_self_threads(self, pn: int = 1, *, public_only: bool = False) -> get_user_contents.UserThreads: """ 获取当前用户发布的主题帖列表 Args: pn (int, optional): 页码. Defaults to 1. public_only (bool, optional): 是否仅获取公开主题帖 该选项在获取他人主题帖时无效. Defaults to False. Returns: UserThreads: 主题帖列表 """ user = await self.get_self_info(ReqUInfo.USER_ID) user_id = user.user_id if self._ws_core.status == WsStatus.OPEN: return await get_user_contents.get_threads.request_ws(self._ws_core, user_id, pn, public_only) return await get_user_contents.get_threads.request_http(self._http_core, user_id, pn, public_only) @handle_exception(get_user_contents.UserThreads) @_try_websocket async def get_user_threads(self, id_: str | int, pn: int = 1) -> get_user_contents.UserThreads: """ 获取用户发布的主题帖列表 Args: id_ (str | int): 用户id user_id / user_name / portrait 优先user_id pn (int, optional): 页码. Defaults to 1. Returns: UserThreads: 主题帖列表 """ if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ if self._ws_core.status == WsStatus.OPEN: return await get_user_contents.get_threads.request_ws(self._ws_core, user_id, pn, False) return await get_user_contents.get_threads.request_http(self._http_core, user_id, pn, False) @handle_exception(get_replys.Replys) @_try_websocket async def get_replys(self, pn: int = 1) -> get_replys.Replys: """ 获取回复信息 Args: pn (int, optional): 页码. Defaults to 1. Returns: Replys: 回复列表 """ if self._ws_core.status == WsStatus.OPEN: return await get_replys.request_ws(self._ws_core, pn) return await get_replys.request_http(self._http_core, pn) @handle_exception(get_ats.Ats) async def get_ats(self, pn: int = 1) -> get_ats.Ats: """ 获取@信息 Args: pn (int, optional): 页码. Defaults to 1. Returns: Ats: at列表 """ return await get_ats.request(self._http_core, pn) @handle_exception(get_images.ImageBytes) async def get_image_bytes(self, img_url: str) -> get_images.ImageBytes: """ 从链接获取静态图像的原始字节流 Args: img_url (str): 图像链接 Returns: ImageBytes: 未解码的原始字节流 """ return await get_images.request_bytes(self._http_core, yarl.URL(img_url)) @handle_exception(get_images.Image) async def get_image(self, img_url: str) -> get_images.Image: """ 从链接获取静态图像 Args: img_url (str): 图像链接 Returns: Image: 图像 """ return await get_images.request(self._http_core, yarl.URL(img_url)) @handle_exception(get_images.Image) async def hash2image(self, raw_hash: str, /, size: Literal["s", "m", "l"] = "s") -> get_images.Image: """ 通过百度图库hash获取静态图像 Args: raw_hash (str): 百度图库hash size (Literal['s', 'm', 'l'], optional): 获取图像的大小 s为宽720 m为宽960 l为原图. Defaults to 's'. Returns: Image: 图像 """ if size == "s": img_url = yarl.URL.build( scheme="http", host="imgsrc.baidu.com", path=f"/forum/w=720;q=60;g=0/sign=__/{raw_hash}.jpg" ) elif size == "m": img_url = yarl.URL.build( scheme="http", host="imgsrc.baidu.com", path=f"/forum/w=960;q=60;g=0/sign=__/{raw_hash}.jpg" ) elif size == "l": img_url = yarl.URL.build(scheme="http", host="imgsrc.baidu.com", path=f"/forum/pic/item/{raw_hash}.jpg") else: LOG().warning(f"Invalid size={size}") return get_images.Image() return await get_images.request(self._http_core, img_url) @handle_exception(get_images.Image) async def get_portrait(self, id_: str | int, /, size: Literal["s", "m", "l"] = "s") -> get_images.Image: """ 获取用户头像 Args: id_ (str | int): 用户id user_id / user_name / portrait 优先portrait size (Literal['s', 'm', 'l'], optional): 获取头像的大小 s为55x55 m为110x110 l为原图. Defaults to 's'. Returns: Image: 头像 """ if not is_portrait(id_): user = await self.get_user_info(id_, ReqUInfo.PORTRAIT) portrait = user.portrait else: portrait = id_ if size == "s": path = "n" elif size == "m": path = "" elif size == "l": path = "h" else: LOG().warning(f"Invalid size={size}") return get_images.Image() img_url = yarl.URL.build(scheme="http", host="tb.himg.baidu.com", path=f"/sys/portrait{path}/item/{portrait}") return await get_images.request(self._http_core, img_url) async def __get_selfinfo_initNickname(self) -> None: user = await get_selfinfo_initNickname.request(self._http_core) self._user |= user async def __get_selfinfo_moindex(self) -> None: user = await get_selfinfo_moindex.request(self._http_core) self._user |= user @handle_exception(get_square_forums.SquareForums) @_try_websocket async def get_square_forums(self, cname: str, /, pn: int = 1, *, rn: int = 20) -> get_square_forums.SquareForums: """ 获取吧广场列表 Args: cname (str): 类别名 pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 20. Max to Inf. Returns: SquareForums: 吧广场列表 """ if self._ws_core.status == WsStatus.OPEN: return await get_square_forums.request_ws(self._ws_core, cname, pn, rn) return await get_square_forums.request_http(self._http_core, cname, pn, rn) @handle_exception(get_bawu_info.BawuInfo) @_try_websocket async def get_bawu_info(self, fname_or_fid: str | int) -> get_bawu_info.BawuInfo: """ 获取吧务团队信息 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先fid Returns: BawuInfo: 吧务团队信息 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if self._ws_core.status == WsStatus.OPEN: return await get_bawu_info.request_ws(self._ws_core, fid) return await get_bawu_info.request_http(self._http_core, fid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def add_bawu( self, fname_or_fid: str | int, /, id_: str | int, *, bawu_type: BawuType = BawuType.MANAGER ) -> BoolResponse: """ 添加吧务 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先fid id_ (str | int): 用户id user_id / user_name / portrait 优先user_name bawu_type (BawuType): 吧务类型. Defaults to BawuType.MANAGER. Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if not is_user_name(id_): user = await self.get_user_info(id_, ReqUInfo.USER_NAME) user_name = user.user_name else: user_name = id_ await self.__init_tbs() return await add_bawu.request(self._http_core, fid, user_name, bawu_type) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def del_bawu( self, fname_or_fid: str | int, /, id_: str | int, *, bawu_type: BawuType = BawuType.MANAGER ) -> BoolResponse: """ 删除吧务 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先fid id_ (str | int): 用户id user_id / user_name / portrait 优先portrait bawu_type (BawuType): 吧务类型. Defaults to BawuType.MANAGER. Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if not is_portrait(id_): user = await self.get_user_info(id_, ReqUInfo.PORTRAIT) portrait = user.portrait else: portrait = id_ return await del_bawu.request(self._http_core, fid, portrait, bawu_type) @handle_exception(get_bawu_perm.BawuPerm) async def get_bawu_perm(self, fname_or_fid: str | int, /, id_: str | int) -> get_bawu_perm.BawuPerm: """ 获取指定吧务已分配的权限 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先fid id_ (str | int): 用户id user_id / user_name / portrait 优先portrait Returns: BawuPerm: 吧务已分配的权限 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if not is_portrait(id_): user = await self.get_user_info(id_, ReqUInfo.PORTRAIT) portrait = user.portrait else: portrait = id_ return await get_bawu_perm.request(self._http_core, fid, portrait) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def set_bawu_perm( self, fname_or_fid: str | int, /, id_: str | int, *, perms: BawuPermType = BawuPermType.NULL ) -> BoolResponse: """ 为指定吧务分配权限 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先fid id_ (str | int): 用户id user_id / user_name / portrait 优先portrait perms (BawuPermType): 待分配的权限. Defaults to BawuPermType.NULL. Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if not is_portrait(id_): user = await self.get_user_info(id_, ReqUInfo.PORTRAIT) portrait = user.portrait else: portrait = id_ return await set_bawu_perm.request(self._http_core, fid, portrait, perms) @handle_exception(get_tab_map.TabMap) @_try_websocket async def get_tab_map(self, fname_or_fid: str | int) -> get_tab_map.TabMap: """ 获取分区名到分区id的映射 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先贴吧名 Returns: TabMap: 分区名到分区id的映射 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) if self._ws_core.status == WsStatus.OPEN: return await get_tab_map.request_ws(self._ws_core, fname) return await get_tab_map.request_http(self._http_core, fname) @handle_exception(get_rank_users.RankUsers) async def get_rank_users(self, fname_or_fid: str | int, /, pn: int = 1) -> get_rank_users.RankUsers: """ 获取pn页的等级排行榜用户列表 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先贴吧名 pn (int, optional): 页码. Defaults to 1. Returns: RankUsers: 等级排行榜用户列表 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) return await get_rank_users.request(self._http_core, fname, pn) @handle_exception(get_member_users.MemberUsers) async def get_member_users(self, fname_or_fid: str | int, /, pn: int = 1) -> get_member_users.MemberUsers: """ 获取pn页的最新关注用户列表 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先贴吧名 pn (int, optional): 页码. Defaults to 1. Returns: MemberUsers: 最新关注用户列表 Note: 本接口需要STOKEN """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) return await get_member_users.request(self._http_core, fname, pn) @handle_exception(get_rank_forums.RankForums) async def get_rank_forums( self, fname_or_fid: str | int, /, pn: int = 1, *, rank_type: RankForumType = RankForumType.WEEKLY ) -> get_rank_forums.RankForums: """ 获取pn页的吧签到排行表 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先贴吧名 pn (int, optional): 页码. Defaults to 1. rank_type (RankForumType, optional): 榜单类型 默认为周榜. Defaults to RankForumType.WEEKLY. Returns: RankForums: 吧签到排行表 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) return await get_rank_forums.request(self._http_core, fname, pn, rank_type) @handle_exception(get_blocks.Blocks) async def get_blocks(self, fname_or_fid: str | int, /, name: str = "", pn: int = 1) -> get_blocks.Blocks: """ 获取pn页的待解封用户列表 Args: fname_or_fid (str | int): 目标贴吧的贴吧名或fid 优先fid name (str, optional): 通过被封禁用户的用户名/昵称查询 默认为空即查询全部. Defaults to ''. pn (int, optional): 页码. Defaults to 1. Returns: Blocks: 待解封用户列表 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) return await get_blocks.request(self._http_core, fid, name, pn) @handle_exception(get_recovers.Recovers) async def get_recovers( self, fname_or_fid: str | int, /, pn: int = 1, *, rn: int = 10, id_: str | int | None = None ) -> get_recovers.Recovers: """ 获取pn页的待恢复帖子列表 Args: fname_or_fid (str | int): 目标贴吧的贴吧名或fid 优先fid pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 10. Max to 50. id_ (str | int, optional): 用于查询的被删帖用户的id user_id / user_name / portrait 优先user_id. Defaults to None. Returns: Recovers: 待恢复帖子列表 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if id_ and not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ return await get_recovers.request(self._http_core, fid, user_id, pn, rn) @handle_exception(get_bawu_userlogs.Userlogs) async def get_bawu_userlogs( self, fname_or_fid: str | int, /, pn: int = 1, *, search_value: str = "", search_type: BawuSearchType = BawuSearchType.USER, start_dt: datetime.datetime | None = None, end_dt: datetime.datetime | None = None, op_type: int = 0, ) -> get_bawu_userlogs.Userlogs: """ 获取吧务用户管理日志表 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先贴吧名 pn (int, optional): 页码. Defaults to 1. search_value (str, optional): 搜索关键字. Defaults to ''. search_type (BawuSearchType, optional): 搜索类型. Defaults to BawuSearchType.USER. start_dt (datetime.datetime, optional): 搜索的起始时间(含). Defaults to None. end_dt (datetime.datetime, optional): 搜索的结束时间(含). Defaults to None. op_type (int, optional): 搜索操作类型. Defaults to 0. Returns: Userlogs: 吧务用户管理日志表 Note: 本接口需要STOKEN """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) return await get_bawu_userlogs.request( self._http_core, fname, pn, search_value, search_type, start_dt, end_dt, op_type ) @handle_exception(get_bawu_postlogs.Postlogs) async def get_bawu_postlogs( self, fname_or_fid: str | int, /, pn: int = 1, *, search_value: str = "", search_type: BawuSearchType = BawuSearchType.USER, start_dt: datetime.datetime | None = None, end_dt: datetime.datetime | None = None, op_type: int = 0, ) -> get_bawu_postlogs.Postlogs: """ 获取吧务帖子管理日志表 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先贴吧名 pn (int, optional): 页码. Defaults to 1. search_value (str, optional): 搜索关键字. Defaults to ''. search_type (BawuSearchType, optional): 搜索类型. Defaults to BawuSearchType.USER. start_dt (datetime.datetime, optional): 搜索的起始时间(含). Defaults to None. end_dt (datetime.datetime, optional): 搜索的结束时间(含). Defaults to None. op_type (int, optional): 搜索操作类型. Defaults to 0. Returns: Postlogs: 吧务帖子管理日志表 Note: 本接口需要STOKEN """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) return await get_bawu_postlogs.request( self._http_core, fname, pn, search_value, search_type, start_dt, end_dt, op_type ) @handle_exception(get_unblock_appeals.Appeals) async def get_unblock_appeals( self, fname_or_fid: str | int, /, pn: int = 1, *, rn: int = 5 ) -> get_unblock_appeals.Appeals: """ 获取申诉请求列表 Args: fname_or_fid (str | int): 目标贴吧的贴吧名或fid 优先fid pn (int, optional): 页码. Defaults to 1. rn (int, optional): 请求的条目数. Defaults to 5. Max to 50. Returns: Appeals: 申诉请求列表 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await get_unblock_appeals.request(self._http_core, fid, pn, rn) @handle_exception(get_bawu_blacklist.BawuBlacklistUsers) async def get_bawu_blacklist( self, fname_or_fid: str | int, /, pn: int = 1 ) -> get_bawu_blacklist.BawuBlacklistUsers: """ 获取pn页的吧务黑名单列表 Args: fname_or_fid (str | int): 目标贴吧的贴吧名或fid 优先贴吧名 pn (int, optional): 页码. Defaults to 1. Returns: BlacklistUsers: 吧务黑名单列表 Note: 本接口需要STOKEN """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) return await get_bawu_blacklist.request(self._http_core, fname, pn) @handle_exception(get_statistics.Statistics) async def get_statistics(self, fname_or_fid: str | int) -> get_statistics.Statistics: """ 获取吧务后台中最近24天的统计数据 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先fid Returns: Statistics: 吧务后台统计信息 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) return await get_statistics.request(self._http_core, fid) @handle_exception(get_recom_status.RecomStatus) async def get_recom_status(self, fname_or_fid: str | int) -> get_recom_status.RecomStatus: """ 获取大吧主推荐功能的月度配额状态 Args: fname_or_fid (str | int): 目标贴吧名或fid 优先fid Returns: RecomStatus: 大吧主推荐功能的月度配额状态 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) return await get_recom_status.request(self._http_core, fid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def block( self, fname_or_fid: str | int, /, id_: str | int, *, day: int = 1, reason: str = "" ) -> BoolResponse: """ 封禁用户 Args: fname_or_fid (str | int): 所在贴吧的贴吧名或fid 优先fid id_ (str | int): 用户id user_id / user_name / portrait 优先portrait day (int, optional): 封禁天数. Defaults to 1. reason (str, optional): 封禁理由. Defaults to ''. Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if not is_portrait(id_): user = await self.get_user_info(id_, ReqUInfo.PORTRAIT) portrait = user.portrait else: portrait = id_ await self.__init_tbs() return await block.request(self._http_core, fid, portrait, day, reason) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def unblock(self, fname_or_fid: str | int, /, id_: str | int) -> BoolResponse: """ 解封用户 Args: fname_or_fid (str | int): 所在贴吧的贴吧名或fid 优先fid id_ (str | int): 用户id user_id / user_name / portrait 优先user_id Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ await self.__init_tbs() return await unblock.request(self._http_core, fid, user_id) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def add_bawu_blacklist(self, fname_or_fid: str | int, /, id_: str | int) -> BoolResponse: """ 添加贴吧黑名单 Args: fname_or_fid (str | int): 目标贴吧的贴吧名或fid 优先贴吧名 id_ (str | int): 用户id user_id / user_name / portrait 优先user_id Returns: BoolResponse: True成功 False失败 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ await self.__init_tbs() return await add_bawu_blacklist.request(self._http_core, fname, user_id) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def del_bawu_blacklist(self, fname_or_fid: str | int, /, id_: str | int) -> BoolResponse: """ 移出贴吧黑名单 Args: fname_or_fid (str | int): 目标贴吧的贴吧名或fid 优先贴吧名 id_ (str | int): 用户id user_id / user_name / portrait 优先user_id Returns: BoolResponse: True成功 False失败 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ await self.__init_tbs() return await del_bawu_blacklist.request(self._http_core, fname, user_id) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def hide_thread(self, fname_or_fid: str | int, /, tid: int) -> BoolResponse: """ 屏蔽主题帖 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tid (int): 待屏蔽的主题帖tid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await del_thread.request(self._http_core, fid, tid, is_hide=True) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def del_thread(self, fname_or_fid: str | int, /, tid: int) -> BoolResponse: """ 删除主题帖 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tid (int): 待删除的主题帖tid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await del_thread.request(self._http_core, fid, tid, is_hide=False) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def del_threads(self, fname_or_fid: str | int, /, tids: list[int], *, block: bool = False) -> BoolResponse: """ 批量删除主题帖 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tids (list[int]): 待删除的主题帖tid列表. Length Max to 30. block (bool, optional): 是否同时封一天. Defaults to False. Returns: BoolResponse: True成功 False失败 部分成功返回True """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await del_threads.request(self._http_core, fid, tids, block) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def del_post(self, fname_or_fid: str | int, /, tid: int, pid: int) -> BoolResponse: """ 删除回复 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tid (int): 所在主题帖tid pid (int): 待删除的回复pid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await del_post.request(self._http_core, fid, tid, pid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def del_posts( self, fname_or_fid: str | int, /, tid: int, pids: list[int], *, block: bool = False ) -> BoolResponse: """ 批量删除回复 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tid (int): 所在主题帖tid pids (list[int]): 待删除的回复pid列表. Length Max to 30. block (bool, optional): 是否同时封一天. Defaults to False. Returns: BoolResponse: True成功 False失败 部分成功返回True """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await del_posts.request(self._http_core, fid, tid, pids, block) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def unhide_thread(self, fname_or_fid: str | int, /, tid: int) -> BoolResponse: """ 解除主题帖屏蔽 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tid (int, optional): 待解除屏蔽的主题帖tid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await recover.request(self._http_core, fid, tid, 0, is_hide=True) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def recover_thread(self, fname_or_fid: str | int, /, tid: int) -> BoolResponse: """ 恢复主题帖 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tid (int, optional): 待恢复的主题帖tid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await recover.request(self._http_core, fid, tid, 0, is_hide=False) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def recover_post(self, fname_or_fid: str | int, /, pid: int) -> BoolResponse: """ 恢复主题帖 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid pid (int, optional): 待恢复的回复pid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await recover.request(self._http_core, fid, 0, pid, is_hide=False) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def recover( self, fname_or_fid: str | int, /, tid: int = 0, pid: int = 0, *, is_hide: bool = False ) -> BoolResponse: """ 帖子恢复相关操作 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tid (int, optional): 待恢复的主题帖tid. Defaults to 0. pid (int, optional): 待恢复的回复pid. Defaults to 0. is_hide (bool, optional): True则取消屏蔽主题帖 False则恢复删帖. Defaults to False. Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await recover.request(self._http_core, fid, tid, pid, is_hide) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def good(self, fname_or_fid: str | int, /, tid: int, *, cname: str = "") -> BoolResponse: """ 加精主题帖 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid tid (int): 待加精的主题帖tid cname (str, optional): 待添加的精华分区名称 默认为''即不分区. Defaults to ''. Returns: BoolResponse: True成功 False失败 """ if isinstance(fname_or_fid, str): fname = fname_or_fid fid = await self.__get_fid(fname) else: fid = fname_or_fid fname = await self.__get_fname(fid) await self.__init_tbs() cid = await self.__get_cid(fname_or_fid, cname) return await good.request(self._http_core, fname, fid, tid, cid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def ungood(self, fname_or_fid: str | int, /, tid: int) -> BoolResponse: """ 撤精主题帖 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid tid (int): 待撤精的主题帖tid Returns: BoolResponse: True成功 False失败 """ if isinstance(fname_or_fid, str): fname = fname_or_fid fid = await self.__get_fid(fname) else: fid = fname_or_fid fname = await self.__get_fname(fid) await self.__init_tbs() return await ungood.request(self._http_core, fname, fid, tid) async def __get_cid(self, fname_or_fid: str | int, /, cname: str = "") -> int: if cname == "": return 0 fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) cates = await get_cid.request(self._http_core, fname) cid = 0 for item in cates: if cname == item["class_name"]: cid = item["class_id"] break return cid @handle_exception(IntResponse) async def get_cid(self, fname_or_fid: str | int, /, cname: str = "") -> IntResponse: """ 通过精华分区名获取精华分区id Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid cname (str, optional): 精华分区名. Defaults to ''. Returns: IntResponse: 精华分区id """ cid = await self.__get_cid(fname_or_fid, cname) return IntResponse(cid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def top(self, fname_or_fid: str | int, /, tid: int, *, is_vip: bool = False) -> BoolResponse: """ 置顶主题帖 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid tid (int): 待置顶的主题帖tid is_vip (bool, optional): 是否会员置顶. Defaults to False. Returns: BoolResponse: True成功 False失败 """ if isinstance(fname_or_fid, str): fname = fname_or_fid fid = await self.__get_fid(fname) else: fid = fname_or_fid fname = await self.__get_fname(fid) await self.__init_tbs() return await top.request(self._http_core, fname, fid, tid, is_vip, True) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def untop(self, fname_or_fid: str | int, /, tid: int, *, is_vip: bool = False) -> BoolResponse: """ 撤销置顶主题帖 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid tid (int): 待撤销置顶的主题帖tid is_vip (bool, optional): 是否会员置顶. Defaults to False. Returns: BoolResponse: True成功 False失败 """ if isinstance(fname_or_fid, str): fname = fname_or_fid fid = await self.__get_fid(fname) else: fid = fname_or_fid fname = await self.__get_fname(fid) await self.__init_tbs() return await top.request(self._http_core, fname, fid, tid, is_vip, False) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def move(self, fname_or_fid: str | int, /, tid: int, *, to_tab_id: int, from_tab_id: int = 0) -> BoolResponse: """ 将主题帖移动至另一分区 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tid (int): 待移动的主题帖tid to_tab_id (int): 目标分区id from_tab_id (int, optional): 来源分区id 默认为0即无分区. Defaults to 0. Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await move.request(self._http_core, fid, tid, to_tab_id, from_tab_id) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def recommend(self, fname_or_fid: str | int, /, tid: int) -> BoolResponse: """ 大吧主首页推荐 Args: fname_or_fid (str | int): 帖子所在贴吧的贴吧名或fid 优先fid tid (int): 待推荐的主题帖tid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) return await recommend.request(self._http_core, fid, tid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def handle_unblock_appeals( self, fname_or_fid: str | int, /, appeal_ids: list[int], *, refuse: bool = True ) -> BoolResponse: """ 拒绝或通过解封申诉 Args: fname_or_fid (str | int): 申诉所在贴吧的贴吧名或fid 优先fid appeal_ids (list[int]): 申诉请求的appeal_id列表. Length Max to 30. refuse (bool, optional): True则拒绝申诉 False则接受申诉. Defaults to True. Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await handle_unblock_appeals.request(self._http_core, fid, appeal_ids, refuse) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def agree(self, tid: int, pid: int = 0, is_comment: bool = False) -> BoolResponse: """ 点赞主题帖或回复 Args: tid (int): 待点赞的主题帖或回复所在的主题帖的tid pid (int, optional): 待点赞的回复pid. Defaults to 0. is_comment (bool, optional): pid是否指向楼中楼. Defaults to False. Returns: BoolResponse: True成功 False失败 Note: 本接口仍处于测试阶段\n 高频率调用会导致<发帖秒删>! 请谨慎使用! """ await self.__init_tbs() return await agree.request(self._http_core, tid, pid, is_comment, is_disagree=False, is_undo=False) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def unagree(self, tid: int, pid: int = 0, is_comment: bool = False) -> BoolResponse: """ 取消点赞主题帖或回复 Args: tid (int): 待取消点赞的主题帖或回复所在的主题帖的tid pid (int, optional): 待取消点赞的回复pid. Defaults to 0. is_comment (bool, optional): pid是否指向楼中楼. Defaults to False. Returns: BoolResponse: True成功 False失败 """ await self.__init_tbs() return await agree.request(self._http_core, tid, pid, is_comment, is_disagree=False, is_undo=True) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def disagree(self, tid: int, pid: int = 0, is_comment: bool = False) -> BoolResponse: """ 点踩主题帖或回复 Args: tid (int): 待点踩的主题帖或回复所在的主题帖的tid pid (int, optional): 待点踩的回复pid. Defaults to 0. is_comment (bool, optional): pid是否指向楼中楼. Defaults to False. Returns: BoolResponse: True成功 False失败 """ await self.__init_tbs() return await agree.request(self._http_core, tid, pid, is_comment, is_disagree=True, is_undo=False) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def undisagree(self, tid: int, pid: int = 0, is_comment: bool = False) -> BoolResponse: """ 取消点踩主题帖或回复 Args: tid (int): 待取消点踩的主题帖或回复所在的主题帖的tid pid (int, optional): 待取消点踩的回复pid. Defaults to 0. is_comment (bool, optional): pid是否指向楼中楼. Defaults to False. Returns: BoolResponse: True成功 False失败 """ await self.__init_tbs() return await agree.request(self._http_core, tid, pid, is_comment, is_disagree=True, is_undo=True) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def follow_user(self, id_: str | int) -> BoolResponse: """ 关注用户 Args: id_ (str | int): 用户id user_id / user_name / portrait 优先portrait Returns: BoolResponse: True成功 False失败 """ if not is_portrait(id_): user = await self.get_user_info(id_, ReqUInfo.PORTRAIT) portrait = user.portrait else: portrait = id_ await self.__init_tbs() return await follow_user.request(self._http_core, portrait) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def unfollow_user(self, id_: str | int) -> BoolResponse: """ 取关用户 Args: id_ (str | int): 用户id user_id / user_name / portrait 优先portrait Returns: BoolResponse: True成功 False失败 """ if not is_portrait(id_): user = await self.get_user_info(id_, ReqUInfo.PORTRAIT) portrait = user.portrait else: portrait = id_ await self.__init_tbs() return await unfollow_user.request(self._http_core, portrait) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def remove_fan(self, id_: str | int) -> BoolResponse: """ 移除粉丝 Args: id_ (str | int): 待移除粉丝的id user_id / user_name / portrait 优先user_id Returns: BoolResponse: True成功 False失败 """ if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ await self.__init_tbs() return await remove_fan.request(self._http_core, user_id) @handle_exception(BoolResponse, ok_log_level=logging.INFO) @_try_websocket async def set_blacklist(self, id_: str | int, *, btype: BlacklistType = BlacklistType.ALL) -> BoolResponse: """ 设置新版用户黑名单 Args: id_ (str | int): 待设置黑名单的用户id user_id / user_name / portrait 优先user_id btype (BlacklistType): 黑名单类型. 默认全屏蔽. Defaults to BlacklistType.ALL. Returns: BoolResponse: True成功 False失败 """ if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ if self._ws_core.status == WsStatus.OPEN: return await set_blacklist.request_ws(self._ws_core, user_id, btype) return await set_blacklist.request_http(self._http_core, user_id, btype) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def add_blacklist_old(self, id_: str | int) -> BoolResponse: """ 添加旧版用户黑名单 Args: id_ (str | int): 待添加黑名单的用户id user_id / user_name / portrait 优先user_id Returns: BoolResponse: True成功 False失败 """ if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ return await add_blacklist_old.request(self._http_core, user_id) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def del_blacklist_old(self, id_: str | int) -> BoolResponse: """ 移除旧版用户黑名单 Args: id_ (str | int): 待移除黑名单的用户id user_id / user_name / portrait 优先user_id Returns: BoolResponse: True成功 False失败 """ if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ return await del_blacklist_old.request(self._http_core, user_id) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def follow_forum(self, fname_or_fid: str | int) -> BoolResponse: """ 关注贴吧 Args: fname_or_fid (str | int): 要关注贴吧的贴吧名或fid 优先fid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await follow_forum.request(self._http_core, fid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def unfollow_forum(self, fname_or_fid: str | int) -> BoolResponse: """ 取关贴吧 Args: fname_or_fid (str | int): 要取关贴吧的贴吧名或fid 优先fid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) await self.__init_tbs() return await unfollow_forum.request(self._http_core, fid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def dislike_forum(self, fname_or_fid: str | int) -> BoolResponse: """ 屏蔽贴吧 使其不再出现在首页推荐列表中 Args: fname_or_fid (str | int): 待屏蔽贴吧的贴吧名或fid 优先fid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) return await dislike_forum.request(self._http_core, fid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def undislike_forum(self, fname_or_fid: str | int) -> BoolResponse: """ 解除贴吧的首页推荐屏蔽 Args: fname_or_fid (str | int): 待屏蔽贴吧的贴吧名或fid 优先fid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) return await undislike_forum.request(self._http_core, fid) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def set_thread_private(self, fname_or_fid: str | int, /, tid: int, pid: int) -> BoolResponse: """ 隐藏主题帖 Args: fname_or_fid (str | int): 主题帖所在贴吧的贴吧名或fid 优先fid tid (int): 主题帖tid tid (int): 主题帖pid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) return await set_thread_privacy.request(self._http_core, fid, tid, pid, is_hide=True) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def set_thread_public(self, fname_or_fid: str | int, /, tid: int, pid: int) -> BoolResponse: """ 公开主题帖 Args: fname_or_fid (str | int): 主题帖所在贴吧的贴吧名或fid 优先fid tid (int): 主题帖tid tid (int): 主题帖pid Returns: BoolResponse: True成功 False失败 """ fid = fname_or_fid if isinstance(fname_or_fid, int) else await self.__get_fid(fname_or_fid) return await set_thread_privacy.request(self._http_core, fid, tid, pid, is_hide=False) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def set_profile(self, nick_name: str, sign: str = "", gender: Gender = Gender.UNKNOWN) -> BoolResponse: """ 设置主页信息 Args: nick_name (str): 昵称 sign (str): 个性签名. Defaults to ''. gender (Gender): 性别. Defaults to Gender.UNKNOWN. Returns: BoolResponse: True成功 False失败 """ return await set_profile.request(self._http_core, nick_name, sign, gender) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def set_nickname_old(self, nick_name: str) -> BoolResponse: """ 设置旧版昵称 Args: nick_name (str): 昵称 Returns: BoolResponse: True成功 False失败 """ return await set_nickname_old.request(self._http_core, nick_name) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def sign_forum(self, fname_or_fid: str | int) -> BoolResponse: """ 单个贴吧签到 Args: fname_or_fid (str | int): 要签到贴吧的贴吧名或fid 优先贴吧名 Returns: BoolResponse: True成功 False失败 """ fname = fname_or_fid if isinstance(fname_or_fid, str) else await self.__get_fname(fname_or_fid) await self.__init_tbs() return await sign_forum.request(self._http_core, fname) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def sign_forums(self) -> BoolResponse: """ 一键签到 Returns: BoolResponse: True成功 False失败 """ return await sign_forums.request(self._http_core) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def sign_growth(self) -> BoolResponse: """ 用户成长等级任务: 签到 Returns: BoolResponse: True成功 False失败 """ await self.__init_tbs() return await sign_growth.request_web(self._http_core, act_type="page_sign") @handle_exception(BoolResponse, ok_log_level=logging.INFO) @_try_websocket @deprecated("此接口风险极高,可能导致账号被永久封禁屏蔽,故弃用并将于近期移除") async def add_post(self, fname_or_fid: str | int, /, tid: int, content: str) -> BoolResponse: """ 回复主题帖 Args: fname_or_fid (str | int): 要回复的主题帖所在贴吧的贴吧名或fid tid (int): 要回复的主题帖的tid content (str): 回复内容 Returns: BoolResponse: 回帖是否成功 Note: 本接口仍处于测试阶段\n 高频率调用会导致<永久封禁屏蔽>! 请谨慎使用! """ if isinstance(fname_or_fid, str): fname = fname_or_fid fid = await self.__get_fid(fname) else: fid = fname_or_fid fname = await self.__get_fname(fid) await self.__init_z_id() await self.__init_tbs() await self.__init_client_id() await self.__init_sample_id() await self.__get_selfinfo_initNickname() show_name = self._user.show_name if self._ws_core.status == WsStatus.OPEN: return await add_post.request_ws(self._ws_core, fname, fid, tid, show_name, content) return await add_post.request_http(self._http_core, fname, fid, tid, show_name, content) @handle_exception(BoolResponse, ok_log_level=logging.INFO) @_force_websocket async def send_msg(self, id_: str | int, content: str) -> BoolResponse: """ 发送私信 Args: id_ (str | int): 用户id user_id / user_name / portrait 优先user_id content (str): 发送内容 Returns: BoolResponse: True成功 False失败 """ if not isinstance(id_, int): user = await self.get_user_info(id_, ReqUInfo.USER_ID) user_id = user.user_id else: user_id = id_ msg_id = await send_msg.request(self._ws_core, user_id, content) mid_manager = self._ws_core.mid_manager mid_manager.update_msg_id(mid_manager.priv_gid, msg_id) return BoolResponse() @handle_exception(BoolResponse, ok_log_level=logging.INFO) @_force_websocket async def set_msg_readed(self, message: get_group_msg.WsMessage) -> BoolResponse: """ 将一条私信设为已读 Args: message (WsMessage): websocket私信消息 Returns: BoolResponse: True成功 False失败 """ return await set_msg_readed.request(self._ws_core, message) @handle_exception(get_group_msg.WsMsgGroups) @_force_websocket async def get_group_msg(self, group_ids: list[int], *, get_type: int = 1) -> get_group_msg.WsMsgGroups: """ 获取分组信息 Args: group_ids (list[int]): 待获取分组的group_id get_type (int, optional): 获取类型. Defaults to 1. Returns: WsMsgGroups: websocket消息组列表 """ return await get_group_msg.request(self._ws_core, group_ids, get_type) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def send_chatroom_msg( self, chatroom_id: int, forum_id: int, text: str, atuser_ids: list[int] = None, robotc: int = -1 ) -> BoolResponse: """ 向吧群发送信息,仅限简单文本。如需要@他人需要指定atuser_ids,如需与bot交互需要指定atuser_ids和robot Args: chatroom_id (int): 聊天室id forum_id (int): 吧id text (str): 待发送内容 atuser_ids (list[int], optional): 需要@的人的user_id列表 robotc (int, optional): 机器人指令id。机器人靠此分辨指令,而非text内容。 Returns: BoolResponse: True成功 False失败 """ async def _ensure_user_info(): required_attrs = ["user_id", "portrait"] max_retries = 3 for _ in range(max_retries + 1): if all(getattr(self._user, attr) for attr in required_attrs): return await self.get_self_info() raise ValueError("登录失败") await _ensure_user_info() if self._blcp_core.status != 1: await self._init_blcp() level_info = await self.__get_forum_level(forum_id) level = level_info.user_level isvip = self._user.is_vip glevel = self._user.glevel # 处理艾特@ atdata = [] if atuser_ids: for count, user_id in enumerate(atuser_ids): userforAt = await self._get_uinfo_profile(user_id) if not all([userforAt.portrait, userforAt.nick_name]): userforAt = await self._get_uinfo_profile(user_id) atdata.append({ "at_type": "user", "at_baidu_uk": self._blcp_core.getBDUKfromUserId(str(user_id)), "at_name": userforAt.nick_name, "at_portrait": userforAt.portrait, "position": str(count), }) return await send_chatroom_msg.request( self._blcp_core, chatroom_id, self._user.uk, self._user.user_id, self._user.trigger_id, self._user.nick_name, self._user.portrait, text, forum_id, level, isvip, glevel, atdata, robot=robotc, ) @handle_exception(BoolResponse, ok_log_level=logging.INFO) async def _init_blcp(self): if self._blcp_core.status == -1: await self._blcp_core.connect() if self._blcp_core.status == 0: await self._blcp_core.login() if self._blcp_core.status == 1: return BoolResponse() else: raise async def __get_forum_level(self, forum_id: int) -> get_forum_level.LevelInfo: if not self._user.user_id: await self.get_self_info() return await get_forum_level.request_http(self._http_core, forum_id) @handle_exception(get_roomlist_by_fid.RoomList) async def get_roomlist_by_fid(self, forum_id: int) -> get_roomlist_by_fid.RoomList: """ 获取某吧所有群聊 Args: forum_id (int),: 吧id. Returns: RoomList: 群信息 """ if not self._user.user_id: # 检查是否登陆 await self.get_self_info() return await get_roomlist_by_fid.request(self._http_core, forum_id) def get_chat_message_queue(self) -> asyncio.Queue: """ 获取消息队列(全局共用),该队列仅包含通知(Notify)类型消息 Returns: Queue: 消息队列 """ return self._blcp_core.message_queue @handle_exception(BoolResponse) async def join_chatroom(self, room_id: int) -> BoolResponse: """ 获取某吧等级 Args: room_id (int): 房间id Returns: BoolResponse: True成功 False失败 """ if not self._user.user_id: # 检查是否登陆 await self.get_self_info() if self._blcp_core.status != 1: # 检查BLCP是否登陆 await self._init_blcp() try: await self._blcp_core.joinChatRoom(room_id) except Exception as err: raise Exception("加入房间失败") from err return BoolResponse() ================================================ FILE: src/aiotieba/config.py ================================================ from __future__ import annotations import dataclasses as dcs import aiohttp import yarl @dcs.dataclass class ProxyConfig: """ 代理配置 Args: url (str | yarl.URL, optional): 代理url. Defaults to None. auth (aiohttp.BasicAuth, optional): 代理认证. Defaults to None. """ url: yarl.URL | None = None auth: aiohttp.BasicAuth | None = None def __init__(self, url: str | yarl.URL | None = None, auth: aiohttp.BasicAuth | None = None) -> None: if isinstance(url, str): url = yarl.URL(url) self.url = url self.auth = auth @staticmethod def from_env() -> ProxyConfig: proxy_info = aiohttp.helpers.proxies_from_env().get("http", None) if proxy_info is None: url, auth = None, None else: url, auth = proxy_info.proxy, proxy_info.proxy_auth return ProxyConfig(url, auth) @dcs.dataclass class TimeoutConfig: """ 各种超时配置 Args: http_acquire_conn (float, optional): 从连接池获取一个可用连接的超时时间. Defaults to 4.0. http_read (float, optional): 从发送http请求到读取全部响应的超时时间. Defaults to 12.0. http_connect (float, optional): 新建一个socket连接的超时时间. Defaults to 3.0. http_keepalive (float, optional): http长连接的保持时间. Defaults to 30.0. ws_send (float, optional): websocket发送数据的超时时间. Defaults to 3.0. ws_read (float, optional): 从发送websocket数据到结束等待响应的超时时间. Defaults to 8.0. ws_close (float, optional): 等待websocket终止连接的时间. Defaults to 10.0. ws_keepalive (float, optional): websocket在长达ws_keepalive的时间内未发生IO则发送close信号关闭连接. Defaults to 300.0. ws_heartbeat (float, optional): websocket心跳间隔. 为None则不发送心跳. Defaults to None. dns_ttl (int, optional): dns的本地缓存超时时间. Defaults to 600. Note: 所有时间均以秒为单位 """ http_acquire_conn: float = 4.0 http_read: float = 12.0 http_connect: float = 3.0 http_keepalive: float = 30.0 ws_send: float = 3.0 ws_read: float = 8.0 ws_close: float = 10.0 ws_keepalive: float = 300.0 ws_heartbeat: float | None = None dns_ttl: int = 600 @property def http_timeout(self) -> aiohttp.ClientTimeout: return aiohttp.ClientTimeout( connect=self.http_acquire_conn, sock_read=self.http_read, sock_connect=self.http_connect ) @property def ws_timeout(self) -> aiohttp.ClientWSTimeout: return aiohttp.ClientWSTimeout(self.ws_read, self.ws_close) ================================================ FILE: src/aiotieba/const.py ================================================ LATEST_VERSION = "22.5.1.0" # 通常用于写API (`agree`...) STABLE_VERSION = "12.64.1.1" # 通常用于只读API (`get_threads`...) CHAT_VERSION = "12.68.1.0" CHAT_APPID = 10773430 CHAT_SDK_VERSION = 11250036 APP_BASE_HOST = "tiebac.baidu.com" WEB_BASE_HOST = "tieba.baidu.com" ================================================ FILE: src/aiotieba/core/__init__.py ================================================ from .account import Account from .blcp import BLCPCore, BLCPData from .http import HttpCore from .net import NetCore from .websocket import TypeWebsocketCallback, WsCore, WsResponse ================================================ FILE: src/aiotieba/core/account.py ================================================ from __future__ import annotations import random from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from ..helper.crypto import c3_aid, cuid_galaxy2 class Account: """ 贴吧的用户参数容器 Args: BDUSS (str, optional): BDUSS. Defaults to ''. STOKEN (str, optional): 网页STOKEN. Defaults to ''. Attributes: BDUSS (str): BDUSS STOKEN (str): 网页STOKEN tbs (str): 长度为26的小写16进制字符串 例:91be894d01799c4991be894d01 android_id (str): 长度为16的小写16进制字符串 包含8字节信息 例:91be894d01799c49 uuid (str): 包含16字节信息 例:e4200716-58a8-4170-af15-ea7edeb8e513 client_id (str): 例:wappc_1653660000000_123 sample_id (str): 例:104505_3-105324_2-...-107269_1 cuid (str): 例:baidutiebaappe4200716-58a8-4170-af15-ea7edeb8e513 cuid_galaxy2 (str): 例:A3ED2D7B9CFC28E8934A3FBD3A9579C7|VZ5FKB5XS c3_aid (str): 例:A00-ZNU3O3EP74D727LMQY745CZSGZQJQZGP-3JXCKC7X z_id (str): z_id aes_ecb_sec_key (bytes): 供贴吧AES-ECB加密使用的随机密码 长度为31字节 aes_ecb_chiper (Any): AES-ECB加密器 aes_cbc_sec_key (bytes): 供贴吧AES-CBC加密使用的随机密码 长度为16字节 aes_cbc_chiper (Any): AES-CBC加密器 """ __slots__ = [ "_BDUSS", "_STOKEN", "_tbs", "_android_id", "_uuid", "_client_id", "_sample_id", "_cuid", "_cuid_galaxy2", "_c3_aid", "_z_id", "_aes_ecb_sec_key", "_aes_ecb_chiper", "_aes_cbc_sec_key", "_aes_cbc_chiper", ] __serialize__ = [ "_BDUSS", "_STOKEN", "_tbs", "_android_id", "_uuid", "_client_id", "_sample_id", "_cuid", "_cuid_galaxy2", "_c3_aid", "_z_id", "_aes_ecb_sec_key", "_aes_cbc_sec_key", ] def __init__(self, BDUSS: str = "", STOKEN: str = "") -> None: self.BDUSS = BDUSS self.STOKEN = STOKEN self._tbs: str = None self._android_id: str = None self._uuid: str = None self._client_id: str = None self._sample_id: str = None self._cuid: str = None self._cuid_galaxy2: str = None self._c3_aid: str = None self._z_id: str = None self._aes_ecb_sec_key: bytes = None self._aes_ecb_chiper = None self._aes_cbc_sec_key: bytes = None self._aes_cbc_chiper = None def __repr__(self) -> str: return str(self.to_dict()) def __hash__(self) -> int: return hash(self.BDUSS) def __eq__(self, obj: Account) -> bool: return self.BDUSS == obj.BDUSS def to_dict(self) -> dict[str, str | bytes]: """ 将Account转换为字典 Returns: dict[str, str | bytes]: 包含用户参数的字典 """ dic = {} for key in self.__serialize__: value = getattr(self, key) if not value: continue key = key[1:] dic[key] = value return dic @staticmethod def from_dict(dic: dict[str, str | bytes]) -> Account: """ 将字典转换为Account Args: dic (Dict[str, str | bytes]): 包含用户参数的字典 Returns: Account: 用户参数容器 """ account = Account() for key, value in dic.items(): if not value: continue key = "_" + key setattr(account, key, value) return account @property def BDUSS(self) -> str: """ 当前账号的BDUSS """ return self._BDUSS @BDUSS.setter def BDUSS(self, new_BDUSS: str) -> None: if new_BDUSS and len(new_BDUSS) != 192: raise ValueError(f"BDUSS的长度应为192个字符 而输入的{new_BDUSS}有{len(new_BDUSS)}个字符") self._BDUSS = new_BDUSS @property def STOKEN(self) -> str: """ 当前账号的STOKEN """ return self._STOKEN @STOKEN.setter def STOKEN(self, new_STOKEN: str) -> None: if new_STOKEN and len(new_STOKEN) != 64: raise ValueError(f"STOKEN的长度应为64个字符 而输入的{new_STOKEN}有{len(new_STOKEN)}个字符") self._STOKEN = new_STOKEN @property def android_id(self) -> str: """ 返回一个随机的android_id Returns: str: 长度为16的16进制字符串 包含8字节信息 字母为小写 Examples: 91be894d01799c49 Note: 在初始化后该属性便不会再发生变化 """ if self._android_id is None: self._android_id = random.randbytes(8).hex() return self._android_id @android_id.setter def android_id(self, new_android_id: str) -> None: self._android_id = new_android_id @property def uuid(self) -> str: """ 使用uuid.uuid4生成并返回一个随机的uuid Returns: str: 包含16字节信息 Examples: e4200716-58a8-4170-af15-ea7edeb8e513 Note: 在初始化后该属性便不会再发生变化 """ if self._uuid is None: import uuid self._uuid = str(uuid.uuid4()) return self._uuid @uuid.setter def uuid(self, new_uuid: str) -> None: self._uuid = new_uuid @property def tbs(self) -> str: """ 返回一个可作为请求参数的反csrf校验码tbs Returns: str: 长度为26的16进制字符串 字母为小写 Examples: 17634e03cbe25e6e1674526199 Note: 在初始化后该属性便不会再发生变化 """ return self._tbs @tbs.setter def tbs(self, new_tbs: str) -> None: self._tbs = new_tbs @property def client_id(self) -> str: """ 返回一个可作为请求参数的client_id Returns: str Examples: wappc_1653660000000_123 Note: 在初始化后该属性便不会再发生变化 """ return self._client_id @client_id.setter def client_id(self, new_client_id: str) -> None: self._client_id = new_client_id @property def sample_id(self) -> str: """ 返回一个可作为请求参数的sample_id Returns: str Examples: 104505_3-105324_2-...-107269_1 Note: 在初始化后该属性便不会再发生变化 """ return self._sample_id @sample_id.setter def sample_id(self, new_sample_id: str) -> None: self._sample_id = new_sample_id @property def cuid(self) -> str: """ 返回一个可作为请求参数的cuid Returns: str Examples: baidutiebaappe4200716-58a8-4170-af15-ea7edeb8e513 Note: 在初始化后该属性便不会再发生变化\n 此实现仅用于9.x等旧版本 11.x后请使用cuid_galaxy2填充对应字段 """ if self._cuid is None: self._cuid = f"baidutiebaapp{self.uuid}" return self._cuid @cuid.setter def cuid(self, new_cuid: str) -> None: self._cuid = new_cuid @property def cuid_galaxy2(self) -> str: """ 返回一个可作为请求参数的cuid_galaxy2 Returns: str Examples: A3ED2D7B9CFC28E8934A3FBD3A9579C7|VZ5FKB5XS Note: 在初始化后该属性便不会再发生变化\n 此实现与12.x版本及以前的官方实现一致 """ if self._cuid_galaxy2 is None: self._cuid_galaxy2 = cuid_galaxy2(self.android_id) return self._cuid_galaxy2 @cuid_galaxy2.setter def cuid_galaxy2(self, new_cuid_galaxy2: str) -> None: self._cuid_galaxy2 = new_cuid_galaxy2 @property def c3_aid(self) -> str: """ 返回一个可作为请求参数的c3_aid Returns: str Examples: A00-ZNU3O3EP74D727LMQY745CZSGZQJQZGP-3JXCKC7X Note: 在初始化后该属性便不会再发生变化\n 此实现与12.x版本及以前的官方实现一致 """ if self._c3_aid is None: self._c3_aid = c3_aid(self.android_id, self.uuid) return self._c3_aid @c3_aid.setter def c3_aid(self, new_c3_aid: str) -> None: self._c3_aid = new_c3_aid @property def z_id(self) -> str: """ 返回一个可作为请求参数的z_id Returns: str Note: 在初始化后该属性便不会再发生变化\n 此实现与12.x版本及以前的官方实现一致 """ return self._z_id @z_id.setter def z_id(self, new_z_id: str) -> None: self._z_id = new_z_id @property def aes_ecb_sec_key(self) -> bytes: """ 返回一个供贴吧AES-ECB加密使用的随机密码 Returns: bytes: 长度为31字节的随机密码 Note: 在初始化后该属性便不会再发生变化 """ if self._aes_ecb_sec_key is None: self._aes_ecb_sec_key = random.randbytes(31) return self._aes_ecb_sec_key @aes_ecb_sec_key.setter def aes_ecb_sec_key(self, new_aes_ecb_sec_key: bytes) -> None: self._aes_ecb_sec_key = new_aes_ecb_sec_key @property def aes_ecb_chiper(self) -> Cipher[modes.ECB]: """ 获取供贴吧websocket使用的AES-ECB加密器 Returns: Cipher[ECB]: AES-ECB加密器 """ if self._aes_ecb_chiper is None: salt = b"\xa4\x0b\xc8\x34\xd6\x95\xf3\x13" kdf = PBKDF2HMAC(hashes.SHA1(), 32, salt, 5) ws_secret_key = kdf.derive(self.aes_ecb_sec_key) self._aes_ecb_chiper = Cipher(algorithms.AES(ws_secret_key), modes.ECB()) return self._aes_ecb_chiper @property def aes_cbc_sec_key(self) -> bytes: """ 返回一个供贴吧AES-CBC加密使用的随机密码 Returns: bytes: 长度为16字节的随机密码 Note: 在初始化后该属性便不会再发生变化 """ if self._aes_cbc_sec_key is None: self._aes_cbc_sec_key = random.randbytes(16) return self._aes_cbc_sec_key @aes_ecb_sec_key.setter def aes_ecb_sec_key(self, new_aes_ecb_sec_key: bytes) -> None: self._aes_ecb_sec_key = new_aes_ecb_sec_key @property def aes_cbc_chiper(self) -> Cipher[modes.CBC]: """ 获取供贴吧客户端使用的AES-CBC加密器 Returns: Cipher[CBC]: AES-CBC加密器 """ if self._aes_cbc_chiper is None: iv = b"\x00" * 16 self._aes_cbc_chiper = Cipher(algorithms.AES(self.aes_cbc_sec_key), modes.CBC(iv)) return self._aes_cbc_chiper ================================================ FILE: src/aiotieba/core/blcp.py ================================================ # ruff: noqa: B904, E722 from __future__ import annotations import asyncio import base64 import dataclasses as dcs import gzip import json import random import socket import ssl import time import urllib.parse import weakref from asyncio import IncompleteReadError, Queue, StreamReader, StreamWriter from hashlib import md5 from sys import maxsize as LongMax from typing import TYPE_CHECKING import aiohttp import yarl from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from ..api._protobuf import Lcm_pb2, Rpc_pb2 from ..config import ProxyConfig, TimeoutConfig from ..const import CHAT_APPID, CHAT_SDK_VERSION, CHAT_VERSION from ..helper import timeout from ..helper.crypto import enuid if TYPE_CHECKING: from ..api._classdef import UserInfo from .account import Account from .net import NetCore @dcs.dataclass class BLCPCore: """ 网络请求相关容器 Args: proxy (ProxyConfig, optional): 代理配置. Defaults to None. timeout (TimeoutConfig, optional): 超时配置. Defaults to None. """ account: Account user: UserInfo reader: StreamReader writer: StreamWriter loop: asyncio.AbstractEventLoop proxy: ProxyConfig timeout: TimeoutConfig blcp_dispatcher: asyncio.Task blcp_responses: ClientBLCPResponses waiter: BLCPWaiter net_core: NetCore trigger_id: int message_queue: Queue # 该消息队列只有群聊消息,没有各种握手 heartbeater: asyncio.Task def __init__( self, proxy: ProxyConfig | None = None, timeout: TimeoutConfig | None = None, loop: asyncio.AbstractEventLoop = None, net_core: NetCore = None, account: Account = None, user: UserInfo = None, max_queue_length: int = 100, ) -> None: if not isinstance(proxy, ProxyConfig): proxy = ProxyConfig() self.proxy = proxy if not isinstance(timeout, TimeoutConfig): timeout = TimeoutConfig() self.timeout = timeout if not loop: self.loop = asyncio.get_running_loop() else: self.loop = loop self.blcp_dispatcher: asyncio.Task = None self.heartbeater = None self.net_core = net_core self.account = account self.trigger_id = -1 self.user = user self.status = -1 self.message_queue = Queue(maxsize=max_queue_length) def set_account(self, new_account: Account) -> None: self.account = new_account async def connect(self) -> None: self.waiter = BLCPWaiter(5) context = ssl.create_default_context() context.check_hostname = False # print("connecting") try: reader, writer = await asyncio.open_connection( "common.lcs.baidu.com", 443, ssl=context, family=socket.AF_INET ) # 实际上可以支持ipv6 except BaseException: raise else: self.reader = reader self.writer = writer if self.blcp_dispatcher is not None and not self.blcp_dispatcher.done(): self.blcp_dispatcher.cancel() self.blcp_responses = ClientBLCPResponses(reader=reader, writer=writer) self.blcp_dispatcher = self.loop.create_task(self.__blcp_dispatch(), name="blcp_dispatcher") self.status = 0 async def login(self) -> None: cuid_galaxy2 = self.account.cuid_galaxy2 blcp_token = await self.generate_lcm_token(cuid_galaxy2) # 握手 loginBLCPRequest = BLCPData(serviceId=1, methodId=1) loginBLCPRequest.RpcBody = self.buildRpcBody( serviceId=1, methodId=1, correlationId=loginBLCPRequest.correlationId, need_common=1 ) LcmBody = Lcm_pb2.RpcData() LcmBody.lcm_request.log_id = loginBLCPRequest.correlationId LcmBody.lcm_request.token = blcp_token LcmBody.lcm_request.common.cuid = cuid_galaxy2 LcmBody.lcm_request.common.device = "android" LcmBody.lcm_request.common.app_id = str(CHAT_APPID) LcmBody.lcm_request.common.app_version = CHAT_VERSION LcmBody.lcm_request.common.sdk_version = "3460016" LcmBody.lcm_request.common.network = "wifi" LcmBody.lcm_request.timestamp = int(time.time() * 1000) LcmBody.lcm_request.start_type = -1 LcmBody.lcm_request.conn_type = 1 loginBLCPRequest.LcmBody = LcmBody.SerializeToString() response = self.waiter.new(loginBLCPRequest.correlationId) self.writer.write(loginBLCPRequest.toBytes()) reps = await response.read() try: rpc, lcm = ClientBLCPResponses.parseBLCPResponse(reps.toBytes()) except: raise Exception("BLCP Handshake error") # TODO: 特殊的握手错误 if rpc.response.error_text != "success" or lcm.lcm_response.error_msg != "success": raise Exception("BLCP Handshake error") # TODO: 特殊的握手错误 # 第二部分登陆 # 构造部分位于 com.baidu.searchbox.cloudcontrolblcp.CloudControlBlCPManager # 参数来源在com.baidu.common.param.CommonUrlParamManager中找到 sid = self.account.sample_id ua = "900_1600_android_12.68.1.0_240" # 分辨率 uid = enuid(cuid_galaxy2) # Enuid,由cuid_galaxy2经过libbase64encoder_v2_0.so得到 login_from = "1008550l" # baidu_imsdk_common_data.xml cfrom = login_from c3_aid = self.account.c3_aid reqdict = { "params": { "appname": "tieba", "sid": sid, "ua": ua, "uid": uid, "cfrom": cfrom, "from": login_from, "network": "1_-1", "p_sv": "32", "mps": "", "mpv": "1", "c3_aid": c3_aid, "type_id": "0", }, "filter": {"aps": {"cpu_abi": "armeabi-v7a"}, "command": {"step": "0"}}, } loginBLCPRequest = BLCPData(serviceId=4, methodId=1) loginBLCPRequest.RpcBody = self.buildRpcBody( serviceId=4, methodId=1, correlationId=loginBLCPRequest.correlationId ) loginBLCPRequest.LcmBody = json.dumps(reqdict).encode() response = self.waiter.new(loginBLCPRequest.correlationId) self.writer.write(loginBLCPRequest.toBytes()) reps = await response.read() try: rpc, lcm = ClientBLCPResponses.parseBLCPResponse(reps.toBytes()) except: raise Exception("BLCP Handshake error.") # TODO: 特殊的握手错误 if rpc.response.error_text != "success" or lcm.get("errno") != "0": raise Exception("BLCP Handshake error.") # TODO: 特殊的握手错误 loginBLCPRequest = BLCPData(serviceId=2, methodId=50) loginBLCPRequest.correlationId = 2000003149381050 loginBLCPRequest.RpcBody = self.buildRpcBody( serviceId=2, methodId=50, correlationId=loginBLCPRequest.correlationId ) client_identifier = {"zid": "", "version_code": ""} # 待完善,目前可以不携带zid cookie = "" # 待完善,目前可以不携带cookie token = self.account.BDUSS reqdict = { "method": 50, "appid": CHAT_APPID, "device_id": "android_" + cuid_galaxy2, "account_type": 1, "token": token, "version": 4, "sdk_version": CHAT_SDK_VERSION, "app_version": CHAT_VERSION, "app_open_type": 0, "client_identifier": json.dumps(client_identifier), "tail": 0, "timeout": 10, "cookie": cookie, "device_info": { "app_version": CHAT_VERSION, "os_version": "32", "platform": "android", "appid": str(CHAT_APPID), "from": login_from, "cfrom": cfrom, }, "rpc": json.dumps({"rpc_retry_time": 0}), "user_type": 0, "client_logid": int(time.time() * 1000 * 1000), } loginBLCPRequest.LcmBody = json.dumps(reqdict).encode() response = self.waiter.new(loginBLCPRequest.correlationId) self.writer.write(loginBLCPRequest.toBytes()) reps = await response.read() try: rpc, lcm = ClientBLCPResponses.parseBLCPResponse(reps.toBytes()) except: raise Exception("BLCP Handshake error.") # TODO: 特殊的握手错误 if rpc.response.error_text != "success" or lcm.get("err_code") != 0: raise Exception("BLCP Handshake error.") # TODO: 特殊的握手错误 rep = json.loads(reps.LcmBody) self.user.trigger_id = rep["trigger_id"][0] self.user.uk = rep["uk"] self.user.bduk = rep["bd_uid"] self.login_id = rep["login_id"] self.status = 1 async def generate_lcm_token(self, cuid_galaxy2): headers = { "Content-Type": "application/json", "Accept-Encoding": "gzip", "User-Agent": "okhttp/3.11.0", "Host": "pim.baidu.com", } request_id = str(int(time.time() * 1000)) ts = int(time.time() * 1000) data = { "app_id": str(CHAT_APPID), "app_version": CHAT_VERSION, "cuid": cuid_galaxy2, "device_type": "android", "manufacture": "", "model_type": "", "request_id": request_id, "sdk_version": "3460016", "sign": md5((str(CHAT_APPID) + cuid_galaxy2 + "android" + str(ts)).encode()).hexdigest(), # appid "ts": ts, "user_key": "", } request = aiohttp.ClientRequest( "POST", yarl.URL.build(scheme="https", host="pim.baidu.com", path="/rest/5.0/generate_lcm_token", port=443), headers=headers, # proxy=self.net_core.proxy.url, # proxy_auth=self.net_core.proxy.auth, # ssl=False, data=json.dumps(data), ) # TODO: 支持代理 try: response = await self.net_core.req2res(request, False, 2 * 1024) rjson = await response.json() return rjson["token"] except Exception: return "" async def groupchat(self): # 模拟正常请求,暂不清楚作用 headers = { "Content-Type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip", "User-Agent": "okhttp/3.11.0", "Host": "pim.baidu.com", "Cookie": "BDUSS=" + self.account.BDUSS, } ts = int(time.time()) data = { "method": "get_joined_groups", "appid": CHAT_APPID, "timestamp": ts, "sign": md5((str(ts) + self.account.BDUSS + str(CHAT_APPID)).encode()).hexdigest(), } request = aiohttp.ClientRequest( "POST", yarl.URL.build(scheme="https", host="pim.baidu.com", path="/rest/2.0/im/groupchat", port=443), headers=headers, # proxy=self.net_core.proxy.url, # proxy_auth=self.net_core.proxy.auth, # ssl=False, data=urllib.parse.urlencode(data), ) # TODO: 支持代理 try: response = await self.net_core.req2res(request, False, 2 * 1024) await response.json() except Exception: return "" async def groupchatv1(self): # 模拟正常请求,暂不清楚作用 headers = { "Content-Type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip", "User-Agent": "okhttp/3.11.0", "Host": "pim.baidu.com", "Cookie": "BDUSS=" + self.account.BDUSS, } ts = int(time.time()) data = { "method": "get_joined_groups", "group_type": 3, "appid": CHAT_APPID, "source": 0, "cuid": self.account.cuid_galaxy2, "app_version": CHAT_VERSION, "sdk_version": str(CHAT_SDK_VERSION), "timestamp": ts, "device_type": 2, "sign": md5((str(ts) + self.account.BDUSS + str(CHAT_APPID)).encode()).hexdigest(), } request = aiohttp.ClientRequest( "POST", yarl.URL.build(scheme="https", host="pim.baidu.com", path="/rest/2.0/im/groupchatv1", port=443), headers=headers, # proxy=self.net_core.proxy.url, # proxy_auth=self.net_core.proxy.auth, # ssl=False, data=urllib.parse.urlencode(data), ) # TODO: 支持代理 try: response = await self.net_core.req2res(request, False, 2 * 1024) await response.json() except Exception: return "" @staticmethod def getBDUKfromUserId(user_id: str): padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_req_body = padder.update(user_id.encode()) + padder.finalize() aes_encryptor = Cipher(algorithms.AES(b"AFD311832EDEEAEF"), modes.CBC(b"2011121211143000")).encryptor() req_body_aes = aes_encryptor.update(padded_req_body) + aes_encryptor.finalize() return base64.urlsafe_b64encode(req_body_aes).strip(b"=").decode() @staticmethod def getmsgkey(bduk: str): return bduk + str(int(time.time() * 1000) * 1000) + f"{random.randint(-LongMax, LongMax)}" @staticmethod def buildRpcBody(serviceId, methodId, correlationId, compress_type=0, need_common=1): event_timestamp = Rpc_pb2.EventTimestamp() event_timestamp.event = "CLCPReqBegin" event_timestamp.timestamp_ms = int(time.time() * 1000) RpcRequestMeta = Rpc_pb2.RpcRequestMeta() RpcRequestMeta.log_id = correlationId RpcRequestMeta.service_id = serviceId RpcRequestMeta.method_id = methodId RpcRequestMeta.need_common = need_common RpcRequestMeta.event_list.append(event_timestamp) RpcMeta = Rpc_pb2.RpcMeta() RpcMeta.request.CopyFrom(RpcRequestMeta) RpcMeta.correlation_id = correlationId RpcMeta.compress_type = compress_type # i RpcMeta.accept_compress_type = 1 return RpcMeta.SerializeToString() async def __blcp_dispatch(self) -> None: async for msg in self.blcp_responses: if not msg: continue self.waiter.set_done(msg.correlationId, msg) # TODO: 加上callbacks以处理服务端主动发送的消息,如群聊消息等。获取群聊消息推送需要先发包绑定群聊。 if msg.isNotify: rpc, lcm = ClientBLCPResponses.parseBLCPResponse( msg.toBytes() ) # TODO:优化。这里在解码后又编码再解码了一次 if self.message_queue.full(): await self.message_queue.get() # 队满自动丢弃 await self.message_queue.put(lcm) self.status = -1 raise Exception("IM服务端断开连接") async def joinChatRoom(self, chatroom_id: int) -> bool: if self.status != 1: raise Exception("IM未登陆") request = BLCPData(serviceId=3, methodId=201) # 加入Chatroom的请求 request.RpcBody = self.buildRpcBody(serviceId=3, methodId=201, correlationId=request.correlationId) reqdict = { "method": 201, "mcast_id": chatroom_id, "appid": CHAT_APPID, "uk": self.user.uk, "origin_id": self.user.trigger_id, "msg_key": "k" + str(int(time.time() * 100000)), "sdk_version": CHAT_SDK_VERSION, "is_reliable": False, "client_logid": int(time.time() * 1000 * 1000), "rpc": json.dumps({"rpc_retry_time": 0}), } request.LcmBody = json.dumps(reqdict).encode() response = self.waiter.new(request.correlationId) self.writer.write(request.toBytes()) reps = await response.read() try: rpc, lcm = ClientBLCPResponses.parseBLCPResponse(reps.toBytes()) except: raise Exception("BLCP Handshake error.") # TODO: 特殊的握手错误 if rpc.response.error_text != "success" or lcm.get("err_code") != 0: raise Exception("BLCP Handshake error.") # TODO: 特殊的握手错误 await self.fetch_mcast_msg_client_request(self.account.cuid_galaxy2, chatroom_id) # TODO:获取历史消息 if self.heartbeater is not None and not self.heartbeater.done(): self.heartbeater.cancel() self.heartbeater = self.loop.create_task(self.__heartbeater(), name="heartbeater") return True async def __heartbeater(self, freq: int = 5): while True: await asyncio.sleep(freq) await self.heartbeat() async def exitChatRoom(self, chatroom_id: int, room_type: int) -> bool: # 待实现 pass async def heartbeat(self): request = BLCPData(serviceId=1, methodId=3) request.RpcBody = self.buildRpcBody(serviceId=1, methodId=3, correlationId=request.correlationId) LcmBody = Lcm_pb2.RpcData() LcmBody.lcm_request.log_id = request.correlationId LcmBody.lcm_request.timestamp = request.timestamp request.LcmBody = LcmBody.SerializeToString() self.writer.write(request.toBytes()) async def enter_chatroom_client_request( self, cuid_galaxy2: str, room_id: int, account_type: int = 1 ): # 模拟正常请求,暂不清楚作用 headers = { "Content-Type": "application/json", "Accept-Encoding": "gzip", "User-Agent": "okhttp/3.11.0", "Host": "pim.baidu.com", "Cookie": "BDUSS=" + self.account.BDUSS, } data = { "appid": CHAT_APPID, "room_id": room_id, "app_version": CHAT_VERSION, "cuid": cuid_galaxy2, "device_id": cuid_galaxy2, "sdk_version": CHAT_SDK_VERSION, "timestamp": int(time.time()), "account_type": account_type, } data["sign"] = generate_sign(data) request = aiohttp.ClientRequest( "POST", yarl.URL.build( scheme="https", host="pim.baidu.com", path="/rest/3.0/im/chatroom/enter_chatroom_client", port=443 ), headers=headers, # proxy=self.net_core.proxy.url, # proxy_auth=self.net_core.proxy.auth, # ssl=False, data=json.dumps(data), ) # TODO: 支持代理 try: response = await self.net_core.req2res(request, False, 2 * 1024) await response.read() rjson = await response.json() except: raise Exception("进入群聊失败.") return rjson async def fetch_mcast_msg_client_request( self, cuid_galaxy2: str, room_id: int, account_type: int = 1 ): # 该方法可以获取历史消息,暂未继续开发 headers = { "Content-Type": "application/json", "Accept-Encoding": "gzip", "User-Agent": "okhttp/3.11.0", "Host": "pim.baidu.com", "Cookie": "BDUSS=" + self.account.BDUSS, } data = { "appid": CHAT_APPID, "mcast_id": room_id, "msgid_begin": 0, "msgid_end": 9223372036854775807, "count": -60, "category": 4, "app_version": CHAT_VERSION, "sdk_version": CHAT_SDK_VERSION, "device_id": cuid_galaxy2, "device_type": 2, "from_action": 1, "ext_info": urllib.parse.quote( json.dumps({"last_callback_msg_id": 0, "cast_id": 0, "local_ts": 0, "latest_msg_id": 0}) ), "timestamp": int(time.time()), "account_type": account_type, } data["sign"] = generate_sign(data) request = aiohttp.ClientRequest( "POST", yarl.URL.build(scheme="https", host="pim.baidu.com", path="/rest/3.0/im/fetch_mcast_msg_client", port=443), headers=headers, # proxy=self.net_core.proxy.url, # proxy_auth=self.net_core.proxy.auth, # ssl=False, data=json.dumps(data), ) # TODO: 支持代理 try: response = await self.net_core.req2res(request, False, 2 * 1024) await response.read() rjson = await response.json() except: raise Exception("进入群聊失败.") return rjson def generate_sign(json_obj): if json_obj is None: return "" # 按照字典序排序键值对 sorted_items = sorted(json_obj.items()) # 构造签名字符串 sign_str = "".join(f"{key}={value}" for key, value in sorted_items) # 计算MD5签名 return md5(sign_str.encode("utf-8")).hexdigest() @dcs.dataclass class BLCPData: serviceId: int methodId: int RpcBody: bytes LcmBody: bytes timestamp: int ifRequest: bool correlationId: int isNotify: bool def __init__( self, serviceId: int, methodId: int, RpcBody: bytes = None, LcmBody: bytes = None, timestamp: int = None, ifRequest: bool = True, isNotify: bool = False, ): self.serviceId = serviceId self.methodId = methodId self.RpcBody = RpcBody self.ifRequest = ifRequest self.LcmBody = LcmBody self.isNotify = isNotify if timestamp is None: self.timestamp = int(time.time() * 1000) else: self.timestamp = timestamp self.correlationId = random.randint(0, LongMax) def toBytes(self): buffer = bytearray() buffer.extend(b"lcp\x01") buffer.extend(int.to_bytes(len(self.RpcBody) + len(self.LcmBody), 4, "big")) buffer.extend(int.to_bytes(len(self.RpcBody), 4, "big")) buffer.extend(self.RpcBody) buffer.extend(self.LcmBody) return bytes(buffer) class ClientBLCPResponses: def __init__(self, reader: StreamReader, writer: StreamWriter): self.reader = reader self.writer = writer def __aiter__(self) -> ClientBLCPResponses: return self async def __anext__(self): rBytes = bytearray() try: rBytes += await self.reader.readuntil(b"lcp\x01") except IncompleteReadError: raise StopAsyncIteration("BLCP连接被关闭") # 连接被关闭,reader会得到EOF返回空字节而不是阻塞。 else: length_bytes = await self.reader.read(8) rBytes += length_bytes all_length = int.from_bytes(length_bytes[0:4], byteorder="big") rBytes += await self.reader.read(all_length) try: RpcMeta, Lcm = self.parseBLCPResponse(rBytes) except: return None RpcMeta: Rpc_pb2.RpcMeta if not RpcMeta or not Lcm: return None service_id = RpcMeta.response.service_id method_id = RpcMeta.response.method_id correlation_id = RpcMeta.correlation_id responseBLCP = BLCPData(serviceId=service_id, methodId=method_id) responseBLCP.correlationId = correlation_id responseBLCP.RpcBody = RpcMeta.SerializeToString() if RpcMeta.notify and (method_id != 3 and service_id != 1): # 屏蔽心跳包 responseBLCP.isNotify = True if isinstance(Lcm, Lcm_pb2.RpcData): responseBLCP.LcmBody = Lcm.SerializeToString() else: responseBLCP.LcmBody = json.dumps(Lcm).encode() # TODO:这里又编码回字节了,性能浪费 responseBLCP.ifRequest = False return responseBLCP async def __aenter__(self) -> ClientBLCPResponses: return self async def __aexit__(self) -> None: await self.close() async def close(self): pass @staticmethod def parseBLCPResponse(receivedBytes: bytes) -> (Rpc_pb2.RpcMeta, Lcm_pb2.RpcData): if len(receivedBytes) < 4: return None, None if receivedBytes[0:4] != b"lcp\x01": return None, None # 跳过lcp和1 receivedBytes = receivedBytes[4:] b1 = int.from_bytes(receivedBytes[0:4], byteorder="big") b2 = int.from_bytes(receivedBytes[4:8], byteorder="big") receivedBytes = receivedBytes[8:] barr1 = receivedBytes[0:b2] barr2 = receivedBytes[b2:b1] Rpc: Rpc_pb2.RpcMeta = Rpc_pb2.RpcMeta() Rpc.ParseFromString(barr1) # TODO: 优化Lcm消息类型判别,目前try处理比较暴力 try: Lcm = Lcm_pb2.RpcData() Lcm.ParseFromString(barr2) except: try: Lcm = Lcm_pb2.RpcData() Lcm.ParseFromString(gzip.decompress(barr2)) except: try: Lcm = json.loads(barr2) except: try: Lcm = json.loads(gzip.decompress(barr2)) except: raise ValueError("无法解析数据") return Rpc, Lcm @dcs.dataclass class BLCPResponse: """ BLCP响应 Args: future (asyncio.Future): 用于等待读事件到来的Future req_id (int): 请求id read_timeout (float): 读超时时间 """ loop: asyncio.AbstractEventLoop future: asyncio.Future req_id: int read_timeout: float def __init__(self, req_id: int, read_timeout: float) -> None: self.loop = asyncio.get_running_loop() self.future = self.loop.create_future() self.req_id = req_id self.read_timeout = read_timeout async def read(self) -> BLCPData: """ 读取BLCP响应 Returns: BLCPData Raises: asyncio.TimeoutError: 读取超时 """ try: async with timeout(self.read_timeout, self.loop): return await self.future except asyncio.TimeoutError as err: self.future.cancel() raise asyncio.TimeoutError("Timeout to read") from err except BaseException: self.future.cancel() raise @dcs.dataclass class BLCPWaiter: """ BLCP等待映射 """ loop: asyncio.AbstractEventLoop waiter: weakref.WeakValueDictionary req_id: int read_timeout: float def __init__(self, read_timeout: float) -> None: self.loop = asyncio.get_running_loop() self.waiter = weakref.WeakValueDictionary() self.req_id = int(time.time()) self.read_timeout = read_timeout weakref.finalize(self, self.__cancel_all) def __cancel_all(self) -> None: for ws_resp in self.waiter.values(): ws_resp.future.cancel() def new(self, req_id: int = None) -> BLCPResponse: """ 创建一个可用于等待数据的响应对象 Args: req_id (int): 请求id Returns: WsResponse: websocket响应 """ if not req_id: self.req_id += 1 else: self.req_id = req_id blcp_resp = BLCPResponse(self.req_id, self.read_timeout) self.waiter[self.req_id] = blcp_resp return blcp_resp def set_done(self, req_id: int, data: BLCPData) -> None: """ 将req_id对应的响应Future设置为已完成 Args: req_id (int): 请求id data (bytes): 填入的数据 """ blcp_resp: BLCPResponse = self.waiter.get(req_id, None) if blcp_resp is None: return blcp_resp.future.set_result(data) ================================================ FILE: src/aiotieba/core/http.py ================================================ from __future__ import annotations import dataclasses as dcs import urllib.parse from http.cookies import Morsel from typing import TYPE_CHECKING import aiohttp from ..__version__ import __version__ from ..const import APP_BASE_HOST from ..helper.crypto import sign if TYPE_CHECKING: import yarl from .account import Account from .net import NetCore @dcs.dataclass class HttpContainer: """ 用于保存会话headers与cookies的容器 """ headers: dict[str, str] cookie_jar: aiohttp.CookieJar def __init__(self, headers: dict[str, str], cookie_jar: aiohttp.CookieJar) -> None: self.headers: dict[str, str] = headers self.cookie_jar: aiohttp.CookieJar = cookie_jar @dcs.dataclass class HttpCore: """ 保存http接口相关状态的核心容器 """ account: Account net_core: NetCore app: HttpContainer app_proto: HttpContainer web: HttpContainer def __init__(self, account: Account, net_core: NetCore) -> None: self.net_core = net_core from aiohttp import hdrs app_headers = { hdrs.USER_AGENT: f"aiotieba/{__version__}", hdrs.ACCEPT_ENCODING: "gzip", hdrs.CONNECTION: "keep-alive", hdrs.HOST: APP_BASE_HOST, } self.app = HttpContainer(app_headers, aiohttp.DummyCookieJar()) app_proto_headers = { hdrs.USER_AGENT: f"aiotieba/{__version__}", "x_bd_data_type": "protobuf", hdrs.ACCEPT_ENCODING: "gzip", hdrs.CONNECTION: "keep-alive", hdrs.HOST: APP_BASE_HOST, } self.app_proto = HttpContainer(app_proto_headers, aiohttp.DummyCookieJar()) web_headers = { hdrs.USER_AGENT: f"aiotieba/{__version__}", hdrs.ACCEPT_ENCODING: "gzip, deflate", hdrs.CACHE_CONTROL: "no-cache", hdrs.CONNECTION: "keep-alive", } self.web = HttpContainer(web_headers, aiohttp.CookieJar()) self.set_account(account) def set_account(self, new_account: Account) -> None: self.account = new_account BDUSS_morsel = Morsel() BDUSS_morsel.set("BDUSS", new_account.BDUSS, new_account.BDUSS) BDUSS_morsel["domain"] = "baidu.com" self.web.cookie_jar._cookies["baidu.com", ""]["BDUSS"] = BDUSS_morsel STOKEN_morsel = Morsel() STOKEN_morsel.set("STOKEN", new_account.STOKEN, new_account.STOKEN) STOKEN_morsel["domain"] = "tieba.baidu.com" self.web.cookie_jar._cookies["tieba.baidu.com", ""]["STOKEN"] = STOKEN_morsel def pack_form_request(self, url: yarl.URL, data: list[tuple[str, str]]) -> aiohttp.ClientRequest: """ 自动签名参数元组列表 并将其打包为移动端表单请求 Args: url (yarl.URL): 链接 data (list[tuple[str, str]]): 参数元组列表 Returns: aiohttp.ClientRequest """ payload = aiohttp.payload.BytesPayload( urllib.parse.urlencode(sign(data), doseq=True).encode("utf-8"), content_type="application/x-www-form-urlencoded", ) request = aiohttp.ClientRequest( aiohttp.hdrs.METH_POST, url, headers=self.app.headers, data=payload, proxy=self.net_core.proxy.url, proxy_auth=self.net_core.proxy.auth, ssl=False, ) return request def pack_proto_request(self, url: yarl.URL, data: bytes) -> aiohttp.ClientRequest: """ 打包移动端protobuf请求 Args: url (yarl.URL): 链接 data (bytes): protobuf序列化后的二进制数据 Returns: aiohttp.ClientRequest """ writer = aiohttp.MultipartWriter("form-data", boundary="-*_r1999") payload_headers = { aiohttp.hdrs.CONTENT_DISPOSITION: aiohttp.helpers.content_disposition_header( "form-data", name="data", filename="file" ) } payload = aiohttp.BytesPayload(data, content_type="", headers=payload_headers) payload.headers.popone(aiohttp.hdrs.CONTENT_TYPE) writer._parts.append((payload, None, None)) request = aiohttp.ClientRequest( aiohttp.hdrs.METH_POST, url, headers=self.app_proto.headers, data=writer, proxy=self.net_core.proxy.url, proxy_auth=self.net_core.proxy.auth, ssl=False, ) return request def pack_web_get_request( self, url: yarl.URL, params: list[tuple[str, str]], *, extra_headers: list[tuple[str, str]] | None = None ) -> aiohttp.ClientRequest: """ 打包网页端参数请求 Args: url (yarl.URL): 链接 params (list[tuple[str, str]]): 参数元组列表 extra_headers (list[tuple[str, str]]): 额外的请求头 Returns: aiohttp.ClientRequest """ url = url.update_query(params) headers = self.web.headers if extra_headers: headers |= extra_headers request = aiohttp.ClientRequest( aiohttp.hdrs.METH_GET, url, headers=headers, cookies=self.web.cookie_jar.filter_cookies(url), proxy=self.net_core.proxy.url, proxy_auth=self.net_core.proxy.auth, ssl=False, ) return request def pack_web_form_request( self, url: yarl.URL, data: list[tuple[str, str]], *, extra_headers: list[tuple[str, str]] | None = None ) -> aiohttp.ClientRequest: """ 打包网页端表单请求 Args: url (yarl.URL): 链接 data (list[tuple[str, str]]): 参数元组列表 extra_headers (list[tuple[str, str]]): 额外的请求头 Returns: aiohttp.ClientRequest """ headers = self.web.headers if extra_headers: headers |= extra_headers payload = aiohttp.payload.BytesPayload( urllib.parse.urlencode(data, doseq=True).encode("utf-8"), content_type="application/x-www-form-urlencoded", ) request = aiohttp.ClientRequest( aiohttp.hdrs.METH_POST, url, headers=headers, data=payload, cookies=self.web.cookie_jar.filter_cookies(url), proxy=self.net_core.proxy.url, proxy_auth=self.net_core.proxy.auth, ssl=False, ) return request ================================================ FILE: src/aiotieba/core/net.py ================================================ from __future__ import annotations import asyncio import dataclasses as dcs from collections.abc import Callable import aiohttp from ..config import ProxyConfig, TimeoutConfig from ..exception import HTTPStatusError from ..helper import timeout def check_status_code(response: aiohttp.ClientResponse) -> None: if response.status != 200: raise HTTPStatusError(response.status, response.reason) TypeHeadersChecker = Callable[[aiohttp.ClientResponse], None] @dcs.dataclass class NetCore: """ 网络请求相关容器 Args: connector (aiohttp.TCPConnector): 用于生成TCP连接的连接器 proxy (ProxyConfig, optional): 代理配置. Defaults to None. timeout (TimeoutConfig, optional): 超时配置. Defaults to None. """ connector: aiohttp.TCPConnector proxy: ProxyConfig timeout: TimeoutConfig def __init__( self, connector: aiohttp.TCPConnector, proxy: ProxyConfig | None = None, timeout: TimeoutConfig | None = None, ) -> None: self.connector = connector if not isinstance(proxy, ProxyConfig): proxy = ProxyConfig() self.proxy = proxy if not isinstance(timeout, TimeoutConfig): timeout = TimeoutConfig() self.timeout = timeout async def req2res( self, request: aiohttp.ClientRequest, read_until_eof: bool = True, read_bufsize: int = 64 * 1024 ) -> aiohttp.ClientResponse: """ 发送http请求并返回ClientResponse Args: request (aiohttp.ClientRequest): 待发送的请求 read_until_eof (bool, optional): 是否读取到EOF就中止. Defaults to True. read_bufsize (int, optional): 读缓冲区大小 以字节为单位. Defaults to 64KiB. Returns: ClientResponse: 响应 """ # 获取TCP连接 try: async with timeout(self.timeout.http_connect, self.connector._loop): conn = await self.connector.connect(request, [], self.timeout.http_timeout) except asyncio.TimeoutError as exc: raise aiohttp.ServerTimeoutError(f"Connection timeout to host {request.url}") from exc # 设置响应解析流程 conn.protocol.set_response_params( read_until_eof=read_until_eof, auto_decompress=True, read_timeout=self.timeout.http_read, read_bufsize=read_bufsize, ) # 发送请求 try: response = await request.send(conn) except BaseException: conn.close() raise try: await response.start(conn) except BaseException: response.close() raise return response async def send_request( self, request: aiohttp.ClientRequest, read_bufsize: int = 64 * 1024, headers_checker: TypeHeadersChecker = check_status_code, ) -> bytes: """ 简单发送http请求 不包含重定向和身份验证功能 Args: request (aiohttp.ClientRequest): 待发送的请求 read_bufsize (int, optional): 读缓冲区大小 以字节为单位. Defaults to 64KiB. headers_checker (TypeHeadersChecker, optional): headers检查函数. Defaults to check_status_code. Returns: bytes: body """ response = await self.req2res(request, True, read_bufsize) # 检查headers headers_checker(response) # 读取响应 response._body = await response.content.read() body = response._body # 释放连接 response.release() return body ================================================ FILE: src/aiotieba/core/websocket.py ================================================ from __future__ import annotations import asyncio import binascii import dataclasses as dcs import gzip import random import time import weakref from collections.abc import Awaitable, Callable from typing import TYPE_CHECKING import aiohttp import yarl from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives.ciphers import algorithms from ..enums import WsStatus from ..exception import HTTPStatusError from ..helper import timeout if TYPE_CHECKING: from .account import Account from .net import NetCore TypeWebsocketCallback = Callable[["WsCore", bytes, int], Awaitable[None]] def pack_ws_bytes( account: Account, data: bytes, cmd: int, req_id: int, *, compress: bool = False, encrypt: bool = True ) -> bytes: """ 打包数据并添加9字节头部 Args: account (Account): 贴吧的用户参数容器 data (bytes): 待发送的websocket数据 cmd (int): 请求的cmd类型 req_id (int): 请求的id compress (bool, optional): 是否需要gzip压缩. Defaults to False. encrypt (bool, optional): 是否需要aes加密. Defaults to True. Returns: bytes: 打包后的websocket数据 """ flag = 0x08 if compress: flag |= 0b01000000 data = gzip.compress(data, compresslevel=6, mtime=0) if encrypt: flag |= 0b10000000 padder = padding.PKCS7(algorithms.AES.block_size).padder() data = padder.update(data) + padder.finalize() encryptor = account.aes_ecb_chiper.encryptor() data = encryptor.update(data) + encryptor.finalize() data = b"".join([ flag.to_bytes(1, "big"), cmd.to_bytes(4, "big"), req_id.to_bytes(4, "big"), data, ]) return data def parse_ws_bytes(account: Account, data: bytes) -> tuple[bytes, int, int]: """ 对websocket返回数据进行解包 Args: account (Account): 贴吧的用户参数容器 data (bytes): 接收到的websocket数据 Returns: bytes: 解包后的websocket数据 int: 对应请求的cmd类型 int: 对应请求的id """ data_view = memoryview(data) flag = data_view[0] cmd = int.from_bytes(data_view[1:5], "big") req_id = int.from_bytes(data_view[5:9], "big") data = data_view[9:].tobytes() if flag & 0b10000000: decryptor = account.aes_ecb_chiper.decryptor() data = decryptor.update(data) + decryptor.finalize() unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() data = unpadder.update(data) + unpadder.finalize() if flag & 0b01000000: data = gzip.decompress(data) return data, cmd, req_id @dcs.dataclass class MsgIDPair: """ 长度为2的msg_id队列 记录新旧msg_id """ last_id: int = 0 curr_id: int = 0 def update_msg_id(self, curr_id: int) -> None: """ 更新msg_id Args: curr_id (int): 当前消息的msg_id """ self.last_id = self.curr_id self.curr_id = curr_id @dcs.dataclass class MsgIDManager: """ msg_id管理器 """ priv_gid: int = 0 gid2mid: dict[int, MsgIDPair] = dcs.field(default_factory=lambda: {0: MsgIDPair()}) def update_msg_id(self, group_id: int, msg_id: int) -> None: """ 更新group_id对应的msg_id Args: group_id (int): 消息组id msg_id (int): 当前消息的msg_id """ mid_pair = self.gid2mid.get(group_id, None) if mid_pair is not None: mid_pair.update_msg_id(msg_id) else: mid_pair = MsgIDPair(msg_id, msg_id) def get_msg_id(self, group_id: int) -> int: """ 获取group_id对应的msg_id Args: group_id (int): 消息组id Returns: int: 上一条消息的msg_id """ return self.gid2mid[group_id].last_id def get_record_id(self) -> int: """ 获取record_id Returns: int: record_id """ return self.get_msg_id(self.priv_gid) * 100 + 1 @dcs.dataclass class WsResponse: """ websocket响应 Args: future (asyncio.Future): 用于等待读事件到来的Future req_id (int): 请求id read_timeout (float): 读超时时间 """ loop: asyncio.AbstractEventLoop future: asyncio.Future req_id: int read_timeout: float def __init__(self, req_id: int, read_timeout: float) -> None: self.loop = asyncio.get_running_loop() self.future = self.loop.create_future() self.req_id = req_id self.read_timeout = read_timeout async def read(self) -> bytes: """ 读取websocket响应 Returns: bytes Raises: asyncio.TimeoutError: 读取超时 """ try: async with timeout(self.read_timeout, self.loop): return await self.future except asyncio.TimeoutError as err: self.future.cancel() raise asyncio.TimeoutError("Timeout to read") from err except BaseException: self.future.cancel() raise @dcs.dataclass class WsWaiter: """ websocket等待映射 """ loop: asyncio.AbstractEventLoop waiter: weakref.WeakValueDictionary req_id: int read_timeout: float def __init__(self, read_timeout: float) -> None: self.loop = asyncio.get_running_loop() self.waiter = weakref.WeakValueDictionary() self.req_id = int(time.time()) self.read_timeout = read_timeout weakref.finalize(self, self.__cancel_all) def __cancel_all(self) -> None: for ws_resp in self.waiter.values(): ws_resp.future.cancel() def new(self) -> WsResponse: """ 创建一个可用于等待数据的响应对象 Args: req_id (int): 请求id Returns: WsResponse: websocket响应 """ self.req_id += 1 ws_resp = WsResponse(self.req_id, self.read_timeout) self.waiter[self.req_id] = ws_resp return ws_resp def set_done(self, req_id: int, data: bytes) -> None: """ 将req_id对应的响应Future设置为已完成 Args: req_id (int): 请求id data (bytes): 填入的数据 """ ws_resp: WsResponse = self.waiter.get(req_id, None) if ws_resp is None: return ws_resp.future.set_result(data) @dcs.dataclass class WsCore: """ 保存websocket接口相关状态的核心容器 """ account: Account net_core: NetCore waiter: WsWaiter callbacks: dict[int, TypeWebsocketCallback] websocket: aiohttp.ClientWebSocketResponse ws_dispatcher: asyncio.Task mid_manager: MsgIDManager _status: WsStatus loop: asyncio.AbstractEventLoop def __init__(self, account: Account, net_core: NetCore) -> None: self.set_account(account) self.net_core = net_core self.callbacks: dict[int, TypeWebsocketCallback] = {} self.websocket: aiohttp.ClientWebSocketResponse = None self.ws_dispatcher: asyncio.Task = None self._status = WsStatus.CLOSED self.loop = asyncio.get_running_loop() def set_account(self, new_account: Account) -> None: self.account = new_account async def connect(self) -> None: """ 建立weboscket连接 Raises: aiohttp.WSServerHandshakeError: websocket握手失败 """ self._status = WsStatus.CONNECTING self.waiter = WsWaiter(self.net_core.timeout.ws_read) self.mid_manager = MsgIDManager() from aiohttp import hdrs ws_url = yarl.URL.build(scheme="ws", host="im.tieba.baidu.com", port=8000) sec_key_bytes = binascii.b2a_base64(random.randbytes(16), newline=False) headers = { hdrs.UPGRADE: "websocket", hdrs.CONNECTION: "upgrade", hdrs.SEC_WEBSOCKET_EXTENSIONS: "im_version=2.3", hdrs.SEC_WEBSOCKET_VERSION: "13", hdrs.SEC_WEBSOCKET_KEY: sec_key_bytes.decode("ascii"), hdrs.ACCEPT_ENCODING: "gzip", hdrs.HOST: "im.tieba.baidu.com:8000", } request = aiohttp.ClientRequest( hdrs.METH_GET, ws_url, headers=headers, proxy=self.net_core.proxy.url, proxy_auth=self.net_core.proxy.auth, ssl=False, ) response = await self.net_core.req2res(request, False, 2 * 1024) if response.status != 101: raise HTTPStatusError(response.status, response.reason) try: conn = response.connection conn_proto = conn.protocol transport = conn.transport reader = aiohttp.client.WebSocketDataQueue(conn_proto, 1 << 16, loop=self.loop) conn_proto.set_parser(aiohttp.client.WebSocketReader(reader, 4 * 1024 * 1024), reader) writer = aiohttp.client.WebSocketWriter(conn_proto, transport, use_mask=True) except BaseException: response.close() raise else: self.websocket = aiohttp.ClientWebSocketResponse( reader, writer, "chat", response, self.net_core.timeout.ws_timeout, True, True, self.loop, heartbeat=self.net_core.timeout.ws_heartbeat, ) if self.ws_dispatcher is not None and not self.ws_dispatcher.done(): self.ws_dispatcher.cancel() self.ws_dispatcher = self.loop.create_task(self.__ws_dispatch(), name="ws_dispatcher") async def close(self) -> None: if self.status == WsStatus.OPEN: await self.websocket.close() self.ws_dispatcher.cancel() self._status = WsStatus.CLOSED def __default_callback(self, req_id: int, data: bytes) -> None: self.waiter.set_done(req_id, data) async def __ws_dispatch(self) -> None: try: async for msg in self.websocket: data, cmd, req_id = parse_ws_bytes(self.account, msg.data) res_callback = self.callbacks.get(cmd, None) if res_callback is None: self.__default_callback(req_id, data) else: self.loop.create_task(res_callback(self, data, req_id)) except asyncio.CancelledError: self._status = WsStatus.CLOSED except Exception: self._status = WsStatus.CLOSED @property def status(self) -> WsStatus: """ websocket状态 """ if self._status != WsStatus.CLOSED and self.websocket._writer.transport.is_closing(): self._status = WsStatus.CLOSED return self._status async def send(self, data: bytes, cmd: int, *, compress: bool = False, encrypt: bool = True) -> WsResponse: """ 将protobuf序列化结果打包发送 Args: data (bytes): 待发送的数据 cmd (int): 请求的cmd类型 compress (bool, optional): 是否需要gzip压缩. Defaults to False. encrypt (bool, optional): 是否需要aes加密. Defaults to True. Returns: WsResponse: websocket响应对象 Raises: asyncio.TimeoutError: 发送超时 """ response = self.waiter.new() req_data = pack_ws_bytes(self.account, data, cmd, response.req_id, compress=compress, encrypt=encrypt) try: async with timeout(self.net_core.timeout.ws_send, self.loop): await self.websocket.send_bytes(req_data) except asyncio.TimeoutError as err: response.future.cancel() raise asyncio.TimeoutError("Timeout to send") from err except BaseException: response.future.cancel() else: return response ================================================ FILE: src/aiotieba/enums.py ================================================ from __future__ import annotations import enum import sys if sys.version_info >= (3, 11): from enum import StrEnum else: from strenum import StrEnum class Gender(enum.IntEnum): """ 用户性别 Note: UNKNOWN 未知\n MALE 男性\n FEMALE 女性 """ UNKNOWN = 0 MALE = 1 FEMALE = 2 class PrivLike(enum.IntEnum): """ 关注吧列表的公开状态 Note: UNKNOWN 未知\n PUBLIC 所有人可见\n FRIEND 好友可见\n HIDE 完全隐藏 """ UNKNOWN = 0 PUBLIC = 1 FRIEND = 2 HIDE = 3 @classmethod def __missing__(cls, _: int) -> PrivLike: return PrivLike.UNKNOWN class PrivReply(enum.IntEnum): """ 帖子评论权限 Note: UNKNOWN 未知\n ALL 允许所有人\n FANS 仅允许我的粉丝\n FOLLOW 仅允许我的关注 """ UNKNOWN = 0 ALL = 1 FANS = 5 FOLLOW = 6 @classmethod def __missing__(cls, _: int) -> PrivReply: return PrivReply.UNKNOWN class ThreadType(enum.IntEnum): """ 主题帖类型 Note: UNKNOWN 未知\n ARTICLE 图文帖\n ALBUM 相册帖\n VOICE 语音帖\n STORY 会员小说帖\n VIDEO 视频帖\n LIVE 直播帖\n HELP 求助帖\n VOTE 打分帖\n LOTTERY 抽奖帖 """ UNKNOWN = -1 ARTICLE = 0 ALBUM = 1 VOICE = 11 STORY = 31 VIDEO = 40 LIVE = 50 HELP = 71 VOTE = 75 LOTTERY = 76 @classmethod def __missing__(cls, _: int) -> ThreadType: return ThreadType.UNKNOWN class ReqUInfo(enum.Flag): """ 使用该枚举类指定待获取的用户信息字段 Note: 各bit位的含义由高到低分别为 OTHER, TIEBA_UID, NICK_NAME, USER_NAME, PORTRAIT, USER_ID\n 其中BASIC = USER_ID | PORTRAIT | USER_NAME """ USER_ID = enum.auto() PORTRAIT = enum.auto() USER_NAME = enum.auto() NICK_NAME = enum.auto() TIEBA_UID = enum.auto() OTHER = enum.auto() BASIC = USER_ID | PORTRAIT | USER_NAME ALL = BASIC | NICK_NAME | TIEBA_UID | OTHER class ThreadSortType(enum.IntEnum): """ 主题帖排序 Note: 对于有热门分区的贴吧 0热门排序(HOT) 1按发布时间(CREATE) 2关注的人(FOLLOW) 34热门排序(HOT) >=6是按回复时间(REPLY)\n 对于无热门分区的贴吧 0按回复时间(REPLY) 1按发布时间(CREATE) 2关注的人(FOLLOW) >=3按回复时间(REPLY) """ REPLY = 6 CREATE = 1 HOT = 3 FOLLOW = 2 class PostSortType(enum.IntEnum): """ 回复排序 Note: ASC 时间顺序\n DESC 时间倒序\n HOT 热门序 """ ASC = 0 DESC = 1 HOT = 2 class BawuSearchType(enum.IntEnum): """ 吧务后台搜索类型 Note: USER 搜索用户\n OP 搜索操作者 """ USER = 0 OP = 1 class SearchType(enum.IntEnum): """ 搜索类型 Note: ALL 搜索全部\n TIME app时间倒序\n RELATION app相关性排序 """ ALL = 0 TIME = 1 RELATION = 2 class BawuType(StrEnum): """ 吧务类型 Note: MANAGER 小吧\n IMAGE_EDITOR 图片小编\n VOICE_EDITOR 语音小编 """ MANAGER = "assist" IMAGE_EDITOR = "picadmin" VOICE_EDITOR = "voiceadmin" class BawuPermType(enum.Flag): """ 吧务已分配的权限 Note: NULL 无权限\n UNBLOCK 解除封禁\n UNBLOCK_APPEAL 封禁申诉处理\n RECOVER 恢复删帖\n RECOVER_APPEAL 删帖申诉处理\n ALL 所有权限 """ NULL = 0 UNBLOCK = enum.auto() UNBLOCK_APPEAL = enum.auto() RECOVER = enum.auto() RECOVER_APPEAL = enum.auto() ALL = UNBLOCK | UNBLOCK_APPEAL | RECOVER | RECOVER_APPEAL class RankForumType(enum.IntEnum): """ 吧签到排行榜类别 Note: TODAY 今日排行\n YESTERDAY 昨日排行\n WEEKLY 周排行\n MONTHLY 月排行 """ TODAY = 0 YESTERDAY = 1 WEEKLY = 2 MONTHLY = 3 class BlacklistType(enum.Flag): """ 用户黑名单类型 Note: NULL 正常状态\n FOLLOW 禁止关注\n INTERACT 禁止互动\n CHAT 禁止私信\n ALL 全屏蔽 """ NULL = 0 FOLLOW = enum.auto() INTERACT = enum.auto() CHAT = enum.auto() ALL = FOLLOW | INTERACT | CHAT class WsStatus(enum.IntEnum): """ 回复排序 Note: CLOSED 已关闭\n CONNECTING 正在连接\n OPEN 可用 """ CLOSED = 0 CONNECTING = enum.auto() OPEN = enum.auto() class GroupType(enum.IntEnum): """ 消息组类型 """ PRIVATE_MSG = 6 MISC = 8 class MsgType(enum.IntEnum): """ 消息类型 """ PRIVATE_MSG = 1 MISC = 10 READED = 22 ================================================ FILE: src/aiotieba/exception.py ================================================ from __future__ import annotations import dataclasses as dcs @dcs.dataclass class TbErrorExt: """ 为类型添加一个`err`项 用于保存捕获到的异常 """ err: Exception | None = dcs.field(default=None, init=False, repr=False) @dcs.dataclass class BoolResponse(TbErrorExt): """ bool返回值 不是内置bool的子类 可能不支持部分bool操作 Attributes: err (Exception | None): 捕获的异常 """ def __bool__(self) -> bool: return self.err is None def __int__(self) -> int: return int(bool(self)) def __repr__(self) -> str: return str(bool(self)) def __hash__(self) -> int: return hash(bool(self)) @dcs.dataclass class IntResponse(TbErrorExt, int): """ int返回值 是内置int的子类 Attributes: err (Exception | None): 捕获的异常 """ def __new__(cls, i: int = 0) -> IntResponse: obj = super().__new__(cls, i) return obj def __init__(self, i: int = 0) -> None: pass def __repr__(self) -> str: return str(int(self)) def __hash__(self) -> int: return hash(int(self)) @dcs.dataclass class StrResponse(TbErrorExt, str): """ str返回值 是内置str的子类 Attributes: err (Exception | None): 捕获的异常 """ __slots__ = [] def __new__(cls, s: str = "") -> StrResponse: obj = super().__new__(cls, s) return obj def __init__(self, s: str = "") -> None: pass def __hash__(self) -> int: return hash(str(self)) class TiebaServerError(RuntimeError): """ 贴吧服务器异常 """ __slots__ = ["code", "msg"] def __init__(self, code: int, msg: str) -> None: super().__init__(code, msg) self.code = code self.msg = msg class HTTPStatusError(RuntimeError): """ 错误的状态码 """ __slots__ = ["code", "msg"] def __init__(self, code: int, msg: str) -> None: super().__init__(code, msg) self.code = code self.msg = msg class TiebaValueError(RuntimeError): """ 意外的字段值 """ class ContentTypeError(RuntimeError): """ 无法解析响应头中的content-type """ ================================================ FILE: src/aiotieba/helper/__init__.py ================================================ from ..helper import cache, crypto, utils from .utils import ( default_datetime, deprecated, handle_exception, is_portrait, is_user_name, jsonlib, pack_json, parse_json, timeout, ) ================================================ FILE: src/aiotieba/helper/cache.py ================================================ from __future__ import annotations from collections import OrderedDict from typing import ClassVar class ForumInfoCache: """ 吧信息缓存 """ _fname2fid: ClassVar[OrderedDict] = OrderedDict() _fid2fname: ClassVar[OrderedDict] = OrderedDict() @classmethod def get_fid(cls, fname: str) -> int: """ 通过贴吧名获取forum_id Args: fname (str): 贴吧名 Returns: int: 该贴吧的forum_id """ return cls._fname2fid.get(fname, "") @classmethod def get_fname(cls, fid: int) -> str: """ 通过forum_id获取贴吧名 Args: fid (int): forum_id Returns: str: 该贴吧的贴吧名 """ return cls._fid2fname.get(fid, "") @classmethod def add_forum(cls, fname: str, fid: int) -> None: """ 将贴吧名与forum_id的映射关系添加到缓存 Args: fname (str): 贴吧名 fid (int): 贴吧id """ if len(cls._fname2fid) == 128: cls._fname2fid.popitem(last=False) cls._fid2fname.popitem(last=False) cls._fname2fid[fname] = fid cls._fid2fname[fid] = fname ================================================ FILE: src/aiotieba/helper/crypto/CMakeLists.txt ================================================ set(TBC_INCLUDE_DIRS ./include) file(GLOB_RECURSE TBC_SOURCES *.c) Python3_add_library(crypto MODULE ${TBC_SOURCES}) target_include_directories(crypto PUBLIC ${TBC_INCLUDE_DIRS}) install(TARGETS crypto DESTINATION ${SKBUILD_PROJECT_NAME}/helper/crypto) install(TARGETS crypto DESTINATION ${PROJECT_SOURCE_DIR}/src/${SKBUILD_PROJECT_NAME}/helper/crypto) ================================================ FILE: src/aiotieba/helper/crypto/__init__.py ================================================ from __future__ import annotations from .crypto import c3_aid, cuid_galaxy2, enuid, rc4_42 from .crypto import sign as _sign def sign(data: list[tuple[str, str | int]]) -> list[tuple[str, str | int]]: """ 为参数元组列表添加贴吧客户端签名 Args: data (list[tuple[str, str | int]]): 参数元组列表 Returns: list[tuple[str, str | int]]: 签名后的form参数元组列表 """ data.append(("sign", _sign(data))) return data ================================================ FILE: src/aiotieba/helper/crypto/crypto.pyi ================================================ def cuid_galaxy2(android_id: str) -> str: """ 使用给定的android_id生成cuid_galaxy2 Args: android_id (str): 长度为16的16进制字符串 包含8字节信息 Returns: str: cuid_galaxy2 长度为42的字符串 Examples: A3ED2D7B9CFC28E8934A3FBD3A9579C7|VZ5FKB5XS Note: 此实现与12.x版本及以前的官方实现一致 """ def c3_aid(android_id: str, uuid: str) -> str: """ 使用给定的android_id和uuid生成c3_aid Args: android_id (str): 长度为16的16进制字符串 包含8字节信息 uuid (str): 包含16字节信息 Returns: str: c3_aid 长度为45的字符串 Examples: A00-ZNU3O3EP74D727LMQY745CZSGZQJQZGP-3JXCKC7X Note: 此实现与12.x版本及以前的官方实现一致 """ def rc4_42(xyus_md5_str: str, aes_cbc_sec_key: bytes) -> bytes: """ RC4加密的变体 一次额外的42异或 Args: xyus_md5_str (str): 32字节长小写字符串 作为RC4密钥 aes_cbc_sec_key (bytes): 贴吧AES-CBC加密使用的随机密码 Returns: bytes """ def sign(data: list[tuple[str, str | int]]) -> str: """ 为参数元组列表计算贴吧客户端签名 Args: data (list[tuple[str, str | int]]): 参数元组列表 Returns: str: 签名 """ def enuid(cuid_galaxy2: str) -> str: """ 生成EnUid Args: cuid_galaxy2 (str) Returns: str: 变种base64编码后的enuid """ ================================================ FILE: src/aiotieba/helper/crypto/include/base32/base32.h ================================================ // Base32 implementation // // Copyright 2010 Google Inc. // Author: Markus Gutschke // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Encode and decode from base32 encoding using the following alphabet: // ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 // This alphabet is documented in RFC 4648/3548 // // We allow white-space and hyphens, but all other characters are considered // invalid. // // All functions return the number of output bytes or -1 on error. If the // output buffer is too small, the result will silently be truncated. #pragma once #define BASE32_LEN(len) (((len) / 5) * 8 + ((len) % 5 ? 8 : 0)) void tbc_base32_encode(const unsigned char* src, int srcLen, unsigned char* dst); ================================================ FILE: src/aiotieba/helper/crypto/include/crc/crc32.h ================================================ /* ** The crc32 is licensed under the Apache License, Version 2.0, and a copy of the license is included in this file. ** ** Author: Wang Yaofu voipman@qq.com ** Description: The source file of class crc32. ** CRC32 implementation according to IEEE standards. ** Polynomials are represented in LSB-first form ** following parameters: ** Width : 32 bit ** Poly : 0xEDB88320 ** Output for "123456789" : 0xCBF43926 */ #pragma once #include // size_t #include // uint32_t uint32_t tbc_crc32(const unsigned char* src, size_t srcLen, uint32_t prev_val); ================================================ FILE: src/aiotieba/helper/crypto/include/mbedtls/alignment.h ================================================ /** * \file alignment.h * * \brief Utility code for dealing with unaligned memory accesses */ /* * Copyright The Mbed TLS Contributors * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef MBEDTLS_LIBRARY_ALIGNMENT_H #define MBEDTLS_LIBRARY_ALIGNMENT_H #include #include #include /* * Define MBEDTLS_EFFICIENT_UNALIGNED_ACCESS for architectures where unaligned memory * accesses are known to be efficient. * * All functions defined here will behave correctly regardless, but might be less * efficient when this is not defined. */ #if defined(__ARM_FEATURE_UNALIGNED) || defined(__i386__) || defined(__amd64__) || defined(__x86_64__) /* * __ARM_FEATURE_UNALIGNED is defined where appropriate by armcc, gcc 7, clang 9 * (and later versions) for Arm v7 and later; all x86 platforms should have * efficient unaligned access. */ #define MBEDTLS_EFFICIENT_UNALIGNED_ACCESS #endif /** * Read the unsigned 16 bits integer from the given address, which need not * be aligned. * * \param p pointer to 2 bytes of data * \return Data at the given address */ inline uint16_t mbedtls_get_unaligned_uint16(const void* p) { uint16_t r; memcpy(&r, p, sizeof(r)); return r; } /** * Write the unsigned 16 bits integer to the given address, which need not * be aligned. * * \param p pointer to 2 bytes of data * \param x data to write */ inline void mbedtls_put_unaligned_uint16(void* p, uint16_t x) { memcpy(p, &x, sizeof(x)); } /** * Read the unsigned 32 bits integer from the given address, which need not * be aligned. * * \param p pointer to 4 bytes of data * \return Data at the given address */ inline uint32_t mbedtls_get_unaligned_uint32(const void* p) { uint32_t r; memcpy(&r, p, sizeof(r)); return r; } /** * Write the unsigned 32 bits integer to the given address, which need not * be aligned. * * \param p pointer to 4 bytes of data * \param x data to write */ inline void mbedtls_put_unaligned_uint32(void* p, uint32_t x) { memcpy(p, &x, sizeof(x)); } /** * Read the unsigned 64 bits integer from the given address, which need not * be aligned. * * \param p pointer to 8 bytes of data * \return Data at the given address */ inline uint64_t mbedtls_get_unaligned_uint64(const void* p) { uint64_t r; memcpy(&r, p, sizeof(r)); return r; } /** * Write the unsigned 64 bits integer to the given address, which need not * be aligned. * * \param p pointer to 8 bytes of data * \param x data to write */ inline void mbedtls_put_unaligned_uint64(void* p, uint64_t x) { memcpy(p, &x, sizeof(x)); } /** Byte Reading Macros * * Given a multi-byte integer \p x, MBEDTLS_BYTE_n retrieves the n-th * byte from x, where byte 0 is the least significant byte. */ #define MBEDTLS_BYTE_0(x) ((uint8_t)((x)&0xff)) #define MBEDTLS_BYTE_1(x) ((uint8_t)(((x) >> 8) & 0xff)) #define MBEDTLS_BYTE_2(x) ((uint8_t)(((x) >> 16) & 0xff)) #define MBEDTLS_BYTE_3(x) ((uint8_t)(((x) >> 24) & 0xff)) #define MBEDTLS_BYTE_4(x) ((uint8_t)(((x) >> 32) & 0xff)) #define MBEDTLS_BYTE_5(x) ((uint8_t)(((x) >> 40) & 0xff)) #define MBEDTLS_BYTE_6(x) ((uint8_t)(((x) >> 48) & 0xff)) #define MBEDTLS_BYTE_7(x) ((uint8_t)(((x) >> 56) & 0xff)) /* * Detect GCC built-in byteswap routines */ #if defined(__GNUC__) && defined(__GNUC_PREREQ) #if __GNUC_PREREQ(4, 8) #define MBEDTLS_BSWAP16 __builtin_bswap16 #endif /* __GNUC_PREREQ(4,8) */ #if __GNUC_PREREQ(4, 3) #define MBEDTLS_BSWAP32 __builtin_bswap32 #define MBEDTLS_BSWAP64 __builtin_bswap64 #endif /* __GNUC_PREREQ(4,3) */ #endif /* defined(__GNUC__) && defined(__GNUC_PREREQ) */ /* * Detect Clang built-in byteswap routines */ #if defined(__clang__) && defined(__has_builtin) #if __has_builtin(__builtin_bswap16) && !defined(MBEDTLS_BSWAP16) #define MBEDTLS_BSWAP16 __builtin_bswap16 #endif /* __has_builtin(__builtin_bswap16) */ #if __has_builtin(__builtin_bswap32) && !defined(MBEDTLS_BSWAP32) #define MBEDTLS_BSWAP32 __builtin_bswap32 #endif /* __has_builtin(__builtin_bswap32) */ #if __has_builtin(__builtin_bswap64) && !defined(MBEDTLS_BSWAP64) #define MBEDTLS_BSWAP64 __builtin_bswap64 #endif /* __has_builtin(__builtin_bswap64) */ #endif /* defined(__clang__) && defined(__has_builtin) */ /* * Detect MSVC built-in byteswap routines */ #if defined(_MSC_VER) #if !defined(MBEDTLS_BSWAP16) #define MBEDTLS_BSWAP16 _byteswap_ushort #endif #if !defined(MBEDTLS_BSWAP32) #define MBEDTLS_BSWAP32 _byteswap_ulong #endif #if !defined(MBEDTLS_BSWAP64) #define MBEDTLS_BSWAP64 _byteswap_uint64 #endif #endif /* defined(_MSC_VER) */ /* Detect armcc built-in byteswap routine */ #if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 410000) && !defined(MBEDTLS_BSWAP32) #define MBEDTLS_BSWAP32 __rev #endif /* * Where compiler built-ins are not present, fall back to C code that the * compiler may be able to detect and transform into the relevant bswap or * similar instruction. */ #if !defined(MBEDTLS_BSWAP16) static inline uint16_t mbedtls_bswap16(uint16_t x) { return (x & 0x00ff) << 8 | (x & 0xff00) >> 8; } #define MBEDTLS_BSWAP16 mbedtls_bswap16 #endif /* !defined(MBEDTLS_BSWAP16) */ #if !defined(MBEDTLS_BSWAP32) static inline uint32_t mbedtls_bswap32(uint32_t x) { return (x & 0x000000ff) << 24 | (x & 0x0000ff00) << 8 | (x & 0x00ff0000) >> 8 | (x & 0xff000000) >> 24; } #define MBEDTLS_BSWAP32 mbedtls_bswap32 #endif /* !defined(MBEDTLS_BSWAP32) */ #if !defined(MBEDTLS_BSWAP64) static inline uint64_t mbedtls_bswap64(uint64_t x) { return (x & 0x00000000000000ffULL) << 56 | (x & 0x000000000000ff00ULL) << 40 | (x & 0x0000000000ff0000ULL) << 24 | (x & 0x00000000ff000000ULL) << 8 | (x & 0x000000ff00000000ULL) >> 8 | (x & 0x0000ff0000000000ULL) >> 24 | (x & 0x00ff000000000000ULL) >> 40 | (x & 0xff00000000000000ULL) >> 56; } #define MBEDTLS_BSWAP64 mbedtls_bswap64 #endif /* !defined(MBEDTLS_BSWAP64) */ #if !defined(__BYTE_ORDER__) static const uint16_t mbedtls_byte_order_detector = {0x100}; #define MBEDTLS_IS_BIG_ENDIAN (*((unsigned char*)(&mbedtls_byte_order_detector)) == 0x01) #else #define MBEDTLS_IS_BIG_ENDIAN ((__BYTE_ORDER__) == (__ORDER_BIG_ENDIAN__)) #endif /* !defined(__BYTE_ORDER__) */ /** * Get the unsigned 32 bits integer corresponding to four bytes in * big-endian order (MSB first). * * \param data Base address of the memory to get the four bytes from. * \param offset Offset from \p data of the first and most significant * byte of the four bytes to build the 32 bits unsigned * integer from. */ #define MBEDTLS_GET_UINT32_BE(data, offset) \ ((MBEDTLS_IS_BIG_ENDIAN) ? mbedtls_get_unaligned_uint32((data) + (offset)) \ : MBEDTLS_BSWAP32(mbedtls_get_unaligned_uint32((data) + (offset)))) /** * Put in memory a 32 bits unsigned integer in big-endian order. * * \param n 32 bits unsigned integer to put in memory. * \param data Base address of the memory where to put the 32 * bits unsigned integer in. * \param offset Offset from \p data where to put the most significant * byte of the 32 bits unsigned integer \p n. */ #define MBEDTLS_PUT_UINT32_BE(n, data, offset) \ { \ if (MBEDTLS_IS_BIG_ENDIAN) { \ mbedtls_put_unaligned_uint32((data) + (offset), (uint32_t)(n)); \ } else { \ mbedtls_put_unaligned_uint32((data) + (offset), MBEDTLS_BSWAP32((uint32_t)(n))); \ } \ } /** * Get the unsigned 32 bits integer corresponding to four bytes in * little-endian order (LSB first). * * \param data Base address of the memory to get the four bytes from. * \param offset Offset from \p data of the first and least significant * byte of the four bytes to build the 32 bits unsigned * integer from. */ #define MBEDTLS_GET_UINT32_LE(data, offset) \ ((MBEDTLS_IS_BIG_ENDIAN) ? MBEDTLS_BSWAP32(mbedtls_get_unaligned_uint32((data) + (offset))) \ : mbedtls_get_unaligned_uint32((data) + (offset))) /** * Put in memory a 32 bits unsigned integer in little-endian order. * * \param n 32 bits unsigned integer to put in memory. * \param data Base address of the memory where to put the 32 * bits unsigned integer in. * \param offset Offset from \p data where to put the least significant * byte of the 32 bits unsigned integer \p n. */ #define MBEDTLS_PUT_UINT32_LE(n, data, offset) \ { \ if (MBEDTLS_IS_BIG_ENDIAN) { \ mbedtls_put_unaligned_uint32((data) + (offset), MBEDTLS_BSWAP32((uint32_t)(n))); \ } else { \ mbedtls_put_unaligned_uint32((data) + (offset), ((uint32_t)(n))); \ } \ } /** * Get the unsigned 16 bits integer corresponding to two bytes in * little-endian order (LSB first). * * \param data Base address of the memory to get the two bytes from. * \param offset Offset from \p data of the first and least significant * byte of the two bytes to build the 16 bits unsigned * integer from. */ #define MBEDTLS_GET_UINT16_LE(data, offset) \ ((MBEDTLS_IS_BIG_ENDIAN) ? MBEDTLS_BSWAP16(mbedtls_get_unaligned_uint16((data) + (offset))) \ : mbedtls_get_unaligned_uint16((data) + (offset))) /** * Put in memory a 16 bits unsigned integer in little-endian order. * * \param n 16 bits unsigned integer to put in memory. * \param data Base address of the memory where to put the 16 * bits unsigned integer in. * \param offset Offset from \p data where to put the least significant * byte of the 16 bits unsigned integer \p n. */ #define MBEDTLS_PUT_UINT16_LE(n, data, offset) \ { \ if (MBEDTLS_IS_BIG_ENDIAN) { \ mbedtls_put_unaligned_uint16((data) + (offset), MBEDTLS_BSWAP16((uint16_t)(n))); \ } else { \ mbedtls_put_unaligned_uint16((data) + (offset), (uint16_t)(n)); \ } \ } /** * Get the unsigned 16 bits integer corresponding to two bytes in * big-endian order (MSB first). * * \param data Base address of the memory to get the two bytes from. * \param offset Offset from \p data of the first and most significant * byte of the two bytes to build the 16 bits unsigned * integer from. */ #define MBEDTLS_GET_UINT16_BE(data, offset) \ ((MBEDTLS_IS_BIG_ENDIAN) ? mbedtls_get_unaligned_uint16((data) + (offset)) \ : MBEDTLS_BSWAP16(mbedtls_get_unaligned_uint16((data) + (offset)))) /** * Put in memory a 16 bits unsigned integer in big-endian order. * * \param n 16 bits unsigned integer to put in memory. * \param data Base address of the memory where to put the 16 * bits unsigned integer in. * \param offset Offset from \p data where to put the most significant * byte of the 16 bits unsigned integer \p n. */ #define MBEDTLS_PUT_UINT16_BE(n, data, offset) \ { \ if (MBEDTLS_IS_BIG_ENDIAN) { \ mbedtls_put_unaligned_uint16((data) + (offset), (uint16_t)(n)); \ } else { \ mbedtls_put_unaligned_uint16((data) + (offset), MBEDTLS_BSWAP16((uint16_t)(n))); \ } \ } /** * Get the unsigned 24 bits integer corresponding to three bytes in * big-endian order (MSB first). * * \param data Base address of the memory to get the three bytes from. * \param offset Offset from \p data of the first and most significant * byte of the three bytes to build the 24 bits unsigned * integer from. */ #define MBEDTLS_GET_UINT24_BE(data, offset) \ (((uint32_t)(data)[(offset)] << 16) | ((uint32_t)(data)[(offset) + 1] << 8) | ((uint32_t)(data)[(offset) + 2])) /** * Put in memory a 24 bits unsigned integer in big-endian order. * * \param n 24 bits unsigned integer to put in memory. * \param data Base address of the memory where to put the 24 * bits unsigned integer in. * \param offset Offset from \p data where to put the most significant * byte of the 24 bits unsigned integer \p n. */ #define MBEDTLS_PUT_UINT24_BE(n, data, offset) \ { \ (data)[(offset)] = MBEDTLS_BYTE_2(n); \ (data)[(offset) + 1] = MBEDTLS_BYTE_1(n); \ (data)[(offset) + 2] = MBEDTLS_BYTE_0(n); \ } /** * Get the unsigned 24 bits integer corresponding to three bytes in * little-endian order (LSB first). * * \param data Base address of the memory to get the three bytes from. * \param offset Offset from \p data of the first and least significant * byte of the three bytes to build the 24 bits unsigned * integer from. */ #define MBEDTLS_GET_UINT24_LE(data, offset) \ (((uint32_t)(data)[(offset)]) | ((uint32_t)(data)[(offset) + 1] << 8) | ((uint32_t)(data)[(offset) + 2] << 16)) /** * Put in memory a 24 bits unsigned integer in little-endian order. * * \param n 24 bits unsigned integer to put in memory. * \param data Base address of the memory where to put the 24 * bits unsigned integer in. * \param offset Offset from \p data where to put the least significant * byte of the 24 bits unsigned integer \p n. */ #define MBEDTLS_PUT_UINT24_LE(n, data, offset) \ { \ (data)[(offset)] = MBEDTLS_BYTE_0(n); \ (data)[(offset) + 1] = MBEDTLS_BYTE_1(n); \ (data)[(offset) + 2] = MBEDTLS_BYTE_2(n); \ } /** * Get the unsigned 64 bits integer corresponding to eight bytes in * big-endian order (MSB first). * * \param data Base address of the memory to get the eight bytes from. * \param offset Offset from \p data of the first and most significant * byte of the eight bytes to build the 64 bits unsigned * integer from. */ #define MBEDTLS_GET_UINT64_BE(data, offset) \ ((MBEDTLS_IS_BIG_ENDIAN) ? mbedtls_get_unaligned_uint64((data) + (offset)) \ : MBEDTLS_BSWAP64(mbedtls_get_unaligned_uint64((data) + (offset)))) /** * Put in memory a 64 bits unsigned integer in big-endian order. * * \param n 64 bits unsigned integer to put in memory. * \param data Base address of the memory where to put the 64 * bits unsigned integer in. * \param offset Offset from \p data where to put the most significant * byte of the 64 bits unsigned integer \p n. */ #define MBEDTLS_PUT_UINT64_BE(n, data, offset) \ { \ if (MBEDTLS_IS_BIG_ENDIAN) { \ mbedtls_put_unaligned_uint64((data) + (offset), (uint64_t)(n)); \ } else { \ mbedtls_put_unaligned_uint64((data) + (offset), MBEDTLS_BSWAP64((uint64_t)(n))); \ } \ } /** * Get the unsigned 64 bits integer corresponding to eight bytes in * little-endian order (LSB first). * * \param data Base address of the memory to get the eight bytes from. * \param offset Offset from \p data of the first and least significant * byte of the eight bytes to build the 64 bits unsigned * integer from. */ #define MBEDTLS_GET_UINT64_LE(data, offset) \ ((MBEDTLS_IS_BIG_ENDIAN) ? MBEDTLS_BSWAP64(mbedtls_get_unaligned_uint64((data) + (offset))) \ : mbedtls_get_unaligned_uint64((data) + (offset))) /** * Put in memory a 64 bits unsigned integer in little-endian order. * * \param n 64 bits unsigned integer to put in memory. * \param data Base address of the memory where to put the 64 * bits unsigned integer in. * \param offset Offset from \p data where to put the least significant * byte of the 64 bits unsigned integer \p n. */ #define MBEDTLS_PUT_UINT64_LE(n, data, offset) \ { \ if (MBEDTLS_IS_BIG_ENDIAN) { \ mbedtls_put_unaligned_uint64((data) + (offset), MBEDTLS_BSWAP64((uint64_t)(n))); \ } else { \ mbedtls_put_unaligned_uint64((data) + (offset), (uint64_t)(n)); \ } \ } #endif /* MBEDTLS_LIBRARY_ALIGNMENT_H */ ================================================ FILE: src/aiotieba/helper/crypto/include/mbedtls/common.h ================================================ /** * \file common.h * * \brief Utility macros for internal use in the library */ /* * Copyright The Mbed TLS Contributors * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef MBEDTLS_LIBRARY_COMMON_H #define MBEDTLS_LIBRARY_COMMON_H #include "mbedtls/alignment.h" #include /** Helper to define a function as static except when building invasive tests. * * If a function is only used inside its own source file and should be * declared `static` to allow the compiler to optimize for code size, * but that function has unit tests, define it with * ``` * MBEDTLS_STATIC_TESTABLE int mbedtls_foo(...) { ... } * ``` * and declare it in a header in the `library/` directory with * ``` * #if defined(MBEDTLS_TEST_HOOKS) * int mbedtls_foo(...); * #endif * ``` */ #if defined(MBEDTLS_TEST_HOOKS) #define MBEDTLS_STATIC_TESTABLE #else #define MBEDTLS_STATIC_TESTABLE static #endif #if defined(MBEDTLS_TEST_HOOKS) extern void (*mbedtls_test_hook_test_fail)(const char* test, int line, const char* file); #define MBEDTLS_TEST_HOOK_TEST_ASSERT(TEST) \ do { \ if ((!(TEST)) && ((*mbedtls_test_hook_test_fail) != NULL)) { \ (*mbedtls_test_hook_test_fail)(#TEST, __LINE__, __FILE__); \ } \ } while (0) #else #define MBEDTLS_TEST_HOOK_TEST_ASSERT(TEST) #endif /* defined(MBEDTLS_TEST_HOOKS) */ /** Allow library to access its structs' private members. * * Although structs defined in header files are publicly available, * their members are private and should not be accessed by the user. */ #define MBEDTLS_ALLOW_PRIVATE_ACCESS /** Return an offset into a buffer. * * This is just the addition of an offset to a pointer, except that this * function also accepts an offset of 0 into a buffer whose pointer is null. * (`p + n` has undefined behavior when `p` is null, even when `n == 0`. * A null pointer is a valid buffer pointer when the size is 0, for example * as the result of `malloc(0)` on some platforms.) * * \param p Pointer to a buffer of at least n bytes. * This may be \p NULL if \p n is zero. * \param n An offset in bytes. * \return Pointer to offset \p n in the buffer \p p. * Note that this is only a valid pointer if the size of the * buffer is at least \p n + 1. */ static inline unsigned char* mbedtls_buffer_offset(unsigned char* p, size_t n) { return p == NULL ? NULL : p + n; } /** Return an offset into a read-only buffer. * * Similar to mbedtls_buffer_offset(), but for const pointers. * * \param p Pointer to a buffer of at least n bytes. * This may be \p NULL if \p n is zero. * \param n An offset in bytes. * \return Pointer to offset \p n in the buffer \p p. * Note that this is only a valid pointer if the size of the * buffer is at least \p n + 1. */ static inline const unsigned char* mbedtls_buffer_offset_const(const unsigned char* p, size_t n) { return p == NULL ? NULL : p + n; } /** * Perform a fast block XOR operation, such that * r[i] = a[i] ^ b[i] where 0 <= i < n * * \param r Pointer to result (buffer of at least \p n bytes). \p r * may be equal to either \p a or \p b, but behaviour when * it overlaps in other ways is undefined. * \param a Pointer to input (buffer of at least \p n bytes) * \param b Pointer to input (buffer of at least \p n bytes) * \param n Number of bytes to process. */ inline void mbedtls_xor(unsigned char* r, const unsigned char* a, const unsigned char* b, size_t n) { size_t i = 0; #if defined(MBEDTLS_EFFICIENT_UNALIGNED_ACCESS) for (; (i + 4) <= n; i += 4) { uint32_t x = mbedtls_get_unaligned_uint32(a + i) ^ mbedtls_get_unaligned_uint32(b + i); mbedtls_put_unaligned_uint32(r + i, x); } #endif for (; i < n; i++) { r[i] = a[i] ^ b[i]; } } /* Fix MSVC C99 compatible issue * MSVC support __func__ from visual studio 2015( 1900 ) * Use MSVC predefine macro to avoid name check fail. */ #if (defined(_MSC_VER) && (_MSC_VER <= 1900)) #define /*no-check-names*/ __func__ __FUNCTION__ #endif /* Define `asm` for compilers which don't define it. */ /* *INDENT-OFF* */ #ifndef asm #define asm __asm__ #endif /* *INDENT-ON* */ #endif /* MBEDTLS_LIBRARY_COMMON_H */ ================================================ FILE: src/aiotieba/helper/crypto/include/mbedtls/md5.h ================================================ /** * \file md5.h * * \brief MD5 message digest algorithm (hash function) * * \warning MD5 is considered a weak message digest and its use constitutes a * security risk. We recommend considering stronger message * digests instead. */ /* * Copyright The Mbed TLS Contributors * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef MBEDTLS_MD5_H #define MBEDTLS_MD5_H #include "mbedtls/private_access.h" #include #include #ifdef __cplusplus extern "C" { #endif /** * \brief MD5 context structure * * \warning MD5 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * */ typedef struct mbedtls_md5_context { uint32_t MBEDTLS_PRIVATE(total)[2]; /*!< number of bytes processed */ uint32_t MBEDTLS_PRIVATE(state)[4]; /*!< intermediate digest state */ unsigned char MBEDTLS_PRIVATE(buffer)[64]; /*!< data block being processed */ } mbedtls_md5_context; /** * \brief Initialize MD5 context * * \param ctx MD5 context to be initialized * * \warning MD5 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * */ void mbedtls_md5_init(mbedtls_md5_context* ctx); /** * \brief MD5 context setup * * \param ctx context to be initialized * * \warning MD5 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * */ void mbedtls_md5_starts(mbedtls_md5_context* ctx); /** * \brief MD5 process buffer * * \param ctx MD5 context * \param input buffer holding the data * \param ilen length of the input data * * \warning MD5 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * */ void mbedtls_md5_update(mbedtls_md5_context* ctx, const unsigned char* input, size_t ilen); /** * \brief MD5 final digest * * \param ctx MD5 context * \param output MD5 checksum result * * \warning MD5 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * */ void mbedtls_md5_finish(mbedtls_md5_context* ctx, unsigned char output[16]); /** * \brief MD5 process data block (internal use only) * * \param ctx MD5 context * \param data buffer holding one block of data * * \warning MD5 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * */ void mbedtls_internal_md5_process(mbedtls_md5_context* ctx, const unsigned char data[64]); /** * \brief Output = MD5( input buffer ) * * \param input buffer holding the data * \param ilen length of the input data * \param output MD5 checksum result * * \warning MD5 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * */ void mbedtls_md5(const unsigned char* input, size_t ilen, unsigned char output[16]); #ifdef __cplusplus } #endif #endif /* mbedtls_md5.h */ ================================================ FILE: src/aiotieba/helper/crypto/include/mbedtls/private_access.h ================================================ /** * \file private_access.h * * \brief Macro wrapper for struct's members. */ /* * Copyright The Mbed TLS Contributors * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef MBEDTLS_PRIVATE_ACCESS_H #define MBEDTLS_PRIVATE_ACCESS_H #ifndef MBEDTLS_ALLOW_PRIVATE_ACCESS #define MBEDTLS_PRIVATE(member) private_##member #else #define MBEDTLS_PRIVATE(member) member #endif #endif /* MBEDTLS_PRIVATE_ACCESS_H */ ================================================ FILE: src/aiotieba/helper/crypto/include/mbedtls/sha1.h ================================================ /** * \file sha1.h * * \brief This file contains SHA-1 definitions and functions. * * The Secure Hash Algorithm 1 (SHA-1) cryptographic hash function is defined in * FIPS 180-4: Secure Hash Standard (SHS). * * \warning SHA-1 is considered a weak message digest and its use constitutes * a security risk. We recommend considering stronger message * digests instead. */ /* * Copyright The Mbed TLS Contributors * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef MBEDTLS_SHA1_H #define MBEDTLS_SHA1_H #include "mbedtls/private_access.h" #include #include #ifdef __cplusplus extern "C" { #endif /** * \brief The SHA-1 context structure. * * \warning SHA-1 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * */ typedef struct mbedtls_sha1_context { uint32_t MBEDTLS_PRIVATE(total)[2]; /*!< The number of Bytes processed. */ uint32_t MBEDTLS_PRIVATE(state)[5]; /*!< The intermediate digest state. */ unsigned char MBEDTLS_PRIVATE(buffer)[64]; /*!< The data block being processed. */ } mbedtls_sha1_context; /** * \brief This function initializes a SHA-1 context. * * \warning SHA-1 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * * \param ctx The SHA-1 context to initialize. * This must not be \c NULL. * */ void mbedtls_sha1_init(mbedtls_sha1_context* ctx); /** * \brief This function starts a SHA-1 checksum calculation. * * \warning SHA-1 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * * \param ctx The SHA-1 context to initialize. This must be initialized. * */ void mbedtls_sha1_starts(mbedtls_sha1_context* ctx); /** * \brief This function feeds an input buffer into an ongoing SHA-1 * checksum calculation. * * \warning SHA-1 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * * \param ctx The SHA-1 context. This must be initialized * and have a hash operation started. * \param input The buffer holding the input data. * This must be a readable buffer of length \p ilen Bytes. * \param ilen The length of the input data \p input in Bytes. * */ void mbedtls_sha1_update(mbedtls_sha1_context* ctx, const unsigned char* input, size_t ilen); /** * \brief This function finishes the SHA-1 operation, and writes * the result to the output buffer. * * \warning SHA-1 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * * \param ctx The SHA-1 context to use. This must be initialized and * have a hash operation started. * \param output The SHA-1 checksum result. This must be a writable * buffer of length \c 20 Bytes. */ void mbedtls_sha1_finish(mbedtls_sha1_context* ctx, unsigned char output[20]); /** * \brief SHA-1 process data block (internal use only). * * \warning SHA-1 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * * \param ctx The SHA-1 context to use. This must be initialized. * \param data The data block being processed. This must be a * readable buffer of length \c 64 Bytes. * */ void mbedtls_internal_sha1_process(mbedtls_sha1_context* ctx, const unsigned char data[64]); /** * \brief This function calculates the SHA-1 checksum of a buffer. * * The function allocates the context, performs the * calculation, and frees the context. * * The SHA-1 result is calculated as * output = SHA-1(input buffer). * * \warning SHA-1 is considered a weak message digest and its use * constitutes a security risk. We recommend considering * stronger message digests instead. * * \param input The buffer holding the input data. * This must be a readable buffer of length \p ilen Bytes. * \param ilen The length of the input data \p input in Bytes. * \param output The SHA-1 checksum result. * This must be a writable buffer of length \c 20 Bytes. * */ void mbedtls_sha1(const unsigned char* input, size_t ilen, unsigned char output[20]); #ifdef __cplusplus } #endif #endif /* mbedtls_sha1.h */ ================================================ FILE: src/aiotieba/helper/crypto/include/rapidjson/itoa.h ================================================ // Tencent is pleased to support the open source community by making RapidJSON available. // // Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. // // Licensed under the MIT License (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at // // http://opensource.org/licenses/MIT // // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #pragma once #include static const char itoaTable[200] = { '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9'}; static inline char* u64toa(uint64_t value, char* buffer) { const uint64_t kTen8 = 100000000; const uint64_t kTen9 = kTen8 * 10; const uint64_t kTen10 = kTen8 * 100; const uint64_t kTen11 = kTen8 * 1000; const uint64_t kTen12 = kTen8 * 10000; const uint64_t kTen13 = kTen8 * 100000; const uint64_t kTen14 = kTen8 * 1000000; const uint64_t kTen15 = kTen8 * 10000000; const uint64_t kTen16 = kTen8 * kTen8; if (value < kTen8) { uint32_t v = (uint32_t)value; if (v < 10000) { const uint32_t d1 = (v / 100) << 1; const uint32_t d2 = (v % 100) << 1; if (v >= 1000) *buffer++ = itoaTable[d1]; if (v >= 100) *buffer++ = itoaTable[d1 + 1]; if (v >= 10) *buffer++ = itoaTable[d2]; *buffer++ = itoaTable[d2 + 1]; } else { // value = bbbbcccc const uint32_t b = v / 10000; const uint32_t c = v % 10000; const uint32_t d1 = (b / 100) << 1; const uint32_t d2 = (b % 100) << 1; const uint32_t d3 = (c / 100) << 1; const uint32_t d4 = (c % 100) << 1; if (value >= 10000000) *buffer++ = itoaTable[d1]; if (value >= 1000000) *buffer++ = itoaTable[d1 + 1]; if (value >= 100000) *buffer++ = itoaTable[d2]; *buffer++ = itoaTable[d2 + 1]; *buffer++ = itoaTable[d3]; *buffer++ = itoaTable[d3 + 1]; *buffer++ = itoaTable[d4]; *buffer++ = itoaTable[d4 + 1]; } } else if (value < kTen16) { const uint32_t v0 = (uint32_t)(value / kTen8); const uint32_t v1 = (uint32_t)(value % kTen8); const uint32_t b0 = v0 / 10000; const uint32_t c0 = v0 % 10000; const uint32_t d1 = (b0 / 100) << 1; const uint32_t d2 = (b0 % 100) << 1; const uint32_t d3 = (c0 / 100) << 1; const uint32_t d4 = (c0 % 100) << 1; const uint32_t b1 = v1 / 10000; const uint32_t c1 = v1 % 10000; const uint32_t d5 = (b1 / 100) << 1; const uint32_t d6 = (b1 % 100) << 1; const uint32_t d7 = (c1 / 100) << 1; const uint32_t d8 = (c1 % 100) << 1; if (value >= kTen15) *buffer++ = itoaTable[d1]; if (value >= kTen14) *buffer++ = itoaTable[d1 + 1]; if (value >= kTen13) *buffer++ = itoaTable[d2]; if (value >= kTen12) *buffer++ = itoaTable[d2 + 1]; if (value >= kTen11) *buffer++ = itoaTable[d3]; if (value >= kTen10) *buffer++ = itoaTable[d3 + 1]; if (value >= kTen9) *buffer++ = itoaTable[d4]; *buffer++ = itoaTable[d4 + 1]; *buffer++ = itoaTable[d5]; *buffer++ = itoaTable[d5 + 1]; *buffer++ = itoaTable[d6]; *buffer++ = itoaTable[d6 + 1]; *buffer++ = itoaTable[d7]; *buffer++ = itoaTable[d7 + 1]; *buffer++ = itoaTable[d8]; *buffer++ = itoaTable[d8 + 1]; } else { const uint32_t a = (uint32_t)(value / kTen16); // 1 to 1844 value %= kTen16; if (a < 10) *buffer++ = (char)('0' + (char)(a)); else if (a < 100) { const uint32_t i = a << 1; *buffer++ = itoaTable[i]; *buffer++ = itoaTable[i + 1]; } else if (a < 1000) { *buffer++ = (char)('0' + (char)(a / 100)); const uint32_t i = (a % 100) << 1; *buffer++ = itoaTable[i]; *buffer++ = itoaTable[i + 1]; } else { const uint32_t i = (a / 100) << 1; const uint32_t j = (a % 100) << 1; *buffer++ = itoaTable[i]; *buffer++ = itoaTable[i + 1]; *buffer++ = itoaTable[j]; *buffer++ = itoaTable[j + 1]; } const uint32_t v0 = (uint32_t)(value / kTen8); const uint32_t v1 = (uint32_t)(value % kTen8); const uint32_t b0 = v0 / 10000; const uint32_t c0 = v0 % 10000; const uint32_t d1 = (b0 / 100) << 1; const uint32_t d2 = (b0 % 100) << 1; const uint32_t d3 = (c0 / 100) << 1; const uint32_t d4 = (c0 % 100) << 1; const uint32_t b1 = v1 / 10000; const uint32_t c1 = v1 % 10000; const uint32_t d5 = (b1 / 100) << 1; const uint32_t d6 = (b1 % 100) << 1; const uint32_t d7 = (c1 / 100) << 1; const uint32_t d8 = (c1 % 100) << 1; *buffer++ = itoaTable[d1]; *buffer++ = itoaTable[d1 + 1]; *buffer++ = itoaTable[d2]; *buffer++ = itoaTable[d2 + 1]; *buffer++ = itoaTable[d3]; *buffer++ = itoaTable[d3 + 1]; *buffer++ = itoaTable[d4]; *buffer++ = itoaTable[d4 + 1]; *buffer++ = itoaTable[d5]; *buffer++ = itoaTable[d5 + 1]; *buffer++ = itoaTable[d6]; *buffer++ = itoaTable[d6 + 1]; *buffer++ = itoaTable[d7]; *buffer++ = itoaTable[d7 + 1]; *buffer++ = itoaTable[d8]; *buffer++ = itoaTable[d8 + 1]; } return buffer; } char* i64toa(int64_t value, char* buffer) { uint64_t u = (uint64_t)value; if (value < 0) { *buffer++ = '-'; u = ~u + 1; } return u64toa(u, buffer); } ================================================ FILE: src/aiotieba/helper/crypto/include/tbcrypto/bb64.h ================================================ #pragma once void tbc_BB64Encode(const unsigned char *inputArray, int srcLen, int mode, unsigned char *dst); ================================================ FILE: src/aiotieba/helper/crypto/include/tbcrypto/const.h ================================================ #pragma once #include "base32/base32.h" #define TBC_UUID_SIZE 36 #define TBC_ANDROID_ID_SIZE 16 #define TBC_MD5_HASH_SIZE 16 #define TBC_MD5_STR_SIZE (TBC_MD5_HASH_SIZE * 2) #define TBC_SHA1_HASH_SIZE 20 #define TBC_SHA1_HEX_SIZE (TBC_SHA1_HASH_SIZE * 2) #define TBC_SHA1_BASE32_SIZE (BASE32_LEN(TBC_SHA1_HASH_SIZE)) #define TBC_HELIOS_HASH_SIZE 5 #define TBC_HELIOS_BASE32_SIZE (BASE32_LEN(TBC_HELIOS_HASH_SIZE)) #define TBC_CUID_GALAXY2_SIZE (TBC_MD5_STR_SIZE + 2 + TBC_HELIOS_BASE32_SIZE) #define TBC_C3_AID_SIZE (4 + TBC_SHA1_BASE32_SIZE + 1 + TBC_HELIOS_BASE32_SIZE) #define TBC_ENUID_SIZE (4 * ((TBC_CUID_GALAXY2_SIZE + 2) / 3) + 1) static const unsigned char HEX_UPPERCASE_TABLE[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; static const unsigned char HEX_LOWERCASE_TABLE[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; ================================================ FILE: src/aiotieba/helper/crypto/include/tbcrypto/cuid.h ================================================ #pragma once #include /** * @brief impl of TiebaLite tieba/post/utils/helios * * @param src alloc and free by user * @param srcSize size of src. must >= 1 * @param dst 5 bytes. alloc and free by user * * @note 12.x loc: com.baidu.tieba.l40.a / com.baidu.tieba.pz.a */ void tbc_heliosHash(const unsigned char* src, size_t srcSize, unsigned char* dst); /** * @brief generate `cuid_galaxy2` * * @param androidID 16 bytes. alloc and free by user * @param dst 42 bytes. alloc and free by user * * @note 12.x loc: com.baidu.tieba.oz.m */ void tbc_cuid_galaxy2(const unsigned char* androidID, unsigned char* dst); /** * @brief generate `c3_aid` * * @param androidID 16 bytes. alloc and free by user * @param uuid 36 bytes. alloc and free by user * @param dst 45 bytes. alloc and free by user * * @note 12.x loc: com.baidu.tieba.r50.f */ void tbc_c3_aid(const unsigned char* androidID, const unsigned char* uuid, unsigned char* dst); ================================================ FILE: src/aiotieba/helper/crypto/include/tbcrypto/error.h ================================================ #pragma once #define TBC_OK 0x0000 #define TBC_MEMORY_ERROR 0x0001 ================================================ FILE: src/aiotieba/helper/crypto/include/tbcrypto/pywrap.h ================================================ #pragma once #define PY_SSIZE_T_CLEAN // use Py_ssize_t instead of int #ifdef TBC_PYTHON_DEBUG # include #else # ifdef _DEBUG # undef _DEBUG // use these steps to avoid linking with python_d.lib # define __TBC_RESTORE_DEBUG # endif # include # ifdef __TBC_RESTORE_DEBUG # define _DEBUG # undef __TBC_RESTORE_DEBUG # endif #endif ================================================ FILE: src/aiotieba/helper/crypto/include/tbcrypto/rc442.h ================================================ #pragma once #define TBC_CBC_SECKEY_SIZE 16 #define TBC_RC4_SIZE 16 /** * @brief RC4 includes an extra XOR against 42 * * @param xyusMd5Str 32 bytes. alloc and free by user * @param cbcSecKey 16 bytes. alloc and free by user * @param dst 16 bytes. alloc and free by user * * @return non 0 if any error * * @note 12.x loc: com.baidu.sofire.x6.oCOCcooCCoC.ocOOCCoOOCcC.CcooOoocOOo */ void tbc_rc4_42(const unsigned char* xyusMd5Str, const unsigned char* cbcSecKey, unsigned char* dst); ================================================ FILE: src/aiotieba/helper/crypto/include/tbcrypto/sign.h ================================================ #pragma once #include "tbcrypto/pywrap.h" PyObject* sign(PyObject* Py_UNUSED(self), PyObject* args); ================================================ FILE: src/aiotieba/helper/crypto/include/xxHash/xxhash.h ================================================ /* * xxHash - Extremely Fast Hash algorithm * Header File * Copyright (C) 2012-2023 Yann Collet * * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * You can contact the author at: * - xxHash homepage: https://www.xxhash.com * - xxHash source repository: https://github.com/Cyan4973/xxHash */ /*! * @mainpage xxHash * * xxHash is an extremely fast non-cryptographic hash algorithm, working at RAM speed * limits. * * It is proposed in four flavors, in three families: * 1. @ref XXH32_family * - Classic 32-bit hash function. Simple, compact, and runs on almost all * 32-bit and 64-bit systems. * 2. @ref XXH64_family * - Classic 64-bit adaptation of XXH32. Just as simple, and runs well on most * 64-bit systems (but _not_ 32-bit systems). * 3. @ref XXH3_family * - Modern 64-bit and 128-bit hash function family which features improved * strength and performance across the board, especially on smaller data. * It benefits greatly from SIMD and 64-bit without requiring it. * * Benchmarks * --- * The reference system uses an Intel i7-9700K CPU, and runs Ubuntu x64 20.04. * The open source benchmark program is compiled with clang v10.0 using -O3 flag. * * | Hash Name | ISA ext | Width | Large Data Speed | Small Data Velocity | * | -------------------- | ------- | ----: | ---------------: | ------------------: | * | XXH3_64bits() | @b AVX2 | 64 | 59.4 GB/s | 133.1 | * | MeowHash | AES-NI | 128 | 58.2 GB/s | 52.5 | * | XXH3_128bits() | @b AVX2 | 128 | 57.9 GB/s | 118.1 | * | CLHash | PCLMUL | 64 | 37.1 GB/s | 58.1 | * | XXH3_64bits() | @b SSE2 | 64 | 31.5 GB/s | 133.1 | * | XXH3_128bits() | @b SSE2 | 128 | 29.6 GB/s | 118.1 | * | RAM sequential read | | N/A | 28.0 GB/s | N/A | * | ahash | AES-NI | 64 | 22.5 GB/s | 107.2 | * | City64 | | 64 | 22.0 GB/s | 76.6 | * | T1ha2 | | 64 | 22.0 GB/s | 99.0 | * | City128 | | 128 | 21.7 GB/s | 57.7 | * | FarmHash | AES-NI | 64 | 21.3 GB/s | 71.9 | * | XXH64() | | 64 | 19.4 GB/s | 71.0 | * | SpookyHash | | 64 | 19.3 GB/s | 53.2 | * | Mum | | 64 | 18.0 GB/s | 67.0 | * | CRC32C | SSE4.2 | 32 | 13.0 GB/s | 57.9 | * | XXH32() | | 32 | 9.7 GB/s | 71.9 | * | City32 | | 32 | 9.1 GB/s | 66.0 | * | Blake3* | @b AVX2 | 256 | 4.4 GB/s | 8.1 | * | Murmur3 | | 32 | 3.9 GB/s | 56.1 | * | SipHash* | | 64 | 3.0 GB/s | 43.2 | * | Blake3* | @b SSE2 | 256 | 2.4 GB/s | 8.1 | * | HighwayHash | | 64 | 1.4 GB/s | 6.0 | * | FNV64 | | 64 | 1.2 GB/s | 62.7 | * | Blake2* | | 256 | 1.1 GB/s | 5.1 | * | SHA1* | | 160 | 0.8 GB/s | 5.6 | * | MD5* | | 128 | 0.6 GB/s | 7.8 | * @note * - Hashes which require a specific ISA extension are noted. SSE2 is also noted, * even though it is mandatory on x64. * - Hashes with an asterisk are cryptographic. Note that MD5 is non-cryptographic * by modern standards. * - Small data velocity is a rough average of algorithm's efficiency for small * data. For more accurate information, see the wiki. * - More benchmarks and strength tests are found on the wiki: * https://github.com/Cyan4973/xxHash/wiki * * Usage * ------ * All xxHash variants use a similar API. Changing the algorithm is a trivial * substitution. * * @pre * For functions which take an input and length parameter, the following * requirements are assumed: * - The range from [`input`, `input + length`) is valid, readable memory. * - The only exception is if the `length` is `0`, `input` may be `NULL`. * - For C++, the objects must have the *TriviallyCopyable* property, as the * functions access bytes directly as if it was an array of `unsigned char`. * * @anchor single_shot_example * **Single Shot** * * These functions are stateless functions which hash a contiguous block of memory, * immediately returning the result. They are the easiest and usually the fastest * option. * * XXH32(), XXH64(), XXH3_64bits(), XXH3_128bits() * * @code{.c} * #include * #include "xxhash.h" * * // Example for a function which hashes a null terminated string with XXH32(). * XXH32_hash_t hash_string(const char* string, XXH32_hash_t seed) * { * // NULL pointers are only valid if the length is zero * size_t length = (string == NULL) ? 0 : strlen(string); * return XXH32(string, length, seed); * } * @endcode * * * @anchor streaming_example * **Streaming** * * These groups of functions allow incremental hashing of unknown size, even * more than what would fit in a size_t. * * XXH32_reset(), XXH64_reset(), XXH3_64bits_reset(), XXH3_128bits_reset() * * @code{.c} * #include * #include * #include "xxhash.h" * // Example for a function which hashes a FILE incrementally with XXH3_64bits(). * XXH64_hash_t hashFile(FILE* f) * { * // Allocate a state struct. Do not just use malloc() or new. * XXH3_state_t* state = XXH3_createState(); * assert(state != NULL && "Out of memory!"); * // Reset the state to start a new hashing session. * XXH3_64bits_reset(state); * char buffer[4096]; * size_t count; * // Read the file in chunks * while ((count = fread(buffer, 1, sizeof(buffer), f)) != 0) { * // Run update() as many times as necessary to process the data * XXH3_64bits_update(state, buffer, count); * } * // Retrieve the finalized hash. This will not change the state. * XXH64_hash_t result = XXH3_64bits_digest(state); * // Free the state. Do not use free(). * XXH3_freeState(state); * return result; * } * @endcode * * Streaming functions generate the xxHash value from an incremental input. * This method is slower than single-call functions, due to state management. * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized. * * An XXH state must first be allocated using `XXH*_createState()`. * * Start a new hash by initializing the state with a seed using `XXH*_reset()`. * * Then, feed the hash state by calling `XXH*_update()` as many times as necessary. * * The function returns an error code, with 0 meaning OK, and any other value * meaning there is an error. * * Finally, a hash value can be produced anytime, by using `XXH*_digest()`. * This function returns the nn-bits hash as an int or long long. * * It's still possible to continue inserting input into the hash state after a * digest, and generate new hash values later on by invoking `XXH*_digest()`. * * When done, release the state using `XXH*_freeState()`. * * * @anchor canonical_representation_example * **Canonical Representation** * * The default return values from XXH functions are unsigned 32, 64 and 128 bit * integers. * This the simplest and fastest format for further post-processing. * * However, this leaves open the question of what is the order on the byte level, * since little and big endian conventions will store the same number differently. * * The canonical representation settles this issue by mandating big-endian * convention, the same convention as human-readable numbers (large digits first). * * When writing hash values to storage, sending them over a network, or printing * them, it's highly recommended to use the canonical representation to ensure * portability across a wider range of systems, present and future. * * The following functions allow transformation of hash values to and from * canonical format. * * XXH32_canonicalFromHash(), XXH32_hashFromCanonical(), * XXH64_canonicalFromHash(), XXH64_hashFromCanonical(), * XXH128_canonicalFromHash(), XXH128_hashFromCanonical(), * * @code{.c} * #include * #include "xxhash.h" * * // Example for a function which prints XXH32_hash_t in human readable format * void printXxh32(XXH32_hash_t hash) * { * XXH32_canonical_t cano; * XXH32_canonicalFromHash(&cano, hash); * size_t i; * for(i = 0; i < sizeof(cano.digest); ++i) { * printf("%02x", cano.digest[i]); * } * printf("\n"); * } * * // Example for a function which converts XXH32_canonical_t to XXH32_hash_t * XXH32_hash_t convertCanonicalToXxh32(XXH32_canonical_t cano) * { * XXH32_hash_t hash = XXH32_hashFromCanonical(&cano); * return hash; * } * @endcode * * * @file xxhash.h * xxHash prototypes and implementation */ #if defined(__cplusplus) && !defined(XXH_NO_EXTERNC_GUARD) extern "C" { #endif /* **************************** * INLINE mode ******************************/ /*! * @defgroup public Public API * Contains details on the public xxHash functions. * @{ */ #ifdef XXH_DOXYGEN /*! * @brief Gives access to internal state declaration, required for static allocation. * * Incompatible with dynamic linking, due to risks of ABI changes. * * Usage: * @code{.c} * #define XXH_STATIC_LINKING_ONLY * #include "xxhash.h" * @endcode */ # define XXH_STATIC_LINKING_ONLY /* Do not undef XXH_STATIC_LINKING_ONLY for Doxygen */ /*! * @brief Gives access to internal definitions. * * Usage: * @code{.c} * #define XXH_STATIC_LINKING_ONLY * #define XXH_IMPLEMENTATION * #include "xxhash.h" * @endcode */ # define XXH_IMPLEMENTATION /* Do not undef XXH_IMPLEMENTATION for Doxygen */ /*! * @brief Exposes the implementation and marks all functions as `inline`. * * Use these build macros to inline xxhash into the target unit. * Inlining improves performance on small inputs, especially when the length is * expressed as a compile-time constant: * * https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html * * It also keeps xxHash symbols private to the unit, so they are not exported. * * Usage: * @code{.c} * #define XXH_INLINE_ALL * #include "xxhash.h" * @endcode * Do not compile and link xxhash.o as a separate object, as it is not useful. */ # define XXH_INLINE_ALL # undef XXH_INLINE_ALL /*! * @brief Exposes the implementation without marking functions as inline. */ # define XXH_PRIVATE_API # undef XXH_PRIVATE_API /*! * @brief Emulate a namespace by transparently prefixing all symbols. * * If you want to include _and expose_ xxHash functions from within your own * library, but also want to avoid symbol collisions with other libraries which * may also include xxHash, you can use @ref XXH_NAMESPACE to automatically prefix * any public symbol from xxhash library with the value of @ref XXH_NAMESPACE * (therefore, avoid empty or numeric values). * * Note that no change is required within the calling program as long as it * includes `xxhash.h`: Regular symbol names will be automatically translated * by this header. */ # define XXH_NAMESPACE /* YOUR NAME HERE */ # undef XXH_NAMESPACE #endif #define XXH_CAT(A,B) A##B #define XXH_NAME2(A,B) XXH_CAT(A,B) #define XXH_IPREF(Id) XXH_NAME2(XXH_NAMESPACE, Id) #if (defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)) \ && !defined(XXH_INLINE_ALL_31684351384) /* this section should be traversed only once */ # define XXH_INLINE_ALL_31684351384 /* give access to the advanced API, required to compile implementations */ # undef XXH_STATIC_LINKING_ONLY /* avoid macro redef */ # define XXH_STATIC_LINKING_ONLY /* make all functions private */ # undef XXH_PUBLIC_API # if defined(__GNUC__) # define XXH_PUBLIC_API static __inline __attribute__((__unused__)) # elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) # define XXH_PUBLIC_API static inline # elif defined(_MSC_VER) # define XXH_PUBLIC_API static __inline # else /* note: this version may generate warnings for unused static functions */ # define XXH_PUBLIC_API static # endif /* * This part deals with the special case where a unit wants to inline xxHash, * but "xxhash.h" has previously been included without XXH_INLINE_ALL, * such as part of some previously included *.h header file. * Without further action, the new include would just be ignored, * and functions would effectively _not_ be inlined (silent failure). * The following macros solve this situation by prefixing all inlined names, * avoiding naming collision with previous inclusions. */ /* Before that, we unconditionally #undef all symbols, * in case they were already defined with XXH_NAMESPACE. * They will then be redefined for XXH_INLINE_ALL */ # undef XXH_versionNumber /* XXH32 */ # undef XXH32 # undef XXH32_createState # undef XXH32_freeState # undef XXH32_reset # undef XXH32_update # undef XXH32_digest # undef XXH32_copyState # undef XXH32_canonicalFromHash # undef XXH32_hashFromCanonical /* XXH64 */ # undef XXH64 # undef XXH64_createState # undef XXH64_freeState # undef XXH64_reset # undef XXH64_update # undef XXH64_digest # undef XXH64_copyState # undef XXH64_canonicalFromHash # undef XXH64_hashFromCanonical /* XXH3_64bits */ # undef XXH3_64bits # undef XXH3_64bits_withSecret # undef XXH3_64bits_withSeed # undef XXH3_64bits_withSecretandSeed # undef XXH3_createState # undef XXH3_freeState # undef XXH3_copyState # undef XXH3_64bits_reset # undef XXH3_64bits_reset_withSeed # undef XXH3_64bits_reset_withSecret # undef XXH3_64bits_update # undef XXH3_64bits_digest # undef XXH3_generateSecret /* XXH3_128bits */ # undef XXH128 # undef XXH3_128bits # undef XXH3_128bits_withSeed # undef XXH3_128bits_withSecret # undef XXH3_128bits_reset # undef XXH3_128bits_reset_withSeed # undef XXH3_128bits_reset_withSecret # undef XXH3_128bits_reset_withSecretandSeed # undef XXH3_128bits_update # undef XXH3_128bits_digest # undef XXH128_isEqual # undef XXH128_cmp # undef XXH128_canonicalFromHash # undef XXH128_hashFromCanonical /* Finally, free the namespace itself */ # undef XXH_NAMESPACE /* employ the namespace for XXH_INLINE_ALL */ # define XXH_NAMESPACE XXH_INLINE_ /* * Some identifiers (enums, type names) are not symbols, * but they must nonetheless be renamed to avoid redeclaration. * Alternative solution: do not redeclare them. * However, this requires some #ifdefs, and has a more dispersed impact. * Meanwhile, renaming can be achieved in a single place. */ # define XXH_OK XXH_IPREF(XXH_OK) # define XXH_ERROR XXH_IPREF(XXH_ERROR) # define XXH_errorcode XXH_IPREF(XXH_errorcode) # define XXH32_canonical_t XXH_IPREF(XXH32_canonical_t) # define XXH64_canonical_t XXH_IPREF(XXH64_canonical_t) # define XXH128_canonical_t XXH_IPREF(XXH128_canonical_t) # define XXH32_state_s XXH_IPREF(XXH32_state_s) # define XXH32_state_t XXH_IPREF(XXH32_state_t) # define XXH64_state_s XXH_IPREF(XXH64_state_s) # define XXH64_state_t XXH_IPREF(XXH64_state_t) # define XXH3_state_s XXH_IPREF(XXH3_state_s) # define XXH3_state_t XXH_IPREF(XXH3_state_t) # define XXH128_hash_t XXH_IPREF(XXH128_hash_t) /* Ensure the header is parsed again, even if it was previously included */ # undef XXHASH_H_5627135585666179 # undef XXHASH_H_STATIC_13879238742 #endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */ /* **************************************************************** * Stable API *****************************************************************/ #ifndef XXHASH_H_5627135585666179 #define XXHASH_H_5627135585666179 1 /*! @brief Marks a global symbol. */ #if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API) # if defined(_WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT)) # ifdef XXH_EXPORT # define XXH_PUBLIC_API __declspec(dllexport) # elif XXH_IMPORT # define XXH_PUBLIC_API __declspec(dllimport) # endif # else # define XXH_PUBLIC_API /* do nothing */ # endif #endif #ifdef XXH_NAMESPACE # define XXH_versionNumber XXH_IPREF(XXH_versionNumber) /* XXH32 */ # define XXH32 XXH_IPREF(XXH32) # define XXH32_createState XXH_IPREF(XXH32_createState) # define XXH32_freeState XXH_IPREF(XXH32_freeState) # define XXH32_reset XXH_IPREF(XXH32_reset) # define XXH32_update XXH_IPREF(XXH32_update) # define XXH32_digest XXH_IPREF(XXH32_digest) # define XXH32_copyState XXH_IPREF(XXH32_copyState) # define XXH32_canonicalFromHash XXH_IPREF(XXH32_canonicalFromHash) # define XXH32_hashFromCanonical XXH_IPREF(XXH32_hashFromCanonical) /* XXH64 */ # define XXH64 XXH_IPREF(XXH64) # define XXH64_createState XXH_IPREF(XXH64_createState) # define XXH64_freeState XXH_IPREF(XXH64_freeState) # define XXH64_reset XXH_IPREF(XXH64_reset) # define XXH64_update XXH_IPREF(XXH64_update) # define XXH64_digest XXH_IPREF(XXH64_digest) # define XXH64_copyState XXH_IPREF(XXH64_copyState) # define XXH64_canonicalFromHash XXH_IPREF(XXH64_canonicalFromHash) # define XXH64_hashFromCanonical XXH_IPREF(XXH64_hashFromCanonical) /* XXH3_64bits */ # define XXH3_64bits XXH_IPREF(XXH3_64bits) # define XXH3_64bits_withSecret XXH_IPREF(XXH3_64bits_withSecret) # define XXH3_64bits_withSeed XXH_IPREF(XXH3_64bits_withSeed) # define XXH3_64bits_withSecretandSeed XXH_IPREF(XXH3_64bits_withSecretandSeed) # define XXH3_createState XXH_IPREF(XXH3_createState) # define XXH3_freeState XXH_IPREF(XXH3_freeState) # define XXH3_copyState XXH_IPREF(XXH3_copyState) # define XXH3_64bits_reset XXH_IPREF(XXH3_64bits_reset) # define XXH3_64bits_reset_withSeed XXH_IPREF(XXH3_64bits_reset_withSeed) # define XXH3_64bits_reset_withSecret XXH_IPREF(XXH3_64bits_reset_withSecret) # define XXH3_64bits_reset_withSecretandSeed XXH_IPREF(XXH3_64bits_reset_withSecretandSeed) # define XXH3_64bits_update XXH_IPREF(XXH3_64bits_update) # define XXH3_64bits_digest XXH_IPREF(XXH3_64bits_digest) # define XXH3_generateSecret XXH_IPREF(XXH3_generateSecret) # define XXH3_generateSecret_fromSeed XXH_IPREF(XXH3_generateSecret_fromSeed) /* XXH3_128bits */ # define XXH128 XXH_IPREF(XXH128) # define XXH3_128bits XXH_IPREF(XXH3_128bits) # define XXH3_128bits_withSeed XXH_IPREF(XXH3_128bits_withSeed) # define XXH3_128bits_withSecret XXH_IPREF(XXH3_128bits_withSecret) # define XXH3_128bits_withSecretandSeed XXH_IPREF(XXH3_128bits_withSecretandSeed) # define XXH3_128bits_reset XXH_IPREF(XXH3_128bits_reset) # define XXH3_128bits_reset_withSeed XXH_IPREF(XXH3_128bits_reset_withSeed) # define XXH3_128bits_reset_withSecret XXH_IPREF(XXH3_128bits_reset_withSecret) # define XXH3_128bits_reset_withSecretandSeed XXH_IPREF(XXH3_128bits_reset_withSecretandSeed) # define XXH3_128bits_update XXH_IPREF(XXH3_128bits_update) # define XXH3_128bits_digest XXH_IPREF(XXH3_128bits_digest) # define XXH128_isEqual XXH_IPREF(XXH128_isEqual) # define XXH128_cmp XXH_IPREF(XXH128_cmp) # define XXH128_canonicalFromHash XXH_IPREF(XXH128_canonicalFromHash) # define XXH128_hashFromCanonical XXH_IPREF(XXH128_hashFromCanonical) #endif /* ************************************* * Compiler specifics ***************************************/ /* specific declaration modes for Windows */ #if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API) # if defined(_WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT)) # ifdef XXH_EXPORT # define XXH_PUBLIC_API __declspec(dllexport) # elif XXH_IMPORT # define XXH_PUBLIC_API __declspec(dllimport) # endif # else # define XXH_PUBLIC_API /* do nothing */ # endif #endif #if defined (__GNUC__) # define XXH_CONSTF __attribute__((__const__)) # define XXH_PUREF __attribute__((__pure__)) # define XXH_MALLOCF __attribute__((__malloc__)) #else # define XXH_CONSTF /* disable */ # define XXH_PUREF # define XXH_MALLOCF #endif /* ************************************* * Version ***************************************/ #define XXH_VERSION_MAJOR 0 #define XXH_VERSION_MINOR 8 #define XXH_VERSION_RELEASE 3 /*! @brief Version number, encoded as two digits each */ #define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE) /*! * @brief Obtains the xxHash version. * * This is mostly useful when xxHash is compiled as a shared library, * since the returned value comes from the library, as opposed to header file. * * @return @ref XXH_VERSION_NUMBER of the invoked library. */ XXH_PUBLIC_API XXH_CONSTF unsigned XXH_versionNumber (void); /* **************************** * Common basic types ******************************/ #include /* size_t */ /*! * @brief Exit code for the streaming API. */ typedef enum { XXH_OK = 0, /*!< OK */ XXH_ERROR /*!< Error */ } XXH_errorcode; /*-********************************************************************** * 32-bit hash ************************************************************************/ #if defined(XXH_DOXYGEN) /* Don't show include */ /*! * @brief An unsigned 32-bit integer. * * Not necessarily defined to `uint32_t` but functionally equivalent. */ typedef uint32_t XXH32_hash_t; #elif !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) # ifdef _AIX # include # else # include # endif typedef uint32_t XXH32_hash_t; #else # include # if UINT_MAX == 0xFFFFFFFFUL typedef unsigned int XXH32_hash_t; # elif ULONG_MAX == 0xFFFFFFFFUL typedef unsigned long XXH32_hash_t; # else # error "unsupported platform: need a 32-bit type" # endif #endif /*! * @} * * @defgroup XXH32_family XXH32 family * @ingroup public * Contains functions used in the classic 32-bit xxHash algorithm. * * @note * XXH32 is useful for older platforms, with no or poor 64-bit performance. * Note that the @ref XXH3_family provides competitive speed for both 32-bit * and 64-bit systems, and offers true 64/128 bit hash results. * * @see @ref XXH64_family, @ref XXH3_family : Other xxHash families * @see @ref XXH32_impl for implementation details * @{ */ /*! * @brief Calculates the 32-bit hash of @p input using xxHash32. * * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * @param seed The 32-bit seed to alter the hash's output predictably. * * @pre * The memory between @p input and @p input + @p length must be valid, * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * * @return The calculated 32-bit xxHash32 value. * * @see @ref single_shot_example "Single Shot Example" for an example. */ XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed); #ifndef XXH_NO_STREAM /*! * @typedef struct XXH32_state_s XXH32_state_t * @brief The opaque state struct for the XXH32 streaming API. * * @see XXH32_state_s for details. * @see @ref streaming_example "Streaming Example" */ typedef struct XXH32_state_s XXH32_state_t; /*! * @brief Allocates an @ref XXH32_state_t. * * @return An allocated pointer of @ref XXH32_state_t on success. * @return `NULL` on failure. * * @note Must be freed with XXH32_freeState(). * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_MALLOCF XXH32_state_t* XXH32_createState(void); /*! * @brief Frees an @ref XXH32_state_t. * * @param statePtr A pointer to an @ref XXH32_state_t allocated with @ref XXH32_createState(). * * @return @ref XXH_OK. * * @note @p statePtr must be allocated with XXH32_createState(). * * @see @ref streaming_example "Streaming Example" * */ XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr); /*! * @brief Copies one @ref XXH32_state_t to another. * * @param dst_state The state to copy to. * @param src_state The state to copy from. * @pre * @p dst_state and @p src_state must not be `NULL` and must not overlap. */ XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state); /*! * @brief Resets an @ref XXH32_state_t to begin a new hash. * * @param statePtr The state struct to reset. * @param seed The 32-bit seed to alter the hash result predictably. * * @pre * @p statePtr must not be `NULL`. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note This function resets and seeds a state. Call it before @ref XXH32_update(). * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, XXH32_hash_t seed); /*! * @brief Consumes a block of @p input to an @ref XXH32_state_t. * * @param statePtr The state struct to update. * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * * @pre * @p statePtr must not be `NULL`. * @pre * The memory between @p input and @p input + @p length must be valid, * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note Call this to incrementally consume blocks of data. * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length); /*! * @brief Returns the calculated hash value from an @ref XXH32_state_t. * * @param statePtr The state struct to calculate the hash from. * * @pre * @p statePtr must not be `NULL`. * * @return The calculated 32-bit xxHash32 value from that state. * * @note * Calling XXH32_digest() will not affect @p statePtr, so you can update, * digest, and update again. * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr); #endif /* !XXH_NO_STREAM */ /******* Canonical representation *******/ /*! * @brief Canonical (big endian) representation of @ref XXH32_hash_t. */ typedef struct { unsigned char digest[4]; /*!< Hash bytes, big endian */ } XXH32_canonical_t; /*! * @brief Converts an @ref XXH32_hash_t to a big endian @ref XXH32_canonical_t. * * @param dst The @ref XXH32_canonical_t pointer to be stored to. * @param hash The @ref XXH32_hash_t to be converted. * * @pre * @p dst must not be `NULL`. * * @see @ref canonical_representation_example "Canonical Representation Example" */ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash); /*! * @brief Converts an @ref XXH32_canonical_t to a native @ref XXH32_hash_t. * * @param src The @ref XXH32_canonical_t to convert. * * @pre * @p src must not be `NULL`. * * @return The converted hash. * * @see @ref canonical_representation_example "Canonical Representation Example" */ XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src); /*! @cond Doxygen ignores this part */ #ifdef __has_attribute # define XXH_HAS_ATTRIBUTE(x) __has_attribute(x) #else # define XXH_HAS_ATTRIBUTE(x) 0 #endif /*! @endcond */ /*! @cond Doxygen ignores this part */ /* C-language Attributes are added in C23. */ #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202311L) && defined(__has_c_attribute) # define XXH_HAS_C_ATTRIBUTE(x) __has_c_attribute(x) #else # define XXH_HAS_C_ATTRIBUTE(x) 0 #endif /*! @endcond */ /*! @cond Doxygen ignores this part */ #if defined(__cplusplus) && defined(__has_cpp_attribute) # define XXH_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) #else # define XXH_HAS_CPP_ATTRIBUTE(x) 0 #endif /*! @endcond */ /*! @cond Doxygen ignores this part */ /* * Define XXH_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute * introduced in CPP17 and C23. * CPP17 : https://en.cppreference.com/w/cpp/language/attributes/fallthrough * C23 : https://en.cppreference.com/w/c/language/attributes/fallthrough */ #if XXH_HAS_C_ATTRIBUTE(fallthrough) || XXH_HAS_CPP_ATTRIBUTE(fallthrough) # define XXH_FALLTHROUGH [[fallthrough]] #elif XXH_HAS_ATTRIBUTE(__fallthrough__) # define XXH_FALLTHROUGH __attribute__ ((__fallthrough__)) #else # define XXH_FALLTHROUGH /* fallthrough */ #endif /*! @endcond */ /*! @cond Doxygen ignores this part */ /* * Define XXH_NOESCAPE for annotated pointers in public API. * https://clang.llvm.org/docs/AttributeReference.html#noescape * As of writing this, only supported by clang. */ #if XXH_HAS_ATTRIBUTE(noescape) # define XXH_NOESCAPE __attribute__((__noescape__)) #else # define XXH_NOESCAPE #endif /*! @endcond */ /*! * @} * @ingroup public * @{ */ #ifndef XXH_NO_LONG_LONG /*-********************************************************************** * 64-bit hash ************************************************************************/ #if defined(XXH_DOXYGEN) /* don't include */ /*! * @brief An unsigned 64-bit integer. * * Not necessarily defined to `uint64_t` but functionally equivalent. */ typedef uint64_t XXH64_hash_t; #elif !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) # ifdef _AIX # include # else # include # endif typedef uint64_t XXH64_hash_t; #else # include # if defined(__LP64__) && ULONG_MAX == 0xFFFFFFFFFFFFFFFFULL /* LP64 ABI says uint64_t is unsigned long */ typedef unsigned long XXH64_hash_t; # else /* the following type must have a width of 64-bit */ typedef unsigned long long XXH64_hash_t; # endif #endif /*! * @} * * @defgroup XXH64_family XXH64 family * @ingroup public * @{ * Contains functions used in the classic 64-bit xxHash algorithm. * * @note * XXH3 provides competitive speed for both 32-bit and 64-bit systems, * and offers true 64/128 bit hash results. * It provides better speed for systems with vector processing capabilities. */ /*! * @brief Calculates the 64-bit hash of @p input using xxHash64. * * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * @param seed The 64-bit seed to alter the hash's output predictably. * * @pre * The memory between @p input and @p input + @p length must be valid, * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * * @return The calculated 64-bit xxHash64 value. * * @see @ref single_shot_example "Single Shot Example" for an example. */ XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed); /******* Streaming *******/ #ifndef XXH_NO_STREAM /*! * @brief The opaque state struct for the XXH64 streaming API. * * @see XXH64_state_s for details. * @see @ref streaming_example "Streaming Example" */ typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */ /*! * @brief Allocates an @ref XXH64_state_t. * * @return An allocated pointer of @ref XXH64_state_t on success. * @return `NULL` on failure. * * @note Must be freed with XXH64_freeState(). * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_MALLOCF XXH64_state_t* XXH64_createState(void); /*! * @brief Frees an @ref XXH64_state_t. * * @param statePtr A pointer to an @ref XXH64_state_t allocated with @ref XXH64_createState(). * * @return @ref XXH_OK. * * @note @p statePtr must be allocated with XXH64_createState(). * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr); /*! * @brief Copies one @ref XXH64_state_t to another. * * @param dst_state The state to copy to. * @param src_state The state to copy from. * @pre * @p dst_state and @p src_state must not be `NULL` and must not overlap. */ XXH_PUBLIC_API void XXH64_copyState(XXH_NOESCAPE XXH64_state_t* dst_state, const XXH64_state_t* src_state); /*! * @brief Resets an @ref XXH64_state_t to begin a new hash. * * @param statePtr The state struct to reset. * @param seed The 64-bit seed to alter the hash result predictably. * * @pre * @p statePtr must not be `NULL`. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note This function resets and seeds a state. Call it before @ref XXH64_update(). * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH_NOESCAPE XXH64_state_t* statePtr, XXH64_hash_t seed); /*! * @brief Consumes a block of @p input to an @ref XXH64_state_t. * * @param statePtr The state struct to update. * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * * @pre * @p statePtr must not be `NULL`. * @pre * The memory between @p input and @p input + @p length must be valid, * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note Call this to incrementally consume blocks of data. * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH_NOESCAPE XXH64_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length); /*! * @brief Returns the calculated hash value from an @ref XXH64_state_t. * * @param statePtr The state struct to calculate the hash from. * * @pre * @p statePtr must not be `NULL`. * * @return The calculated 64-bit xxHash64 value from that state. * * @note * Calling XXH64_digest() will not affect @p statePtr, so you can update, * digest, and update again. * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_digest (XXH_NOESCAPE const XXH64_state_t* statePtr); #endif /* !XXH_NO_STREAM */ /******* Canonical representation *******/ /*! * @brief Canonical (big endian) representation of @ref XXH64_hash_t. */ typedef struct { unsigned char digest[sizeof(XXH64_hash_t)]; } XXH64_canonical_t; /*! * @brief Converts an @ref XXH64_hash_t to a big endian @ref XXH64_canonical_t. * * @param dst The @ref XXH64_canonical_t pointer to be stored to. * @param hash The @ref XXH64_hash_t to be converted. * * @pre * @p dst must not be `NULL`. * * @see @ref canonical_representation_example "Canonical Representation Example" */ XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH_NOESCAPE XXH64_canonical_t* dst, XXH64_hash_t hash); /*! * @brief Converts an @ref XXH64_canonical_t to a native @ref XXH64_hash_t. * * @param src The @ref XXH64_canonical_t to convert. * * @pre * @p src must not be `NULL`. * * @return The converted hash. * * @see @ref canonical_representation_example "Canonical Representation Example" */ XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_hashFromCanonical(XXH_NOESCAPE const XXH64_canonical_t* src); #ifndef XXH_NO_XXH3 /*! * @} * ************************************************************************ * @defgroup XXH3_family XXH3 family * @ingroup public * @{ * * XXH3 is a more recent hash algorithm featuring: * - Improved speed for both small and large inputs * - True 64-bit and 128-bit outputs * - SIMD acceleration * - Improved 32-bit viability * * Speed analysis methodology is explained here: * * https://fastcompression.blogspot.com/2019/03/presenting-xxh3.html * * Compared to XXH64, expect XXH3 to run approximately * ~2x faster on large inputs and >3x faster on small ones, * exact differences vary depending on platform. * * XXH3's speed benefits greatly from SIMD and 64-bit arithmetic, * but does not require it. * Most 32-bit and 64-bit targets that can run XXH32 smoothly can run XXH3 * at competitive speeds, even without vector support. Further details are * explained in the implementation. * * XXH3 has a fast scalar implementation, but it also includes accelerated SIMD * implementations for many common platforms: * - AVX512 * - AVX2 * - SSE2 * - ARM NEON * - WebAssembly SIMD128 * - POWER8 VSX * - s390x ZVector * This can be controlled via the @ref XXH_VECTOR macro, but it automatically * selects the best version according to predefined macros. For the x86 family, an * automatic runtime dispatcher is included separately in @ref xxh_x86dispatch.c. * * XXH3 implementation is portable: * it has a generic C90 formulation that can be compiled on any platform, * all implementations generate exactly the same hash value on all platforms. * Starting from v0.8.0, it's also labelled "stable", meaning that * any future version will also generate the same hash value. * * XXH3 offers 2 variants, _64bits and _128bits. * * When only 64 bits are needed, prefer invoking the _64bits variant, as it * reduces the amount of mixing, resulting in faster speed on small inputs. * It's also generally simpler to manipulate a scalar return type than a struct. * * The API supports one-shot hashing, streaming mode, and custom secrets. */ /*! * @ingroup tuning * @brief Possible values for @ref XXH_VECTOR. * * Unless set explicitly, determined automatically. */ # define XXH_SCALAR 0 /*!< Portable scalar version */ # define XXH_SSE2 1 /*!< SSE2 for Pentium 4, Opteron, all x86_64. */ # define XXH_AVX2 2 /*!< AVX2 for Haswell and Bulldozer */ # define XXH_AVX512 3 /*!< AVX512 for Skylake and Icelake */ # define XXH_NEON 4 /*!< NEON for most ARMv7-A, all AArch64, and WASM SIMD128 */ # define XXH_VSX 5 /*!< VSX and ZVector for POWER8/z13 (64-bit) */ # define XXH_SVE 6 /*!< SVE for some ARMv8-A and ARMv9-A */ # define XXH_LSX 7 /*!< LSX (128-bit SIMD) for LoongArch64 */ # define XXH_LASX 8 /*!< LASX (256-bit SIMD) for LoongArch64 */ # define XXH_RVV 9 /*!< RVV (RISC-V Vector) for RISC-V */ /*-********************************************************************** * XXH3 64-bit variant ************************************************************************/ /*! * @brief Calculates 64-bit unseeded variant of XXH3 hash of @p input. * * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * * @pre * The memory between @p input and @p input + @p length must be valid, * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * * @return The calculated 64-bit XXH3 hash value. * * @note * This is equivalent to @ref XXH3_64bits_withSeed() with a seed of `0`, however * it may have slightly better performance due to constant propagation of the * defaults. * * @see * XXH3_64bits_withSeed(), XXH3_64bits_withSecret(): other seeding variants * @see @ref single_shot_example "Single Shot Example" for an example. */ XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits(XXH_NOESCAPE const void* input, size_t length); /*! * @brief Calculates 64-bit seeded variant of XXH3 hash of @p input. * * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * @param seed The 64-bit seed to alter the hash result predictably. * * @pre * The memory between @p input and @p input + @p length must be valid, * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * * @return The calculated 64-bit XXH3 hash value. * * @note * seed == 0 produces the same results as @ref XXH3_64bits(). * * This variant generates a custom secret on the fly based on default secret * altered using the @p seed value. * * While this operation is decently fast, note that it's not completely free. * * @see @ref single_shot_example "Single Shot Example" for an example. */ XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSeed(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed); /*! * The bare minimum size for a custom secret. * * @see * XXH3_64bits_withSecret(), XXH3_64bits_reset_withSecret(), * XXH3_128bits_withSecret(), XXH3_128bits_reset_withSecret(). */ #define XXH3_SECRET_SIZE_MIN 136 /*! * @brief Calculates 64-bit variant of XXH3 with a custom "secret". * * @param data The block of data to be hashed, at least @p len bytes in size. * @param len The length of @p data, in bytes. * @param secret The secret data. * @param secretSize The length of @p secret, in bytes. * * @return The calculated 64-bit XXH3 hash value. * * @pre * The memory between @p data and @p data + @p len must be valid, * readable, contiguous memory. However, if @p length is `0`, @p data may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * * It's possible to provide any blob of bytes as a "secret" to generate the hash. * This makes it more difficult for an external actor to prepare an intentional collision. * The main condition is that @p secretSize *must* be large enough (>= @ref XXH3_SECRET_SIZE_MIN). * However, the quality of the secret impacts the dispersion of the hash algorithm. * Therefore, the secret _must_ look like a bunch of random bytes. * Avoid "trivial" or structured data such as repeated sequences or a text document. * Whenever in doubt about the "randomness" of the blob of bytes, * consider employing @ref XXH3_generateSecret() instead (see below). * It will generate a proper high entropy secret derived from the blob of bytes. * Another advantage of using XXH3_generateSecret() is that * it guarantees that all bits within the initial blob of bytes * will impact every bit of the output. * This is not necessarily the case when using the blob of bytes directly * because, when hashing _small_ inputs, only a portion of the secret is employed. * * @see @ref single_shot_example "Single Shot Example" for an example. */ XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSecret(XXH_NOESCAPE const void* data, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize); /******* Streaming *******/ #ifndef XXH_NO_STREAM /* * Streaming requires state maintenance. * This operation costs memory and CPU. * As a consequence, streaming is slower than one-shot hashing. * For better performance, prefer one-shot functions whenever applicable. */ /*! * @brief The opaque state struct for the XXH3 streaming API. * * @see XXH3_state_s for details. * @see @ref streaming_example "Streaming Example" */ typedef struct XXH3_state_s XXH3_state_t; XXH_PUBLIC_API XXH_MALLOCF XXH3_state_t* XXH3_createState(void); XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr); /*! * @brief Copies one @ref XXH3_state_t to another. * * @param dst_state The state to copy to. * @param src_state The state to copy from. * @pre * @p dst_state and @p src_state must not be `NULL` and must not overlap. */ XXH_PUBLIC_API void XXH3_copyState(XXH_NOESCAPE XXH3_state_t* dst_state, XXH_NOESCAPE const XXH3_state_t* src_state); /*! * @brief Resets an @ref XXH3_state_t to begin a new hash. * * @param statePtr The state struct to reset. * * @pre * @p statePtr must not be `NULL`. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note * - This function resets `statePtr` and generate a secret with default parameters. * - Call this function before @ref XXH3_64bits_update(). * - Digest will be equivalent to `XXH3_64bits()`. * * @see @ref streaming_example "Streaming Example" * */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr); /*! * @brief Resets an @ref XXH3_state_t with 64-bit seed to begin a new hash. * * @param statePtr The state struct to reset. * @param seed The 64-bit seed to alter the hash result predictably. * * @pre * @p statePtr must not be `NULL`. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note * - This function resets `statePtr` and generate a secret from `seed`. * - Call this function before @ref XXH3_64bits_update(). * - Digest will be equivalent to `XXH3_64bits_withSeed()`. * * @see @ref streaming_example "Streaming Example" * */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed); /*! * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash. * * @param statePtr The state struct to reset. * @param secret The secret data. * @param secretSize The length of @p secret, in bytes. * * @pre * @p statePtr must not be `NULL`. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note * `secret` is referenced, it _must outlive_ the hash streaming session. * * Similar to one-shot API, `secretSize` must be >= @ref XXH3_SECRET_SIZE_MIN, * and the quality of produced hash values depends on secret's entropy * (secret's content should look like a bunch of random bytes). * When in doubt about the randomness of a candidate `secret`, * consider employing `XXH3_generateSecret()` instead (see below). * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize); /*! * @brief Consumes a block of @p input to an @ref XXH3_state_t. * * @param statePtr The state struct to update. * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * * @pre * @p statePtr must not be `NULL`. * @pre * The memory between @p input and @p input + @p length must be valid, * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note Call this to incrementally consume blocks of data. * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update (XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length); /*! * @brief Returns the calculated XXH3 64-bit hash value from an @ref XXH3_state_t. * * @param statePtr The state struct to calculate the hash from. * * @pre * @p statePtr must not be `NULL`. * * @return The calculated XXH3 64-bit hash value from that state. * * @note * Calling XXH3_64bits_digest() will not affect @p statePtr, so you can update, * digest, and update again. * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_digest (XXH_NOESCAPE const XXH3_state_t* statePtr); #endif /* !XXH_NO_STREAM */ /* note : canonical representation of XXH3 is the same as XXH64 * since they both produce XXH64_hash_t values */ /*-********************************************************************** * XXH3 128-bit variant ************************************************************************/ /*! * @brief The return value from 128-bit hashes. * * Stored in little endian order, although the fields themselves are in native * endianness. */ typedef struct { XXH64_hash_t low64; /*!< `value & 0xFFFFFFFFFFFFFFFF` */ XXH64_hash_t high64; /*!< `value >> 64` */ } XXH128_hash_t; /*! * @brief Calculates 128-bit unseeded variant of XXH3 of @p data. * * @param data The block of data to be hashed, at least @p length bytes in size. * @param len The length of @p data, in bytes. * * @return The calculated 128-bit variant of XXH3 value. * * The 128-bit variant of XXH3 has more strength, but it has a bit of overhead * for shorter inputs. * * This is equivalent to @ref XXH3_128bits_withSeed() with a seed of `0`, however * it may have slightly better performance due to constant propagation of the * defaults. * * @see XXH3_128bits_withSeed(), XXH3_128bits_withSecret(): other seeding variants * @see @ref single_shot_example "Single Shot Example" for an example. */ XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits(XXH_NOESCAPE const void* data, size_t len); /*! @brief Calculates 128-bit seeded variant of XXH3 hash of @p data. * * @param data The block of data to be hashed, at least @p length bytes in size. * @param len The length of @p data, in bytes. * @param seed The 64-bit seed to alter the hash result predictably. * * @return The calculated 128-bit variant of XXH3 value. * * @note * seed == 0 produces the same results as @ref XXH3_64bits(). * * This variant generates a custom secret on the fly based on default secret * altered using the @p seed value. * * While this operation is decently fast, note that it's not completely free. * * @see XXH3_128bits(), XXH3_128bits_withSecret(): other seeding variants * @see @ref single_shot_example "Single Shot Example" for an example. */ XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSeed(XXH_NOESCAPE const void* data, size_t len, XXH64_hash_t seed); /*! * @brief Calculates 128-bit variant of XXH3 with a custom "secret". * * @param data The block of data to be hashed, at least @p len bytes in size. * @param len The length of @p data, in bytes. * @param secret The secret data. * @param secretSize The length of @p secret, in bytes. * * @return The calculated 128-bit variant of XXH3 value. * * It's possible to provide any blob of bytes as a "secret" to generate the hash. * This makes it more difficult for an external actor to prepare an intentional collision. * The main condition is that @p secretSize *must* be large enough (>= @ref XXH3_SECRET_SIZE_MIN). * However, the quality of the secret impacts the dispersion of the hash algorithm. * Therefore, the secret _must_ look like a bunch of random bytes. * Avoid "trivial" or structured data such as repeated sequences or a text document. * Whenever in doubt about the "randomness" of the blob of bytes, * consider employing @ref XXH3_generateSecret() instead (see below). * It will generate a proper high entropy secret derived from the blob of bytes. * Another advantage of using XXH3_generateSecret() is that * it guarantees that all bits within the initial blob of bytes * will impact every bit of the output. * This is not necessarily the case when using the blob of bytes directly * because, when hashing _small_ inputs, only a portion of the secret is employed. * * @see @ref single_shot_example "Single Shot Example" for an example. */ XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSecret(XXH_NOESCAPE const void* data, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize); /******* Streaming *******/ #ifndef XXH_NO_STREAM /* * Streaming requires state maintenance. * This operation costs memory and CPU. * As a consequence, streaming is slower than one-shot hashing. * For better performance, prefer one-shot functions whenever applicable. * * XXH3_128bits uses the same XXH3_state_t as XXH3_64bits(). * Use already declared XXH3_createState() and XXH3_freeState(). * * All reset and streaming functions have same meaning as their 64-bit counterpart. */ /*! * @brief Resets an @ref XXH3_state_t to begin a new hash. * * @param statePtr The state struct to reset. * * @pre * @p statePtr must not be `NULL`. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note * - This function resets `statePtr` and generate a secret with default parameters. * - Call it before @ref XXH3_128bits_update(). * - Digest will be equivalent to `XXH3_128bits()`. * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr); /*! * @brief Resets an @ref XXH3_state_t with 64-bit seed to begin a new hash. * * @param statePtr The state struct to reset. * @param seed The 64-bit seed to alter the hash result predictably. * * @pre * @p statePtr must not be `NULL`. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note * - This function resets `statePtr` and generate a secret from `seed`. * - Call it before @ref XXH3_128bits_update(). * - Digest will be equivalent to `XXH3_128bits_withSeed()`. * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed); /*! * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash. * * @param statePtr The state struct to reset. * @param secret The secret data. * @param secretSize The length of @p secret, in bytes. * * @pre * @p statePtr must not be `NULL`. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * `secret` is referenced, it _must outlive_ the hash streaming session. * Similar to one-shot API, `secretSize` must be >= @ref XXH3_SECRET_SIZE_MIN, * and the quality of produced hash values depends on secret's entropy * (secret's content should look like a bunch of random bytes). * When in doubt about the randomness of a candidate `secret`, * consider employing `XXH3_generateSecret()` instead (see below). * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize); /*! * @brief Consumes a block of @p input to an @ref XXH3_state_t. * * Call this to incrementally consume blocks of data. * * @param statePtr The state struct to update. * @param input The block of data to be hashed, at least @p length bytes in size. * @param length The length of @p input, in bytes. * * @pre * @p statePtr must not be `NULL`. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @note * The memory between @p input and @p input + @p length must be valid, * readable, contiguous memory. However, if @p length is `0`, @p input may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update (XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length); /*! * @brief Returns the calculated XXH3 128-bit hash value from an @ref XXH3_state_t. * * @param statePtr The state struct to calculate the hash from. * * @pre * @p statePtr must not be `NULL`. * * @return The calculated XXH3 128-bit hash value from that state. * * @note * Calling XXH3_128bits_digest() will not affect @p statePtr, so you can update, * digest, and update again. * */ XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_digest (XXH_NOESCAPE const XXH3_state_t* statePtr); #endif /* !XXH_NO_STREAM */ /* Following helper functions make it possible to compare XXH128_hast_t values. * Since XXH128_hash_t is a structure, this capability is not offered by the language. * Note: For better performance, these functions can be inlined using XXH_INLINE_ALL */ /*! * @brief Check equality of two XXH128_hash_t values * * @param h1 The 128-bit hash value. * @param h2 Another 128-bit hash value. * * @return `1` if `h1` and `h2` are equal. * @return `0` if they are not. */ XXH_PUBLIC_API XXH_PUREF int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2); /*! * @brief Compares two @ref XXH128_hash_t * * This comparator is compatible with stdlib's `qsort()`/`bsearch()`. * * @param h128_1 Left-hand side value * @param h128_2 Right-hand side value * * @return >0 if @p h128_1 > @p h128_2 * @return =0 if @p h128_1 == @p h128_2 * @return <0 if @p h128_1 < @p h128_2 */ XXH_PUBLIC_API XXH_PUREF int XXH128_cmp(XXH_NOESCAPE const void* h128_1, XXH_NOESCAPE const void* h128_2); /******* Canonical representation *******/ typedef struct { unsigned char digest[sizeof(XXH128_hash_t)]; } XXH128_canonical_t; /*! * @brief Converts an @ref XXH128_hash_t to a big endian @ref XXH128_canonical_t. * * @param dst The @ref XXH128_canonical_t pointer to be stored to. * @param hash The @ref XXH128_hash_t to be converted. * * @pre * @p dst must not be `NULL`. * @see @ref canonical_representation_example "Canonical Representation Example" */ XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH_NOESCAPE XXH128_canonical_t* dst, XXH128_hash_t hash); /*! * @brief Converts an @ref XXH128_canonical_t to a native @ref XXH128_hash_t. * * @param src The @ref XXH128_canonical_t to convert. * * @pre * @p src must not be `NULL`. * * @return The converted hash. * @see @ref canonical_representation_example "Canonical Representation Example" */ XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128_hashFromCanonical(XXH_NOESCAPE const XXH128_canonical_t* src); #endif /* !XXH_NO_XXH3 */ #endif /* XXH_NO_LONG_LONG */ /*! * @} */ #endif /* XXHASH_H_5627135585666179 */ #if defined(XXH_STATIC_LINKING_ONLY) && !defined(XXHASH_H_STATIC_13879238742) #define XXHASH_H_STATIC_13879238742 /* **************************************************************************** * This section contains declarations which are not guaranteed to remain stable. * They may change in future versions, becoming incompatible with a different * version of the library. * These declarations should only be used with static linking. * Never use them in association with dynamic linking! ***************************************************************************** */ /* * These definitions are only present to allow static allocation * of XXH states, on stack or in a struct, for example. * Never **ever** access their members directly. */ /*! * @internal * @brief Structure for XXH32 streaming API. * * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY, * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is * an opaque type. This allows fields to safely be changed. * * Typedef'd to @ref XXH32_state_t. * Do not access the members of this struct directly. * @see XXH64_state_s, XXH3_state_s */ struct XXH32_state_s { XXH32_hash_t total_len_32; /*!< Total length hashed, modulo 2^32 */ XXH32_hash_t large_len; /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */ XXH32_hash_t acc[4]; /*!< Accumulator lanes */ unsigned char buffer[16]; /*!< Internal buffer for partial reads. */ XXH32_hash_t bufferedSize; /*!< Amount of data in @ref buffer */ XXH32_hash_t reserved; /*!< Reserved field. Do not read nor write to it. */ }; /* typedef'd to XXH32_state_t */ #ifndef XXH_NO_LONG_LONG /* defined when there is no 64-bit support */ /*! * @internal * @brief Structure for XXH64 streaming API. * * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY, * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is * an opaque type. This allows fields to safely be changed. * * Typedef'd to @ref XXH64_state_t. * Do not access the members of this struct directly. * @see XXH32_state_s, XXH3_state_s */ struct XXH64_state_s { XXH64_hash_t total_len; /*!< Total length hashed. This is always 64-bit. */ XXH64_hash_t acc[4]; /*!< Accumulator lanes */ unsigned char buffer[32]; /*!< Internal buffer for partial reads.. */ XXH32_hash_t bufferedSize; /*!< Amount of data in @ref buffer */ XXH32_hash_t reserved32; /*!< Reserved field, needed for padding anyways*/ XXH64_hash_t reserved64; /*!< Reserved field. Do not read or write to it. */ }; /* typedef'd to XXH64_state_t */ #ifndef XXH_NO_XXH3 #if defined(__cplusplus) && (__cplusplus >= 201103L) /* >= C++11 */ /* In C++ alignas() is a keyword */ # define XXH_ALIGN(n) alignas(n) #elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* > C11 */ # define XXH_ALIGN(n) _Alignas(n) #elif defined(__GNUC__) # define XXH_ALIGN(n) __attribute__ ((aligned(n))) #elif defined(_MSC_VER) # define XXH_ALIGN(n) __declspec(align(n)) #else # define XXH_ALIGN(n) /* disabled */ #endif /* Old GCC versions only accept the attribute after the type in structures. */ #if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) /* C11+ */ \ && ! (defined(__cplusplus) && (__cplusplus >= 201103L)) /* >= C++11 */ \ && defined(__GNUC__) # define XXH_ALIGN_MEMBER(align, type) type XXH_ALIGN(align) #else # define XXH_ALIGN_MEMBER(align, type) XXH_ALIGN(align) type #endif /*! * @internal * @brief The size of the internal XXH3 buffer. * * This is the optimal update size for incremental hashing. * * @see XXH3_64b_update(), XXH3_128b_update(). */ #define XXH3_INTERNALBUFFER_SIZE 256 /*! * @def XXH3_SECRET_DEFAULT_SIZE * @brief Default Secret's size * * This is the size of internal XXH3_kSecret * and is needed by XXH3_generateSecret_fromSeed(). * * Not to be confused with @ref XXH3_SECRET_SIZE_MIN. */ #define XXH3_SECRET_DEFAULT_SIZE 192 /*! * @internal * @brief Structure for XXH3 streaming API. * * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY, * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. * Otherwise it is an opaque type. * Never use this definition in combination with dynamic library. * This allows fields to safely be changed in the future. * * @note ** This structure has a strict alignment requirement of 64 bytes!! ** * Do not allocate this with `malloc()` or `new`, * it will not be sufficiently aligned. * Use @ref XXH3_createState() and @ref XXH3_freeState(), or stack allocation. * * Typedef'd to @ref XXH3_state_t. * Do never access the members of this struct directly. * * @see XXH3_INITSTATE() for stack initialization. * @see XXH3_createState(), XXH3_freeState(). * @see XXH32_state_s, XXH64_state_s */ struct XXH3_state_s { XXH_ALIGN_MEMBER(64, XXH64_hash_t acc[8]); /*!< The 8 accumulators. See @ref XXH32_state_s::acc and @ref XXH64_state_s::acc */ XXH_ALIGN_MEMBER(64, unsigned char customSecret[XXH3_SECRET_DEFAULT_SIZE]); /*!< Used to store a custom secret generated from a seed. */ XXH_ALIGN_MEMBER(64, unsigned char buffer[XXH3_INTERNALBUFFER_SIZE]); /*!< The internal buffer. @see XXH32_state_s::mem32 */ XXH32_hash_t bufferedSize; /*!< The amount of memory in @ref buffer, @see XXH32_state_s::memsize */ XXH32_hash_t useSeed; /*!< Reserved field. Needed for padding on 64-bit. */ size_t nbStripesSoFar; /*!< Number or stripes processed. */ XXH64_hash_t totalLen; /*!< Total length hashed. 64-bit even on 32-bit targets. */ size_t nbStripesPerBlock; /*!< Number of stripes per block. */ size_t secretLimit; /*!< Size of @ref customSecret or @ref extSecret */ XXH64_hash_t seed; /*!< Seed for _withSeed variants. Must be zero otherwise, @see XXH3_INITSTATE() */ XXH64_hash_t reserved64; /*!< Reserved field. */ const unsigned char* extSecret; /*!< Reference to an external secret for the _withSecret variants, NULL * for other variants. */ /* note: there may be some padding at the end due to alignment on 64 bytes */ }; /* typedef'd to XXH3_state_t */ #undef XXH_ALIGN_MEMBER /*! * @brief Initializes a stack-allocated `XXH3_state_s`. * * When the @ref XXH3_state_t structure is merely emplaced on stack, * it should be initialized with XXH3_INITSTATE() or a memset() * in case its first reset uses XXH3_NNbits_reset_withSeed(). * This init can be omitted if the first reset uses default or _withSecret mode. * This operation isn't necessary when the state is created with XXH3_createState(). * Note that this doesn't prepare the state for a streaming operation, * it's still necessary to use XXH3_NNbits_reset*() afterwards. */ #define XXH3_INITSTATE(XXH3_state_ptr) \ do { \ XXH3_state_t* tmp_xxh3_state_ptr = (XXH3_state_ptr); \ tmp_xxh3_state_ptr->seed = 0; \ tmp_xxh3_state_ptr->extSecret = NULL; \ } while(0) /*! * @brief Calculates the 128-bit hash of @p data using XXH3. * * @param data The block of data to be hashed, at least @p len bytes in size. * @param len The length of @p data, in bytes. * @param seed The 64-bit seed to alter the hash's output predictably. * * @pre * The memory between @p data and @p data + @p len must be valid, * readable, contiguous memory. However, if @p len is `0`, @p data may be * `NULL`. In C++, this also must be *TriviallyCopyable*. * * @return The calculated 128-bit XXH3 value. * * @see @ref single_shot_example "Single Shot Example" for an example. */ XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128(XXH_NOESCAPE const void* data, size_t len, XXH64_hash_t seed); /* === Experimental API === */ /* Symbols defined below must be considered tied to a specific library version. */ /*! * @brief Derive a high-entropy secret from any user-defined content, named customSeed. * * @param secretBuffer A writable buffer for derived high-entropy secret data. * @param secretSize Size of secretBuffer, in bytes. Must be >= XXH3_SECRET_SIZE_MIN. * @param customSeed A user-defined content. * @param customSeedSize Size of customSeed, in bytes. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * The generated secret can be used in combination with `*_withSecret()` functions. * The `_withSecret()` variants are useful to provide a higher level of protection * than 64-bit seed, as it becomes much more difficult for an external actor to * guess how to impact the calculation logic. * * The function accepts as input a custom seed of any length and any content, * and derives from it a high-entropy secret of length @p secretSize into an * already allocated buffer @p secretBuffer. * * The generated secret can then be used with any `*_withSecret()` variant. * The functions @ref XXH3_128bits_withSecret(), @ref XXH3_64bits_withSecret(), * @ref XXH3_128bits_reset_withSecret() and @ref XXH3_64bits_reset_withSecret() * are part of this list. They all accept a `secret` parameter * which must be large enough for implementation reasons (>= @ref XXH3_SECRET_SIZE_MIN) * _and_ feature very high entropy (consist of random-looking bytes). * These conditions can be a high bar to meet, so @ref XXH3_generateSecret() can * be employed to ensure proper quality. * * @p customSeed can be anything. It can have any size, even small ones, * and its content can be anything, even "poor entropy" sources such as a bunch * of zeroes. The resulting `secret` will nonetheless provide all required qualities. * * @pre * - @p secretSize must be >= @ref XXH3_SECRET_SIZE_MIN * - When @p customSeedSize > 0, supplying NULL as customSeed is undefined behavior. * * Example code: * @code{.c} * #include * #include * #include * #define XXH_STATIC_LINKING_ONLY // expose unstable API * #include "xxhash.h" * // Hashes argv[2] using the entropy from argv[1]. * int main(int argc, char* argv[]) * { * char secret[XXH3_SECRET_SIZE_MIN]; * if (argv != 3) { return 1; } * XXH3_generateSecret(secret, sizeof(secret), argv[1], strlen(argv[1])); * XXH64_hash_t h = XXH3_64bits_withSecret( * argv[2], strlen(argv[2]), * secret, sizeof(secret) * ); * printf("%016llx\n", (unsigned long long) h); * } * @endcode */ XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(XXH_NOESCAPE void* secretBuffer, size_t secretSize, XXH_NOESCAPE const void* customSeed, size_t customSeedSize); /*! * @brief Generate the same secret as the _withSeed() variants. * * @param secretBuffer A writable buffer of @ref XXH3_SECRET_DEFAULT_SIZE bytes * @param seed The 64-bit seed to alter the hash result predictably. * * The generated secret can be used in combination with *`*_withSecret()` and `_withSecretandSeed()` variants. * * Example C++ `std::string` hash class: * @code{.cpp} * #include * #define XXH_STATIC_LINKING_ONLY // expose unstable API * #include "xxhash.h" * // Slow, seeds each time * class HashSlow { * XXH64_hash_t seed; * public: * HashSlow(XXH64_hash_t s) : seed{s} {} * size_t operator()(const std::string& x) const { * return size_t{XXH3_64bits_withSeed(x.c_str(), x.length(), seed)}; * } * }; * // Fast, caches the seeded secret for future uses. * class HashFast { * unsigned char secret[XXH3_SECRET_DEFAULT_SIZE]; * public: * HashFast(XXH64_hash_t s) { * XXH3_generateSecret_fromSeed(secret, seed); * } * size_t operator()(const std::string& x) const { * return size_t{ * XXH3_64bits_withSecret(x.c_str(), x.length(), secret, sizeof(secret)) * }; * } * }; * @endcode */ XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(XXH_NOESCAPE void* secretBuffer, XXH64_hash_t seed); /*! * @brief Maximum size of "short" key in bytes. */ #define XXH3_MIDSIZE_MAX 240 /*! * @brief Calculates 64/128-bit seeded variant of XXH3 hash of @p data. * * @param data The block of data to be hashed, at least @p len bytes in size. * @param len The length of @p data, in bytes. * @param secret The secret data. * @param secretSize The length of @p secret, in bytes. * @param seed The 64-bit seed to alter the hash result predictably. * * These variants generate hash values using either: * - @p seed for "short" keys (< @ref XXH3_MIDSIZE_MAX = 240 bytes) * - @p secret for "large" keys (>= @ref XXH3_MIDSIZE_MAX). * * This generally benefits speed, compared to `_withSeed()` or `_withSecret()`. * `_withSeed()` has to generate the secret on the fly for "large" keys. * It's fast, but can be perceptible for "not so large" keys (< 1 KB). * `_withSecret()` has to generate the masks on the fly for "small" keys, * which requires more instructions than _withSeed() variants. * Therefore, _withSecretandSeed variant combines the best of both worlds. * * When @p secret has been generated by XXH3_generateSecret_fromSeed(), * this variant produces *exactly* the same results as `_withSeed()` variant, * hence offering only a pure speed benefit on "large" input, * by skipping the need to regenerate the secret for every large input. * * Another usage scenario is to hash the secret to a 64-bit hash value, * for example with XXH3_64bits(), which then becomes the seed, * and then employ both the seed and the secret in _withSecretandSeed(). * On top of speed, an added benefit is that each bit in the secret * has a 50% chance to swap each bit in the output, via its impact to the seed. * * This is not guaranteed when using the secret directly in "small data" scenarios, * because only portions of the secret are employed for small data. */ XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSecretandSeed(XXH_NOESCAPE const void* data, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed); /*! * @brief Calculates 128-bit seeded variant of XXH3 hash of @p data. * * @param input The memory segment to be hashed, at least @p len bytes in size. * @param length The length of @p data, in bytes. * @param secret The secret used to alter hash result predictably. * @param secretSize The length of @p secret, in bytes (must be >= XXH3_SECRET_SIZE_MIN) * @param seed64 The 64-bit seed to alter the hash result predictably. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @see XXH3_64bits_withSecretandSeed(): contract is the same. */ XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t length, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64); #ifndef XXH_NO_STREAM /*! * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash. * * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState(). * @param secret The secret data. * @param secretSize The length of @p secret, in bytes. * @param seed64 The 64-bit seed to alter the hash result predictably. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @see XXH3_64bits_withSecretandSeed(). Contract is identical. */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64); /*! * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash. * * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState(). * @param secret The secret data. * @param secretSize The length of @p secret, in bytes. * @param seed64 The 64-bit seed to alter the hash result predictably. * * @return @ref XXH_OK on success. * @return @ref XXH_ERROR on failure. * * @see XXH3_64bits_withSecretandSeed(). Contract is identical. * * Note: there was a bug in an earlier version of this function (<= v0.8.2) * that would make it generate an incorrect hash value * when @p seed == 0 and @p length < XXH3_MIDSIZE_MAX * and @p secret is different from XXH3_generateSecret_fromSeed(). * As stated in the contract, the correct hash result must be * the same as XXH3_128bits_withSeed() when @p length <= XXH3_MIDSIZE_MAX. * Results generated by this older version are wrong, hence not comparable. */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64); #endif /* !XXH_NO_STREAM */ #endif /* !XXH_NO_XXH3 */ #endif /* XXH_NO_LONG_LONG */ #if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) # define XXH_IMPLEMENTATION #endif #endif /* defined(XXH_STATIC_LINKING_ONLY) && !defined(XXHASH_H_STATIC_13879238742) */ /* ======================================================================== */ /* ======================================================================== */ /* ======================================================================== */ /*-********************************************************************** * xxHash implementation *-********************************************************************** * xxHash's implementation used to be hosted inside xxhash.c. * * However, inlining requires implementation to be visible to the compiler, * hence be included alongside the header. * Previously, implementation was hosted inside xxhash.c, * which was then #included when inlining was activated. * This construction created issues with a few build and install systems, * as it required xxhash.c to be stored in /include directory. * * xxHash implementation is now directly integrated within xxhash.h. * As a consequence, xxhash.c is no longer needed in /include. * * xxhash.c is still available and is still useful. * In a "normal" setup, when xxhash is not inlined, * xxhash.h only exposes the prototypes and public symbols, * while xxhash.c can be built into an object file xxhash.o * which can then be linked into the final binary. ************************************************************************/ #if ( defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) \ || defined(XXH_IMPLEMENTATION) ) && !defined(XXH_IMPLEM_13a8737387) # define XXH_IMPLEM_13a8737387 /* ************************************* * Tuning parameters ***************************************/ /*! * @defgroup tuning Tuning parameters * @{ * * Various macros to control xxHash's behavior. */ #ifdef XXH_DOXYGEN /*! * @brief Define this to disable 64-bit code. * * Useful if only using the @ref XXH32_family and you have a strict C90 compiler. */ # define XXH_NO_LONG_LONG # undef XXH_NO_LONG_LONG /* don't actually */ /*! * @brief Controls how unaligned memory is accessed. * * By default, access to unaligned memory is controlled by `memcpy()`, which is * safe and portable. * * Unfortunately, on some target/compiler combinations, the generated assembly * is sub-optimal. * * The below switch allow selection of a different access method * in the search for improved performance. * * @par Possible options: * * - `XXH_FORCE_MEMORY_ACCESS=0` (default): `memcpy` * @par * Use `memcpy()`. Safe and portable. Note that most modern compilers will * eliminate the function call and treat it as an unaligned access. * * - `XXH_FORCE_MEMORY_ACCESS=1`: `__attribute__((aligned(1)))` * @par * Depends on compiler extensions and is therefore not portable. * This method is safe _if_ your compiler supports it, * and *generally* as fast or faster than `memcpy`. * * - `XXH_FORCE_MEMORY_ACCESS=2`: Direct cast * @par * Casts directly and dereferences. This method doesn't depend on the * compiler, but it violates the C standard as it directly dereferences an * unaligned pointer. It can generate buggy code on targets which do not * support unaligned memory accesses, but in some circumstances, it's the * only known way to get the most performance. * * - `XXH_FORCE_MEMORY_ACCESS=3`: Byteshift * @par * Also portable. This can generate the best code on old compilers which don't * inline small `memcpy()` calls, and it might also be faster on big-endian * systems which lack a native byteswap instruction. However, some compilers * will emit literal byteshifts even if the target supports unaligned access. * * * @warning * Methods 1 and 2 rely on implementation-defined behavior. Use these with * care, as what works on one compiler/platform/optimization level may cause * another to read garbage data or even crash. * * See https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html for details. * * Prefer these methods in priority order (0 > 3 > 1 > 2) */ # define XXH_FORCE_MEMORY_ACCESS 0 /*! * @def XXH_SIZE_OPT * @brief Controls how much xxHash optimizes for size. * * xxHash, when compiled, tends to result in a rather large binary size. This * is mostly due to heavy usage to forced inlining and constant folding of the * @ref XXH3_family to increase performance. * * However, some developers prefer size over speed. This option can * significantly reduce the size of the generated code. When using the `-Os` * or `-Oz` options on GCC or Clang, this is defined to 1 by default, * otherwise it is defined to 0. * * Most of these size optimizations can be controlled manually. * * This is a number from 0-2. * - `XXH_SIZE_OPT` == 0: Default. xxHash makes no size optimizations. Speed * comes first. * - `XXH_SIZE_OPT` == 1: Default for `-Os` and `-Oz`. xxHash is more * conservative and disables hacks that increase code size. It implies the * options @ref XXH_NO_INLINE_HINTS == 1, @ref XXH_FORCE_ALIGN_CHECK == 0, * and @ref XXH3_NEON_LANES == 8 if they are not already defined. * - `XXH_SIZE_OPT` == 2: xxHash tries to make itself as small as possible. * Performance may cry. For example, the single shot functions just use the * streaming API. */ # define XXH_SIZE_OPT 0 /*! * @def XXH_FORCE_ALIGN_CHECK * @brief If defined to non-zero, adds a special path for aligned inputs (XXH32() * and XXH64() only). * * This is an important performance trick for architectures without decent * unaligned memory access performance. * * It checks for input alignment, and when conditions are met, uses a "fast * path" employing direct 32-bit/64-bit reads, resulting in _dramatically * faster_ read speed. * * The check costs one initial branch per hash, which is generally negligible, * but not zero. * * Moreover, it's not useful to generate an additional code path if memory * access uses the same instruction for both aligned and unaligned * addresses (e.g. x86 and aarch64). * * In these cases, the alignment check can be removed by setting this macro to 0. * Then the code will always use unaligned memory access. * Align check is automatically disabled on x86, x64, ARM64, and some ARM chips * which are platforms known to offer good unaligned memory accesses performance. * * It is also disabled by default when @ref XXH_SIZE_OPT >= 1. * * This option does not affect XXH3 (only XXH32 and XXH64). */ # define XXH_FORCE_ALIGN_CHECK 0 /*! * @def XXH_NO_INLINE_HINTS * @brief When non-zero, sets all functions to `static`. * * By default, xxHash tries to force the compiler to inline almost all internal * functions. * * This can usually improve performance due to reduced jumping and improved * constant folding, but significantly increases the size of the binary which * might not be favorable. * * Additionally, sometimes the forced inlining can be detrimental to performance, * depending on the architecture. * * XXH_NO_INLINE_HINTS marks all internal functions as static, giving the * compiler full control on whether to inline or not. * * When not optimizing (-O0), using `-fno-inline` with GCC or Clang, or if * @ref XXH_SIZE_OPT >= 1, this will automatically be defined. */ # define XXH_NO_INLINE_HINTS 0 /*! * @def XXH3_INLINE_SECRET * @brief Determines whether to inline the XXH3 withSecret code. * * When the secret size is known, the compiler can improve the performance * of XXH3_64bits_withSecret() and XXH3_128bits_withSecret(). * * However, if the secret size is not known, it doesn't have any benefit. This * happens when xxHash is compiled into a global symbol. Therefore, if * @ref XXH_INLINE_ALL is *not* defined, this will be defined to 0. * * Additionally, this defaults to 0 on GCC 12+, which has an issue with function pointers * that are *sometimes* force inline on -Og, and it is impossible to automatically * detect this optimization level. */ # define XXH3_INLINE_SECRET 0 /*! * @def XXH32_ENDJMP * @brief Whether to use a jump for `XXH32_finalize`. * * For performance, `XXH32_finalize` uses multiple branches in the finalizer. * This is generally preferable for performance, * but depending on exact architecture, a jmp may be preferable. * * This setting is only possibly making a difference for very small inputs. */ # define XXH32_ENDJMP 0 /*! * @internal * @brief Redefines old internal names. * * For compatibility with code that uses xxHash's internals before the names * were changed to improve namespacing. There is no other reason to use this. */ # define XXH_OLD_NAMES # undef XXH_OLD_NAMES /* don't actually use, it is ugly. */ /*! * @def XXH_NO_STREAM * @brief Disables the streaming API. * * When xxHash is not inlined and the streaming functions are not used, disabling * the streaming functions can improve code size significantly, especially with * the @ref XXH3_family which tends to make constant folded copies of itself. */ # define XXH_NO_STREAM # undef XXH_NO_STREAM /* don't actually */ #endif /* XXH_DOXYGEN */ /*! * @} */ #ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */ /* prefer __packed__ structures (method 1) for GCC * < ARMv7 with unaligned access (e.g. Raspbian armhf) still uses byte shifting, so we use memcpy * which for some reason does unaligned loads. */ # if defined(__GNUC__) && !(defined(__ARM_ARCH) && __ARM_ARCH < 7 && defined(__ARM_FEATURE_UNALIGNED)) # define XXH_FORCE_MEMORY_ACCESS 1 # endif #endif #ifndef XXH_SIZE_OPT /* default to 1 for -Os or -Oz */ # if (defined(__GNUC__) || defined(__clang__)) && defined(__OPTIMIZE_SIZE__) # define XXH_SIZE_OPT 1 # else # define XXH_SIZE_OPT 0 # endif #endif #ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */ /* don't check on sizeopt, x86, aarch64, or arm when unaligned access is available */ # if XXH_SIZE_OPT >= 1 || \ defined(__i386) || defined(__x86_64__) || defined(__aarch64__) || defined(__ARM_FEATURE_UNALIGNED) \ || defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64) || defined(_M_ARM) /* visual */ # define XXH_FORCE_ALIGN_CHECK 0 # else # define XXH_FORCE_ALIGN_CHECK 1 # endif #endif #ifndef XXH_NO_INLINE_HINTS # if XXH_SIZE_OPT >= 1 || defined(__NO_INLINE__) /* -O0, -fno-inline */ # define XXH_NO_INLINE_HINTS 1 # else # define XXH_NO_INLINE_HINTS 0 # endif #endif #ifndef XXH3_INLINE_SECRET # if (defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 12) \ || !defined(XXH_INLINE_ALL) # define XXH3_INLINE_SECRET 0 # else # define XXH3_INLINE_SECRET 1 # endif #endif #ifndef XXH32_ENDJMP /* generally preferable for performance */ # define XXH32_ENDJMP 0 #endif /*! * @defgroup impl Implementation * @{ */ /* ************************************* * Includes & Memory related functions ***************************************/ #if defined(XXH_NO_STREAM) /* nothing */ #elif defined(XXH_NO_STDLIB) /* When requesting to disable any mention of stdlib, * the library loses the ability to invoked malloc / free. * In practice, it means that functions like `XXH*_createState()` * will always fail, and return NULL. * This flag is useful in situations where * xxhash.h is integrated into some kernel, embedded or limited environment * without access to dynamic allocation. */ static XXH_CONSTF void* XXH_malloc(size_t s) { (void)s; return NULL; } static void XXH_free(void* p) { (void)p; } #else /* * Modify the local functions below should you wish to use * different memory routines for malloc() and free() */ #include /*! * @internal * @brief Modify this function to use a different routine than malloc(). */ static XXH_MALLOCF void* XXH_malloc(size_t s) { return malloc(s); } /*! * @internal * @brief Modify this function to use a different routine than free(). */ static void XXH_free(void* p) { free(p); } #endif /* XXH_NO_STDLIB */ #ifndef XXH_memcpy /*! * @internal * @brief XXH_memcpy() macro can be redirected at compile time */ # include # define XXH_memcpy memcpy #endif #ifndef XXH_memset /*! * @internal * @brief XXH_memset() macro can be redirected at compile time */ # include # define XXH_memset memset #endif #ifndef XXH_memcmp /*! * @internal * @brief XXH_memcmp() macro can be redirected at compile time * Note: only needed by XXH128. */ # include # define XXH_memcmp memcmp #endif #include /* ULLONG_MAX */ /* ************************************* * Compiler Specific Options ***************************************/ #ifdef _MSC_VER /* Visual Studio warning fix */ # pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ #endif #if XXH_NO_INLINE_HINTS /* disable inlining hints */ # if defined(__GNUC__) || defined(__clang__) # define XXH_FORCE_INLINE static __attribute__((__unused__)) # else # define XXH_FORCE_INLINE static # endif # define XXH_NO_INLINE static /* enable inlining hints */ #elif defined(__GNUC__) || defined(__clang__) # define XXH_FORCE_INLINE static __inline__ __attribute__((__always_inline__, __unused__)) # define XXH_NO_INLINE static __attribute__((__noinline__)) #elif defined(_MSC_VER) /* Visual Studio */ # define XXH_FORCE_INLINE static __forceinline # define XXH_NO_INLINE static __declspec(noinline) #elif defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) /* C99 */ # define XXH_FORCE_INLINE static inline # define XXH_NO_INLINE static #else # define XXH_FORCE_INLINE static # define XXH_NO_INLINE static #endif #if defined(XXH_INLINE_ALL) # define XXH_STATIC XXH_FORCE_INLINE #else # define XXH_STATIC static #endif #if XXH3_INLINE_SECRET # define XXH3_WITH_SECRET_INLINE XXH_FORCE_INLINE #else # define XXH3_WITH_SECRET_INLINE XXH_NO_INLINE #endif /* Solaris includes __STDC_VERSION__ with C++. Tested with GCC 5.5 */ #if ((defined(sun) || defined(__sun)) && defined(__cplusplus)) # define XXH_RESTRICT /* disable */ #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* >= C99 */ # define XXH_RESTRICT restrict #elif (defined (__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) \ || (defined (__clang__)) \ || (defined (_MSC_VER) && (_MSC_VER >= 1400)) \ || (defined (__INTEL_COMPILER) && (__INTEL_COMPILER >= 1300)) /* * There are a LOT more compilers that recognize __restrict but this * covers the major ones. */ # define XXH_RESTRICT __restrict #else # define XXH_RESTRICT /* disable */ #endif /* ************************************* * Debug ***************************************/ /*! * @ingroup tuning * @def XXH_DEBUGLEVEL * @brief Sets the debugging level. * * XXH_DEBUGLEVEL is expected to be defined externally, typically via the * compiler's command line options. The value must be a number. */ #ifndef XXH_DEBUGLEVEL # ifdef DEBUGLEVEL /* backwards compat */ # define XXH_DEBUGLEVEL DEBUGLEVEL # else # define XXH_DEBUGLEVEL 0 # endif #endif #if (XXH_DEBUGLEVEL>=1) # include /* note: can still be disabled with NDEBUG */ # define XXH_ASSERT(c) assert(c) #else # if defined(__INTEL_COMPILER) # define XXH_ASSERT(c) XXH_ASSUME((unsigned char) (c)) # else # define XXH_ASSERT(c) XXH_ASSUME(c) # endif #endif /* note: use after variable declarations */ #ifndef XXH_STATIC_ASSERT # if defined(__cplusplus) && (__cplusplus >= 201103L) /* C++11 */ # define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0) # elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */ # define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { _Static_assert((c),m); } while(0) # else # define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { struct xxh_sa { char x[(c) ? 1 : -1]; }; } while(0) # endif # define XXH_STATIC_ASSERT(c) XXH_STATIC_ASSERT_WITH_MESSAGE((c),#c) #endif /*! * @internal * @def XXH_COMPILER_GUARD(var) * @brief Used to prevent unwanted optimizations for @p var. * * It uses an empty GCC inline assembly statement with a register constraint * which forces @p var into a general purpose register (eg eax, ebx, ecx * on x86) and marks it as modified. * * This is used in a few places to avoid unwanted autovectorization (e.g. * XXH32_round()). All vectorization we want is explicit via intrinsics, * and _usually_ isn't wanted elsewhere. * * We also use it to prevent unwanted constant folding for AArch64 in * XXH3_initCustomSecret_scalar(). */ #if defined(__GNUC__) || defined(__clang__) # define XXH_COMPILER_GUARD(var) __asm__("" : "+r" (var)) #else # define XXH_COMPILER_GUARD(var) ((void)0) #endif /* Specifically for NEON vectors which use the "w" constraint, on * Clang. */ #if defined(__clang__) && defined(__ARM_ARCH) && !defined(__wasm__) # define XXH_COMPILER_GUARD_CLANG_NEON(var) __asm__("" : "+w" (var)) #else # define XXH_COMPILER_GUARD_CLANG_NEON(var) ((void)0) #endif /* ************************************* * Basic Types ***************************************/ #if !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) # ifdef _AIX # include # else # include # endif typedef uint8_t xxh_u8; #else typedef unsigned char xxh_u8; #endif typedef XXH32_hash_t xxh_u32; #ifdef XXH_OLD_NAMES # warning "XXH_OLD_NAMES is planned to be removed starting v0.9. If the program depends on it, consider moving away from it by employing newer type names directly" # define BYTE xxh_u8 # define U8 xxh_u8 # define U32 xxh_u32 #endif /* *** Memory access *** */ /*! * @internal * @fn xxh_u32 XXH_read32(const void* ptr) * @brief Reads an unaligned 32-bit integer from @p ptr in native endianness. * * Affected by @ref XXH_FORCE_MEMORY_ACCESS. * * @param ptr The pointer to read from. * @return The 32-bit native endian integer from the bytes at @p ptr. */ /*! * @internal * @fn xxh_u32 XXH_readLE32(const void* ptr) * @brief Reads an unaligned 32-bit little endian integer from @p ptr. * * Affected by @ref XXH_FORCE_MEMORY_ACCESS. * * @param ptr The pointer to read from. * @return The 32-bit little endian integer from the bytes at @p ptr. */ /*! * @internal * @fn xxh_u32 XXH_readBE32(const void* ptr) * @brief Reads an unaligned 32-bit big endian integer from @p ptr. * * Affected by @ref XXH_FORCE_MEMORY_ACCESS. * * @param ptr The pointer to read from. * @return The 32-bit big endian integer from the bytes at @p ptr. */ /*! * @internal * @fn xxh_u32 XXH_readLE32_align(const void* ptr, XXH_alignment align) * @brief Like @ref XXH_readLE32(), but has an option for aligned reads. * * Affected by @ref XXH_FORCE_MEMORY_ACCESS. * Note that when @ref XXH_FORCE_ALIGN_CHECK == 0, the @p align parameter is * always @ref XXH_alignment::XXH_unaligned. * * @param ptr The pointer to read from. * @param align Whether @p ptr is aligned. * @pre * If @p align == @ref XXH_alignment::XXH_aligned, @p ptr must be 4 byte * aligned. * @return The 32-bit little endian integer from the bytes at @p ptr. */ #if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3)) /* * Manual byteshift. Best for old compilers which don't inline memcpy. * We actually directly use XXH_readLE32 and XXH_readBE32. */ #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) /* * Force direct memory access. Only works on CPU which support unaligned memory * access in hardware. */ static xxh_u32 XXH_read32(const void* memPtr) { return *(const xxh_u32*) memPtr; } #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) /* * __attribute__((aligned(1))) is supported by gcc and clang. Originally the * documentation claimed that it only increased the alignment, but actually it * can decrease it on gcc, clang, and icc: * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502, * https://gcc.godbolt.org/z/xYez1j67Y. */ #ifdef XXH_OLD_NAMES typedef union { xxh_u32 u32; } __attribute__((__packed__)) unalign; #endif static xxh_u32 XXH_read32(const void* ptr) { typedef __attribute__((__aligned__(1))) __attribute__((__may_alias__)) xxh_u32 xxh_unalign32; return *((const xxh_unalign32*)ptr); } #else /* * Portable and safe solution. Generally efficient. * see: https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html */ static xxh_u32 XXH_read32(const void* memPtr) { xxh_u32 val; XXH_memcpy(&val, memPtr, sizeof(val)); return val; } #endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ /* *** Endianness *** */ /*! * @ingroup tuning * @def XXH_CPU_LITTLE_ENDIAN * @brief Whether the target is little endian. * * Defined to 1 if the target is little endian, or 0 if it is big endian. * It can be defined externally, for example on the compiler command line. * * If it is not defined, * a runtime check (which is usually constant folded) is used instead. * * @note * This is not necessarily defined to an integer constant. * * @see XXH_isLittleEndian() for the runtime check. */ #ifndef XXH_CPU_LITTLE_ENDIAN /* * Try to detect endianness automatically, to avoid the nonstandard behavior * in `XXH_isLittleEndian()` */ # if defined(_WIN32) /* Windows is always little endian */ \ || defined(__LITTLE_ENDIAN__) \ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) # define XXH_CPU_LITTLE_ENDIAN 1 # elif defined(__BIG_ENDIAN__) \ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) # define XXH_CPU_LITTLE_ENDIAN 0 # else /*! * @internal * @brief Runtime check for @ref XXH_CPU_LITTLE_ENDIAN. * * Most compilers will constant fold this. */ static int XXH_isLittleEndian(void) { /* * Portable and well-defined behavior. * Don't use static: it is detrimental to performance. */ const union { xxh_u32 u; xxh_u8 c[4]; } one = { 1 }; return one.c[0]; } # define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian() # endif #endif /* **************************************** * Compiler-specific Functions and Macros ******************************************/ #define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) #ifdef __has_builtin # define XXH_HAS_BUILTIN(x) __has_builtin(x) #else # define XXH_HAS_BUILTIN(x) 0 #endif /* * C23 and future versions have standard "unreachable()". * Once it has been implemented reliably we can add it as an * additional case: * * ``` * #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 202311L) * # include * # ifdef unreachable * # define XXH_UNREACHABLE() unreachable() * # endif * #endif * ``` * * Note C++23 also has std::unreachable() which can be detected * as follows: * ``` * #if defined(__cpp_lib_unreachable) && (__cpp_lib_unreachable >= 202202L) * # include * # define XXH_UNREACHABLE() std::unreachable() * #endif * ``` * NB: `__cpp_lib_unreachable` is defined in the `` header. * We don't use that as including `` in `extern "C"` blocks * doesn't work on GCC12 */ #if XXH_HAS_BUILTIN(__builtin_unreachable) # define XXH_UNREACHABLE() __builtin_unreachable() #elif defined(_MSC_VER) # define XXH_UNREACHABLE() __assume(0) #else # define XXH_UNREACHABLE() #endif #if XXH_HAS_BUILTIN(__builtin_assume) # define XXH_ASSUME(c) __builtin_assume(c) #else # define XXH_ASSUME(c) if (!(c)) { XXH_UNREACHABLE(); } #endif /*! * @internal * @def XXH_rotl32(x,r) * @brief 32-bit rotate left. * * @param x The 32-bit integer to be rotated. * @param r The number of bits to rotate. * @pre * @p r > 0 && @p r < 32 * @note * @p x and @p r may be evaluated multiple times. * @return The rotated result. */ #if !defined(NO_CLANG_BUILTIN) && XXH_HAS_BUILTIN(__builtin_rotateleft32) \ && XXH_HAS_BUILTIN(__builtin_rotateleft64) # define XXH_rotl32 __builtin_rotateleft32 # define XXH_rotl64 __builtin_rotateleft64 #elif XXH_HAS_BUILTIN(__builtin_stdc_rotate_left) # define XXH_rotl32 __builtin_stdc_rotate_left # define XXH_rotl64 __builtin_stdc_rotate_left /* Note: although _rotl exists for minGW (GCC under windows), performance seems poor */ #elif defined(_MSC_VER) # define XXH_rotl32(x,r) _rotl(x,r) # define XXH_rotl64(x,r) _rotl64(x,r) #else # define XXH_rotl32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) # define XXH_rotl64(x,r) (((x) << (r)) | ((x) >> (64 - (r)))) #endif /*! * @internal * @fn xxh_u32 XXH_swap32(xxh_u32 x) * @brief A 32-bit byteswap. * * @param x The 32-bit integer to byteswap. * @return @p x, byteswapped. */ #if defined(_MSC_VER) /* Visual Studio */ # define XXH_swap32 _byteswap_ulong #elif XXH_GCC_VERSION >= 403 # define XXH_swap32 __builtin_bswap32 #else static xxh_u32 XXH_swap32 (xxh_u32 x) { return ((x << 24) & 0xff000000 ) | ((x << 8) & 0x00ff0000 ) | ((x >> 8) & 0x0000ff00 ) | ((x >> 24) & 0x000000ff ); } #endif /* *************************** * Memory reads *****************************/ /*! * @internal * @brief Enum to indicate whether a pointer is aligned. */ typedef enum { XXH_aligned, /*!< Aligned */ XXH_unaligned /*!< Possibly unaligned */ } XXH_alignment; /* * XXH_FORCE_MEMORY_ACCESS==3 is an endian-independent byteshift load. * * This is ideal for older compilers which don't inline memcpy. */ #if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3)) XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* memPtr) { const xxh_u8* bytePtr = (const xxh_u8 *)memPtr; return bytePtr[0] | ((xxh_u32)bytePtr[1] << 8) | ((xxh_u32)bytePtr[2] << 16) | ((xxh_u32)bytePtr[3] << 24); } XXH_FORCE_INLINE xxh_u32 XXH_readBE32(const void* memPtr) { const xxh_u8* bytePtr = (const xxh_u8 *)memPtr; return bytePtr[3] | ((xxh_u32)bytePtr[2] << 8) | ((xxh_u32)bytePtr[1] << 16) | ((xxh_u32)bytePtr[0] << 24); } #else XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* ptr) { return XXH_CPU_LITTLE_ENDIAN ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr)); } static xxh_u32 XXH_readBE32(const void* ptr) { return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr); } #endif XXH_FORCE_INLINE xxh_u32 XXH_readLE32_align(const void* ptr, XXH_alignment align) { if (align==XXH_unaligned) { return XXH_readLE32(ptr); } else { return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u32*)ptr : XXH_swap32(*(const xxh_u32*)ptr); } } /* ************************************* * Misc ***************************************/ /*! @ingroup public */ XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; } /* ******************************************************************* * 32-bit hash functions *********************************************************************/ /*! * @} * @defgroup XXH32_impl XXH32 implementation * @ingroup impl * * Details on the XXH32 implementation. * @{ */ /* #define instead of static const, to be used as initializers */ #define XXH_PRIME32_1 0x9E3779B1U /*!< 0b10011110001101110111100110110001 */ #define XXH_PRIME32_2 0x85EBCA77U /*!< 0b10000101111010111100101001110111 */ #define XXH_PRIME32_3 0xC2B2AE3DU /*!< 0b11000010101100101010111000111101 */ #define XXH_PRIME32_4 0x27D4EB2FU /*!< 0b00100111110101001110101100101111 */ #define XXH_PRIME32_5 0x165667B1U /*!< 0b00010110010101100110011110110001 */ #ifdef XXH_OLD_NAMES # define PRIME32_1 XXH_PRIME32_1 # define PRIME32_2 XXH_PRIME32_2 # define PRIME32_3 XXH_PRIME32_3 # define PRIME32_4 XXH_PRIME32_4 # define PRIME32_5 XXH_PRIME32_5 #endif /*! * @internal * @brief Normal stripe processing routine. * * This shuffles the bits so that any bit from @p input impacts several bits in * @p acc. * * @param acc The accumulator lane. * @param input The stripe of input to mix. * @return The mixed accumulator lane. */ static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input) { acc += input * XXH_PRIME32_2; acc = XXH_rotl32(acc, 13); acc *= XXH_PRIME32_1; #if (defined(__SSE4_1__) || defined(__aarch64__) || defined(__wasm_simd128__)) && !defined(XXH_ENABLE_AUTOVECTORIZE) /* * UGLY HACK: * A compiler fence is used to prevent GCC and Clang from * autovectorizing the XXH32 loop (pragmas and attributes don't work for some * reason) without globally disabling SSE4.1. * * The reason we want to avoid vectorization is because despite working on * 4 integers at a time, there are multiple factors slowing XXH32 down on * SSE4: * - There's a ridiculous amount of lag from pmulld (10 cycles of latency on * newer chips!) making it slightly slower to multiply four integers at * once compared to four integers independently. Even when pmulld was * fastest, Sandy/Ivy Bridge, it is still not worth it to go into SSE * just to multiply unless doing a long operation. * * - Four instructions are required to rotate, * movqda tmp, v // not required with VEX encoding * pslld tmp, 13 // tmp <<= 13 * psrld v, 19 // x >>= 19 * por v, tmp // x |= tmp * compared to one for scalar: * roll v, 13 // reliably fast across the board * shldl v, v, 13 // Sandy Bridge and later prefer this for some reason * * - Instruction level parallelism is actually more beneficial here because * the SIMD actually serializes this operation: While v1 is rotating, v2 * can load data, while v3 can multiply. SSE forces them to operate * together. * * This is also enabled on AArch64, as Clang is *very aggressive* in vectorizing * the loop. NEON is only faster on the A53, and with the newer cores, it is less * than half the speed. * * Additionally, this is used on WASM SIMD128 because it JITs to the same * SIMD instructions and has the same issue. */ XXH_COMPILER_GUARD(acc); #endif return acc; } /*! * @internal * @brief Mixes all bits to finalize the hash. * * The final mix ensures that all input bits have a chance to impact any bit in * the output digest, resulting in an unbiased distribution. * * @param hash The hash to avalanche. * @return The avalanched hash. */ static xxh_u32 XXH32_avalanche(xxh_u32 hash) { hash ^= hash >> 15; hash *= XXH_PRIME32_2; hash ^= hash >> 13; hash *= XXH_PRIME32_3; hash ^= hash >> 16; return hash; } #define XXH_get32bits(p) XXH_readLE32_align(p, align) /*! * @internal * @brief Sets up the initial accumulator state for XXH32(). */ XXH_FORCE_INLINE void XXH32_initAccs(xxh_u32 *acc, xxh_u32 seed) { XXH_ASSERT(acc != NULL); acc[0] = seed + XXH_PRIME32_1 + XXH_PRIME32_2; acc[1] = seed + XXH_PRIME32_2; acc[2] = seed + 0; acc[3] = seed - XXH_PRIME32_1; } /*! * @internal * @brief Consumes a block of data for XXH32(). * * @return the end input pointer. */ XXH_FORCE_INLINE const xxh_u8 * XXH32_consumeLong( xxh_u32 *XXH_RESTRICT acc, xxh_u8 const *XXH_RESTRICT input, size_t len, XXH_alignment align ) { const xxh_u8* const bEnd = input + len; const xxh_u8* const limit = bEnd - 15; XXH_ASSERT(acc != NULL); XXH_ASSERT(input != NULL); XXH_ASSERT(len >= 16); do { acc[0] = XXH32_round(acc[0], XXH_get32bits(input)); input += 4; acc[1] = XXH32_round(acc[1], XXH_get32bits(input)); input += 4; acc[2] = XXH32_round(acc[2], XXH_get32bits(input)); input += 4; acc[3] = XXH32_round(acc[3], XXH_get32bits(input)); input += 4; } while (input < limit); return input; } /*! * @internal * @brief Merges the accumulator lanes together for XXH32() */ XXH_FORCE_INLINE XXH_PUREF xxh_u32 XXH32_mergeAccs(const xxh_u32 *acc) { XXH_ASSERT(acc != NULL); return XXH_rotl32(acc[0], 1) + XXH_rotl32(acc[1], 7) + XXH_rotl32(acc[2], 12) + XXH_rotl32(acc[3], 18); } /*! * @internal * @brief Processes the last 0-15 bytes of @p ptr. * * There may be up to 15 bytes remaining to consume from the input. * This final stage will digest them to ensure that all input bytes are present * in the final mix. * * @param hash The hash to finalize. * @param ptr The pointer to the remaining input. * @param len The remaining length, modulo 16. * @param align Whether @p ptr is aligned. * @return The finalized hash. * @see XXH64_finalize(). */ static XXH_PUREF xxh_u32 XXH32_finalize(xxh_u32 hash, const xxh_u8* ptr, size_t len, XXH_alignment align) { #define XXH_PROCESS1 do { \ hash += (*ptr++) * XXH_PRIME32_5; \ hash = XXH_rotl32(hash, 11) * XXH_PRIME32_1; \ } while (0) #define XXH_PROCESS4 do { \ hash += XXH_get32bits(ptr) * XXH_PRIME32_3; \ ptr += 4; \ hash = XXH_rotl32(hash, 17) * XXH_PRIME32_4; \ } while (0) if (ptr==NULL) XXH_ASSERT(len == 0); /* Compact rerolled version; generally faster */ if (!XXH32_ENDJMP) { len &= 15; while (len >= 4) { XXH_PROCESS4; len -= 4; } while (len > 0) { XXH_PROCESS1; --len; } return XXH32_avalanche(hash); } else { switch(len&15) /* or switch(bEnd - p) */ { case 12: XXH_PROCESS4; XXH_FALLTHROUGH; /* fallthrough */ case 8: XXH_PROCESS4; XXH_FALLTHROUGH; /* fallthrough */ case 4: XXH_PROCESS4; return XXH32_avalanche(hash); case 13: XXH_PROCESS4; XXH_FALLTHROUGH; /* fallthrough */ case 9: XXH_PROCESS4; XXH_FALLTHROUGH; /* fallthrough */ case 5: XXH_PROCESS4; XXH_PROCESS1; return XXH32_avalanche(hash); case 14: XXH_PROCESS4; XXH_FALLTHROUGH; /* fallthrough */ case 10: XXH_PROCESS4; XXH_FALLTHROUGH; /* fallthrough */ case 6: XXH_PROCESS4; XXH_PROCESS1; XXH_PROCESS1; return XXH32_avalanche(hash); case 15: XXH_PROCESS4; XXH_FALLTHROUGH; /* fallthrough */ case 11: XXH_PROCESS4; XXH_FALLTHROUGH; /* fallthrough */ case 7: XXH_PROCESS4; XXH_FALLTHROUGH; /* fallthrough */ case 3: XXH_PROCESS1; XXH_FALLTHROUGH; /* fallthrough */ case 2: XXH_PROCESS1; XXH_FALLTHROUGH; /* fallthrough */ case 1: XXH_PROCESS1; XXH_FALLTHROUGH; /* fallthrough */ case 0: return XXH32_avalanche(hash); } XXH_ASSERT(0); return hash; /* reaching this point is deemed impossible */ } } #ifdef XXH_OLD_NAMES # define PROCESS1 XXH_PROCESS1 # define PROCESS4 XXH_PROCESS4 #else # undef XXH_PROCESS1 # undef XXH_PROCESS4 #endif /*! * @internal * @brief The implementation for @ref XXH32(). * * @param input , len , seed Directly passed from @ref XXH32(). * @param align Whether @p input is aligned. * @return The calculated hash. */ XXH_FORCE_INLINE XXH_PUREF xxh_u32 XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment align) { xxh_u32 h32; if (input==NULL) XXH_ASSERT(len == 0); if (len>=16) { xxh_u32 acc[4]; XXH32_initAccs(acc, seed); input = XXH32_consumeLong(acc, input, len, align); h32 = XXH32_mergeAccs(acc); } else { h32 = seed + XXH_PRIME32_5; } h32 += (xxh_u32)len; return XXH32_finalize(h32, input, len&15, align); } /*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t seed) { #if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2 /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ XXH32_state_t state; XXH32_reset(&state, seed); XXH32_update(&state, (const xxh_u8*)input, len); return XXH32_digest(&state); #else if (XXH_FORCE_ALIGN_CHECK) { if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */ return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_aligned); } } return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_unaligned); #endif } /******* Hash streaming *******/ #ifndef XXH_NO_STREAM /*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void) { return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t)); } /*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr) { XXH_free(statePtr); return XXH_OK; } /*! @ingroup XXH32_family */ XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState) { XXH_memcpy(dstState, srcState, sizeof(*dstState)); } /*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, XXH32_hash_t seed) { XXH_ASSERT(statePtr != NULL); XXH_memset(statePtr, 0, sizeof(*statePtr)); XXH32_initAccs(statePtr->acc, seed); return XXH_OK; } /*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH_errorcode XXH32_update(XXH32_state_t* state, const void* input, size_t len) { if (input==NULL) { XXH_ASSERT(len == 0); return XXH_OK; } state->total_len_32 += (XXH32_hash_t)len; state->large_len |= (XXH32_hash_t)((len>=16) | (state->total_len_32>=16)); XXH_ASSERT(state->bufferedSize < sizeof(state->buffer)); if (len < sizeof(state->buffer) - state->bufferedSize) { /* fill in tmp buffer */ XXH_memcpy(state->buffer + state->bufferedSize, input, len); state->bufferedSize += (XXH32_hash_t)len; return XXH_OK; } { const xxh_u8* xinput = (const xxh_u8*)input; const xxh_u8* const bEnd = xinput + len; if (state->bufferedSize) { /* non-empty buffer: complete first */ XXH_memcpy(state->buffer + state->bufferedSize, xinput, sizeof(state->buffer) - state->bufferedSize); xinput += sizeof(state->buffer) - state->bufferedSize; /* then process one round */ (void)XXH32_consumeLong(state->acc, state->buffer, sizeof(state->buffer), XXH_aligned); state->bufferedSize = 0; } XXH_ASSERT(xinput <= bEnd); if ((size_t)(bEnd - xinput) >= sizeof(state->buffer)) { /* Process the remaining data */ xinput = XXH32_consumeLong(state->acc, xinput, (size_t)(bEnd - xinput), XXH_unaligned); } if (xinput < bEnd) { /* Copy the leftover to the tmp buffer */ XXH_memcpy(state->buffer, xinput, (size_t)(bEnd-xinput)); state->bufferedSize = (unsigned)(bEnd-xinput); } } return XXH_OK; } /*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* state) { xxh_u32 h32; if (state->large_len) { h32 = XXH32_mergeAccs(state->acc); } else { h32 = state->acc[2] /* == seed */ + XXH_PRIME32_5; } h32 += state->total_len_32; return XXH32_finalize(h32, state->buffer, state->bufferedSize, XXH_aligned); } #endif /* !XXH_NO_STREAM */ /******* Canonical representation *******/ /*! @ingroup XXH32_family */ XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash) { XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t)); if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash); XXH_memcpy(dst, &hash, sizeof(*dst)); } /*! @ingroup XXH32_family */ XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src) { return XXH_readBE32(src); } #ifndef XXH_NO_LONG_LONG /* ******************************************************************* * 64-bit hash functions *********************************************************************/ /*! * @} * @ingroup impl * @{ */ /******* Memory access *******/ typedef XXH64_hash_t xxh_u64; #ifdef XXH_OLD_NAMES # define U64 xxh_u64 #endif #if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3)) /* * Manual byteshift. Best for old compilers which don't inline memcpy. * We actually directly use XXH_readLE64 and XXH_readBE64. */ #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2)) /* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */ static xxh_u64 XXH_read64(const void* memPtr) { return *(const xxh_u64*) memPtr; } #elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1)) /* * __attribute__((aligned(1))) is supported by gcc and clang. Originally the * documentation claimed that it only increased the alignment, but actually it * can decrease it on gcc, clang, and icc: * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502, * https://gcc.godbolt.org/z/xYez1j67Y. */ #ifdef XXH_OLD_NAMES typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((__packed__)) unalign64; #endif static xxh_u64 XXH_read64(const void* ptr) { typedef __attribute__((__aligned__(1))) __attribute__((__may_alias__)) xxh_u64 xxh_unalign64; return *((const xxh_unalign64*)ptr); } #else /* * Portable and safe solution. Generally efficient. * see: https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html */ static xxh_u64 XXH_read64(const void* memPtr) { xxh_u64 val; XXH_memcpy(&val, memPtr, sizeof(val)); return val; } #endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */ #if defined(_MSC_VER) /* Visual Studio */ # define XXH_swap64 _byteswap_uint64 #elif XXH_GCC_VERSION >= 403 # define XXH_swap64 __builtin_bswap64 #else static xxh_u64 XXH_swap64(xxh_u64 x) { return ((x << 56) & 0xff00000000000000ULL) | ((x << 40) & 0x00ff000000000000ULL) | ((x << 24) & 0x0000ff0000000000ULL) | ((x << 8) & 0x000000ff00000000ULL) | ((x >> 8) & 0x00000000ff000000ULL) | ((x >> 24) & 0x0000000000ff0000ULL) | ((x >> 40) & 0x000000000000ff00ULL) | ((x >> 56) & 0x00000000000000ffULL); } #endif /* XXH_FORCE_MEMORY_ACCESS==3 is an endian-independent byteshift load. */ #if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3)) XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* memPtr) { const xxh_u8* bytePtr = (const xxh_u8 *)memPtr; return bytePtr[0] | ((xxh_u64)bytePtr[1] << 8) | ((xxh_u64)bytePtr[2] << 16) | ((xxh_u64)bytePtr[3] << 24) | ((xxh_u64)bytePtr[4] << 32) | ((xxh_u64)bytePtr[5] << 40) | ((xxh_u64)bytePtr[6] << 48) | ((xxh_u64)bytePtr[7] << 56); } XXH_FORCE_INLINE xxh_u64 XXH_readBE64(const void* memPtr) { const xxh_u8* bytePtr = (const xxh_u8 *)memPtr; return bytePtr[7] | ((xxh_u64)bytePtr[6] << 8) | ((xxh_u64)bytePtr[5] << 16) | ((xxh_u64)bytePtr[4] << 24) | ((xxh_u64)bytePtr[3] << 32) | ((xxh_u64)bytePtr[2] << 40) | ((xxh_u64)bytePtr[1] << 48) | ((xxh_u64)bytePtr[0] << 56); } #else XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* ptr) { return XXH_CPU_LITTLE_ENDIAN ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr)); } static xxh_u64 XXH_readBE64(const void* ptr) { return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr); } #endif XXH_FORCE_INLINE xxh_u64 XXH_readLE64_align(const void* ptr, XXH_alignment align) { if (align==XXH_unaligned) return XXH_readLE64(ptr); else return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u64*)ptr : XXH_swap64(*(const xxh_u64*)ptr); } /******* xxh64 *******/ /*! * @} * @defgroup XXH64_impl XXH64 implementation * @ingroup impl * * Details on the XXH64 implementation. * @{ */ /* #define rather that static const, to be used as initializers */ #define XXH_PRIME64_1 0x9E3779B185EBCA87ULL /*!< 0b1001111000110111011110011011000110000101111010111100101010000111 */ #define XXH_PRIME64_2 0xC2B2AE3D27D4EB4FULL /*!< 0b1100001010110010101011100011110100100111110101001110101101001111 */ #define XXH_PRIME64_3 0x165667B19E3779F9ULL /*!< 0b0001011001010110011001111011000110011110001101110111100111111001 */ #define XXH_PRIME64_4 0x85EBCA77C2B2AE63ULL /*!< 0b1000010111101011110010100111011111000010101100101010111001100011 */ #define XXH_PRIME64_5 0x27D4EB2F165667C5ULL /*!< 0b0010011111010100111010110010111100010110010101100110011111000101 */ #ifdef XXH_OLD_NAMES # define PRIME64_1 XXH_PRIME64_1 # define PRIME64_2 XXH_PRIME64_2 # define PRIME64_3 XXH_PRIME64_3 # define PRIME64_4 XXH_PRIME64_4 # define PRIME64_5 XXH_PRIME64_5 #endif /*! @copydoc XXH32_round */ static xxh_u64 XXH64_round(xxh_u64 acc, xxh_u64 input) { acc += input * XXH_PRIME64_2; acc = XXH_rotl64(acc, 31); acc *= XXH_PRIME64_1; #if (defined(__AVX512F__)) && !defined(XXH_ENABLE_AUTOVECTORIZE) /* * DISABLE AUTOVECTORIZATION: * A compiler fence is used to prevent GCC and Clang from * autovectorizing the XXH64 loop (pragmas and attributes don't work for some * reason) without globally disabling AVX512. * * Autovectorization of XXH64 tends to be detrimental, * though the exact outcome may change depending on exact cpu and compiler version. * For information, it has been reported as detrimental for Skylake-X, * but possibly beneficial for Zen4. * * The default is to disable auto-vectorization, * but you can select to enable it instead using `XXH_ENABLE_AUTOVECTORIZE` build variable. */ XXH_COMPILER_GUARD(acc); #endif return acc; } static xxh_u64 XXH64_mergeRound(xxh_u64 acc, xxh_u64 val) { val = XXH64_round(0, val); acc ^= val; acc = acc * XXH_PRIME64_1 + XXH_PRIME64_4; return acc; } /*! @copydoc XXH32_avalanche */ static xxh_u64 XXH64_avalanche(xxh_u64 hash) { hash ^= hash >> 33; hash *= XXH_PRIME64_2; hash ^= hash >> 29; hash *= XXH_PRIME64_3; hash ^= hash >> 32; return hash; } #define XXH_get64bits(p) XXH_readLE64_align(p, align) /*! * @internal * @brief Sets up the initial accumulator state for XXH64(). */ XXH_FORCE_INLINE void XXH64_initAccs(xxh_u64 *acc, xxh_u64 seed) { XXH_ASSERT(acc != NULL); acc[0] = seed + XXH_PRIME64_1 + XXH_PRIME64_2; acc[1] = seed + XXH_PRIME64_2; acc[2] = seed + 0; acc[3] = seed - XXH_PRIME64_1; } /*! * @internal * @brief Consumes a block of data for XXH64(). * * @return the end input pointer. */ XXH_FORCE_INLINE const xxh_u8 * XXH64_consumeLong( xxh_u64 *XXH_RESTRICT acc, xxh_u8 const *XXH_RESTRICT input, size_t len, XXH_alignment align ) { const xxh_u8* const bEnd = input + len; const xxh_u8* const limit = bEnd - 31; XXH_ASSERT(acc != NULL); XXH_ASSERT(input != NULL); XXH_ASSERT(len >= 32); do { /* reroll on 32-bit */ if (sizeof(void *) < sizeof(xxh_u64)) { size_t i; for (i = 0; i < 4; i++) { acc[i] = XXH64_round(acc[i], XXH_get64bits(input)); input += 8; } } else { acc[0] = XXH64_round(acc[0], XXH_get64bits(input)); input += 8; acc[1] = XXH64_round(acc[1], XXH_get64bits(input)); input += 8; acc[2] = XXH64_round(acc[2], XXH_get64bits(input)); input += 8; acc[3] = XXH64_round(acc[3], XXH_get64bits(input)); input += 8; } } while (input < limit); return input; } /*! * @internal * @brief Merges the accumulator lanes together for XXH64() */ XXH_FORCE_INLINE XXH_PUREF xxh_u64 XXH64_mergeAccs(const xxh_u64 *acc) { XXH_ASSERT(acc != NULL); { xxh_u64 h64 = XXH_rotl64(acc[0], 1) + XXH_rotl64(acc[1], 7) + XXH_rotl64(acc[2], 12) + XXH_rotl64(acc[3], 18); /* reroll on 32-bit */ if (sizeof(void *) < sizeof(xxh_u64)) { size_t i; for (i = 0; i < 4; i++) { h64 = XXH64_mergeRound(h64, acc[i]); } } else { h64 = XXH64_mergeRound(h64, acc[0]); h64 = XXH64_mergeRound(h64, acc[1]); h64 = XXH64_mergeRound(h64, acc[2]); h64 = XXH64_mergeRound(h64, acc[3]); } return h64; } } /*! * @internal * @brief Processes the last 0-31 bytes of @p ptr. * * There may be up to 31 bytes remaining to consume from the input. * This final stage will digest them to ensure that all input bytes are present * in the final mix. * * @param hash The hash to finalize. * @param ptr The pointer to the remaining input. * @param len The remaining length, modulo 32. * @param align Whether @p ptr is aligned. * @return The finalized hash * @see XXH32_finalize(). */ XXH_STATIC XXH_PUREF xxh_u64 XXH64_finalize(xxh_u64 hash, const xxh_u8* ptr, size_t len, XXH_alignment align) { if (ptr==NULL) XXH_ASSERT(len == 0); len &= 31; while (len >= 8) { xxh_u64 const k1 = XXH64_round(0, XXH_get64bits(ptr)); ptr += 8; hash ^= k1; hash = XXH_rotl64(hash,27) * XXH_PRIME64_1 + XXH_PRIME64_4; len -= 8; } if (len >= 4) { hash ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1; ptr += 4; hash = XXH_rotl64(hash, 23) * XXH_PRIME64_2 + XXH_PRIME64_3; len -= 4; } while (len > 0) { hash ^= (*ptr++) * XXH_PRIME64_5; hash = XXH_rotl64(hash, 11) * XXH_PRIME64_1; --len; } return XXH64_avalanche(hash); } #ifdef XXH_OLD_NAMES # define PROCESS1_64 XXH_PROCESS1_64 # define PROCESS4_64 XXH_PROCESS4_64 # define PROCESS8_64 XXH_PROCESS8_64 #else # undef XXH_PROCESS1_64 # undef XXH_PROCESS4_64 # undef XXH_PROCESS8_64 #endif /*! * @internal * @brief The implementation for @ref XXH64(). * * @param input , len , seed Directly passed from @ref XXH64(). * @param align Whether @p input is aligned. * @return The calculated hash. */ XXH_FORCE_INLINE XXH_PUREF xxh_u64 XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment align) { xxh_u64 h64; if (input==NULL) XXH_ASSERT(len == 0); if (len>=32) { /* Process a large block of data */ xxh_u64 acc[4]; XXH64_initAccs(acc, seed); input = XXH64_consumeLong(acc, input, len, align); h64 = XXH64_mergeAccs(acc); } else { h64 = seed + XXH_PRIME64_5; } h64 += (xxh_u64) len; return XXH64_finalize(h64, input, len, align); } /*! @ingroup XXH64_family */ XXH_PUBLIC_API XXH64_hash_t XXH64 (XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed) { #if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2 /* Simple version, good for code maintenance, but unfortunately slow for small inputs */ XXH64_state_t state; XXH64_reset(&state, seed); XXH64_update(&state, (const xxh_u8*)input, len); return XXH64_digest(&state); #else if (XXH_FORCE_ALIGN_CHECK) { if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */ return XXH64_endian_align((const xxh_u8*)input, len, seed, XXH_aligned); } } return XXH64_endian_align((const xxh_u8*)input, len, seed, XXH_unaligned); #endif } /******* Hash Streaming *******/ #ifndef XXH_NO_STREAM /*! @ingroup XXH64_family*/ XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void) { return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t)); } /*! @ingroup XXH64_family */ XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr) { XXH_free(statePtr); return XXH_OK; } /*! @ingroup XXH64_family */ XXH_PUBLIC_API void XXH64_copyState(XXH_NOESCAPE XXH64_state_t* dstState, const XXH64_state_t* srcState) { XXH_memcpy(dstState, srcState, sizeof(*dstState)); } /*! @ingroup XXH64_family */ XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH_NOESCAPE XXH64_state_t* statePtr, XXH64_hash_t seed) { XXH_ASSERT(statePtr != NULL); XXH_memset(statePtr, 0, sizeof(*statePtr)); XXH64_initAccs(statePtr->acc, seed); return XXH_OK; } /*! @ingroup XXH64_family */ XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH_NOESCAPE XXH64_state_t* state, XXH_NOESCAPE const void* input, size_t len) { if (input==NULL) { XXH_ASSERT(len == 0); return XXH_OK; } state->total_len += len; XXH_ASSERT(state->bufferedSize <= sizeof(state->buffer)); if (len < sizeof(state->buffer) - state->bufferedSize) { /* fill in tmp buffer */ XXH_memcpy(state->buffer + state->bufferedSize, input, len); state->bufferedSize += (XXH32_hash_t)len; return XXH_OK; } { const xxh_u8* xinput = (const xxh_u8*)input; const xxh_u8* const bEnd = xinput + len; if (state->bufferedSize) { /* non-empty buffer => complete first */ XXH_memcpy(state->buffer + state->bufferedSize, xinput, sizeof(state->buffer) - state->bufferedSize); xinput += sizeof(state->buffer) - state->bufferedSize; /* and process one round */ (void)XXH64_consumeLong(state->acc, state->buffer, sizeof(state->buffer), XXH_aligned); state->bufferedSize = 0; } XXH_ASSERT(xinput <= bEnd); if ((size_t)(bEnd - xinput) >= sizeof(state->buffer)) { /* Process the remaining data */ xinput = XXH64_consumeLong(state->acc, xinput, (size_t)(bEnd - xinput), XXH_unaligned); } if (xinput < bEnd) { /* Copy the leftover to the tmp buffer */ XXH_memcpy(state->buffer, xinput, (size_t)(bEnd-xinput)); state->bufferedSize = (unsigned)(bEnd-xinput); } } return XXH_OK; } /*! @ingroup XXH64_family */ XXH_PUBLIC_API XXH64_hash_t XXH64_digest(XXH_NOESCAPE const XXH64_state_t* state) { xxh_u64 h64; if (state->total_len >= 32) { h64 = XXH64_mergeAccs(state->acc); } else { h64 = state->acc[2] /*seed*/ + XXH_PRIME64_5; } h64 += (xxh_u64) state->total_len; return XXH64_finalize(h64, state->buffer, (size_t)state->total_len, XXH_aligned); } #endif /* !XXH_NO_STREAM */ /******* Canonical representation *******/ /*! @ingroup XXH64_family */ XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH_NOESCAPE XXH64_canonical_t* dst, XXH64_hash_t hash) { XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t)); if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash); XXH_memcpy(dst, &hash, sizeof(*dst)); } /*! @ingroup XXH64_family */ XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(XXH_NOESCAPE const XXH64_canonical_t* src) { return XXH_readBE64(src); } #ifndef XXH_NO_XXH3 /* ********************************************************************* * XXH3 * New generation hash designed for speed on small keys and vectorization ************************************************************************ */ /*! * @} * @defgroup XXH3_impl XXH3 implementation * @ingroup impl * @{ */ /* === Compiler specifics === */ #if (defined(__GNUC__) && (__GNUC__ >= 3)) \ || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) \ || defined(__clang__) # define XXH_likely(x) __builtin_expect(x, 1) # define XXH_unlikely(x) __builtin_expect(x, 0) #else # define XXH_likely(x) (x) # define XXH_unlikely(x) (x) #endif #ifndef XXH_HAS_INCLUDE # ifdef __has_include /* * Not defined as XXH_HAS_INCLUDE(x) (function-like) because * this causes segfaults in Apple Clang 4.2 (on Mac OS X 10.7 Lion) */ # define XXH_HAS_INCLUDE __has_include # else # define XXH_HAS_INCLUDE(x) 0 # endif #endif #if defined(__GNUC__) || defined(__clang__) # if defined(__ARM_FEATURE_SVE) # include # endif # if defined(__ARM_NEON__) || defined(__ARM_NEON) \ || (defined(_M_ARM) && _M_ARM >= 7) \ || defined(_M_ARM64) || defined(_M_ARM64EC) \ || (defined(__wasm_simd128__) && XXH_HAS_INCLUDE()) /* WASM SIMD128 via SIMDe */ # define inline __inline__ /* circumvent a clang bug */ # include # undef inline # elif defined(__AVX2__) # include # elif defined(__SSE2__) # include # elif defined(__loongarch_asx) # include # include # elif defined(__loongarch_sx) # include # elif defined(__riscv_vector) # include # endif #endif #if defined(_MSC_VER) # include #endif /* * One goal of XXH3 is to make it fast on both 32-bit and 64-bit, while * remaining a true 64-bit/128-bit hash function. * * This is done by prioritizing a subset of 64-bit operations that can be * emulated without too many steps on the average 32-bit machine. * * For example, these two lines seem similar, and run equally fast on 64-bit: * * xxh_u64 x; * x ^= (x >> 47); // good * x ^= (x >> 13); // bad * * However, to a 32-bit machine, there is a major difference. * * x ^= (x >> 47) looks like this: * * x.lo ^= (x.hi >> (47 - 32)); * * while x ^= (x >> 13) looks like this: * * // note: funnel shifts are not usually cheap. * x.lo ^= (x.lo >> 13) | (x.hi << (32 - 13)); * x.hi ^= (x.hi >> 13); * * The first one is significantly faster than the second, simply because the * shift is larger than 32. This means: * - All the bits we need are in the upper 32 bits, so we can ignore the lower * 32 bits in the shift. * - The shift result will always fit in the lower 32 bits, and therefore, * we can ignore the upper 32 bits in the xor. * * Thanks to this optimization, XXH3 only requires these features to be efficient: * * - Usable unaligned access * - A 32-bit or 64-bit ALU * - If 32-bit, a decent ADC instruction * - A 32 or 64-bit multiply with a 64-bit result * - For the 128-bit variant, a decent byteswap helps short inputs. * * The first two are already required by XXH32, and almost all 32-bit and 64-bit * platforms which can run XXH32 can run XXH3 efficiently. * * Thumb-1, the classic 16-bit only subset of ARM's instruction set, is one * notable exception. * * First of all, Thumb-1 lacks support for the UMULL instruction which * performs the important long multiply. This means numerous __aeabi_lmul * calls. * * Second of all, the 8 functional registers are just not enough. * Setup for __aeabi_lmul, byteshift loads, pointers, and all arithmetic need * Lo registers, and this shuffling results in thousands more MOVs than A32. * * A32 and T32 don't have this limitation. They can access all 14 registers, * do a 32->64 multiply with UMULL, and the flexible operand allowing free * shifts is helpful, too. * * Therefore, we do a quick sanity check. * * If compiling Thumb-1 for a target which supports ARM instructions, we will * emit a warning, as it is not a "sane" platform to compile for. * * Usually, if this happens, it is because of an accident and you probably need * to specify -march, as you likely meant to compile for a newer architecture. * * Credit: large sections of the vectorial and asm source code paths * have been contributed by @easyaspi314 */ #if defined(__thumb__) && !defined(__thumb2__) && defined(__ARM_ARCH_ISA_ARM) # warning "XXH3 is highly inefficient without ARM or Thumb-2." #endif /* ========================================== * Vectorization detection * ========================================== */ #ifdef XXH_DOXYGEN /*! * @ingroup tuning * @brief Overrides the vectorization implementation chosen for XXH3. * * Can be defined to 0 to disable SIMD, * or any other authorized value of @ref XXH_VECTOR. * * If this is not defined, it uses predefined macros to determine the best * implementation. */ # define XXH_VECTOR XXH_SCALAR /*! * @ingroup tuning * @brief Selects the minimum alignment for XXH3's accumulators. * * When using SIMD, this should match the alignment required for said vector * type, so, for example, 32 for AVX2. * * Default: Auto detected. */ # define XXH_ACC_ALIGN 8 #endif /* Actual definition */ #ifndef XXH_DOXYGEN #endif #ifndef XXH_VECTOR /* can be defined on command line */ # if ( \ defined(__ARM_NEON__) || defined(__ARM_NEON) /* gcc */ \ || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) /* msvc */ \ || (defined(__wasm_simd128__) && XXH_HAS_INCLUDE()) /* wasm simd128 via SIMDe */ \ ) && ( \ defined(_WIN32) || defined(__LITTLE_ENDIAN__) /* little endian only */ \ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) \ ) # define XXH_VECTOR XXH_NEON # elif defined(__ARM_FEATURE_SVE) # define XXH_VECTOR XXH_SVE # elif defined(__AVX512F__) # define XXH_VECTOR XXH_AVX512 # elif defined(__AVX2__) # define XXH_VECTOR XXH_AVX2 # elif defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP == 2)) # define XXH_VECTOR XXH_SSE2 # elif (defined(__PPC64__) && defined(__POWER8_VECTOR__)) \ || (defined(__s390x__) && defined(__VEC__)) \ && defined(__GNUC__) /* TODO: IBM XL */ # define XXH_VECTOR XXH_VSX # elif defined(__loongarch_asx) # define XXH_VECTOR XXH_LASX # elif defined(__loongarch_sx) # define XXH_VECTOR XXH_LSX # elif defined(__riscv_vector) # define XXH_VECTOR XXH_RVV # else # define XXH_VECTOR XXH_SCALAR # endif #endif /* __ARM_FEATURE_SVE is only supported by GCC & Clang. */ #if (XXH_VECTOR == XXH_SVE) && !defined(__ARM_FEATURE_SVE) # ifdef _MSC_VER # pragma warning(once : 4606) # else # warning "__ARM_FEATURE_SVE isn't supported. Use SCALAR instead." # endif # undef XXH_VECTOR # define XXH_VECTOR XXH_SCALAR #endif /* * Controls the alignment of the accumulator, * for compatibility with aligned vector loads, which are usually faster. */ #ifndef XXH_ACC_ALIGN # if defined(XXH_X86DISPATCH) # define XXH_ACC_ALIGN 64 /* for compatibility with avx512 */ # elif XXH_VECTOR == XXH_SCALAR /* scalar */ # define XXH_ACC_ALIGN 8 # elif XXH_VECTOR == XXH_SSE2 /* sse2 */ # define XXH_ACC_ALIGN 16 # elif XXH_VECTOR == XXH_AVX2 /* avx2 */ # define XXH_ACC_ALIGN 32 # elif XXH_VECTOR == XXH_NEON /* neon */ # define XXH_ACC_ALIGN 16 # elif XXH_VECTOR == XXH_VSX /* vsx */ # define XXH_ACC_ALIGN 16 # elif XXH_VECTOR == XXH_AVX512 /* avx512 */ # define XXH_ACC_ALIGN 64 # elif XXH_VECTOR == XXH_SVE /* sve */ # define XXH_ACC_ALIGN 64 # elif XXH_VECTOR == XXH_LASX /* lasx */ # define XXH_ACC_ALIGN 64 # elif XXH_VECTOR == XXH_LSX /* lsx */ # define XXH_ACC_ALIGN 64 # elif XXH_VECTOR == XXH_RVV /* rvv */ # define XXH_ACC_ALIGN 64 /* could be 8, but 64 may be faster */ # endif #endif #if defined(XXH_X86DISPATCH) || XXH_VECTOR == XXH_SSE2 \ || XXH_VECTOR == XXH_AVX2 || XXH_VECTOR == XXH_AVX512 # define XXH_SEC_ALIGN XXH_ACC_ALIGN #elif XXH_VECTOR == XXH_SVE # define XXH_SEC_ALIGN XXH_ACC_ALIGN #elif XXH_VECTOR == XXH_RVV # define XXH_SEC_ALIGN XXH_ACC_ALIGN #else # define XXH_SEC_ALIGN 8 #endif #if defined(__GNUC__) || defined(__clang__) # define XXH_ALIASING __attribute__((__may_alias__)) #else # define XXH_ALIASING /* nothing */ #endif /* * UGLY HACK: * GCC usually generates the best code with -O3 for xxHash. * * However, when targeting AVX2, it is overzealous in its unrolling resulting * in code roughly 3/4 the speed of Clang. * * There are other issues, such as GCC splitting _mm256_loadu_si256 into * _mm_loadu_si128 + _mm256_inserti128_si256. This is an optimization which * only applies to Sandy and Ivy Bridge... which don't even support AVX2. * * That is why when compiling the AVX2 version, it is recommended to use either * -O2 -mavx2 -march=haswell * or * -O2 -mavx2 -mno-avx256-split-unaligned-load * for decent performance, or to use Clang instead. * * Fortunately, we can control the first one with a pragma that forces GCC into * -O2, but the other one we can't control without "failed to inline always * inline function due to target mismatch" warnings. */ #if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \ && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \ && defined(__OPTIMIZE__) && XXH_SIZE_OPT <= 0 /* respect -O0 and -Os */ # pragma GCC push_options # pragma GCC optimize("-O2") #endif #if XXH_VECTOR == XXH_NEON /* * UGLY HACK: While AArch64 GCC on Linux does not seem to care, on macOS, GCC -O3 * optimizes out the entire hashLong loop because of the aliasing violation. * * However, GCC is also inefficient at load-store optimization with vld1q/vst1q, * so the only option is to mark it as aliasing. */ typedef uint64x2_t xxh_aliasing_uint64x2_t XXH_ALIASING; /*! * @internal * @brief `vld1q_u64` but faster and alignment-safe. * * On AArch64, unaligned access is always safe, but on ARMv7-a, it is only * *conditionally* safe (`vld1` has an alignment bit like `movdq[ua]` in x86). * * GCC for AArch64 sees `vld1q_u8` as an intrinsic instead of a load, so it * prohibits load-store optimizations. Therefore, a direct dereference is used. * * Otherwise, `vld1q_u8` is used with `vreinterpretq_u8_u64` to do a safe * unaligned load. */ #if defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__) XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr) /* silence -Wcast-align */ { return *(xxh_aliasing_uint64x2_t const *)ptr; } #else XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr) { return vreinterpretq_u64_u8(vld1q_u8((uint8_t const*)ptr)); } #endif /*! * @internal * @brief `vmlal_u32` on low and high halves of a vector. * * This is a workaround for AArch64 GCC < 11 which implemented arm_neon.h with * inline assembly and were therefore incapable of merging the `vget_{low, high}_u32` * with `vmlal_u32`. */ #if defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 11 XXH_FORCE_INLINE uint64x2_t XXH_vmlal_low_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs) { /* Inline assembly is the only way */ __asm__("umlal %0.2d, %1.2s, %2.2s" : "+w" (acc) : "w" (lhs), "w" (rhs)); return acc; } XXH_FORCE_INLINE uint64x2_t XXH_vmlal_high_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs) { /* This intrinsic works as expected */ return vmlal_high_u32(acc, lhs, rhs); } #else /* Portable intrinsic versions */ XXH_FORCE_INLINE uint64x2_t XXH_vmlal_low_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs) { return vmlal_u32(acc, vget_low_u32(lhs), vget_low_u32(rhs)); } /*! @copydoc XXH_vmlal_low_u32 * Assume the compiler converts this to vmlal_high_u32 on aarch64 */ XXH_FORCE_INLINE uint64x2_t XXH_vmlal_high_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs) { return vmlal_u32(acc, vget_high_u32(lhs), vget_high_u32(rhs)); } #endif /*! * @ingroup tuning * @brief Controls the NEON to scalar ratio for XXH3 * * This can be set to 2, 4, 6, or 8. * * ARM Cortex CPUs are _very_ sensitive to how their pipelines are used. * * For example, the Cortex-A73 can dispatch 3 micro-ops per cycle, but only 2 of those * can be NEON. If you are only using NEON instructions, you are only using 2/3 of the CPU * bandwidth. * * This is even more noticeable on the more advanced cores like the Cortex-A76 which * can dispatch 8 micro-ops per cycle, but still only 2 NEON micro-ops at once. * * Therefore, to make the most out of the pipeline, it is beneficial to run 6 NEON lanes * and 2 scalar lanes, which is chosen by default. * * This does not apply to Apple processors or 32-bit processors, which run better with * full NEON. These will default to 8. Additionally, size-optimized builds run 8 lanes. * * This change benefits CPUs with large micro-op buffers without negatively affecting * most other CPUs: * * | Chipset | Dispatch type | NEON only | 6:2 hybrid | Diff. | * |:----------------------|:--------------------|----------:|-----------:|------:| * | Snapdragon 730 (A76) | 2 NEON/8 micro-ops | 8.8 GB/s | 10.1 GB/s | ~16% | * | Snapdragon 835 (A73) | 2 NEON/3 micro-ops | 5.1 GB/s | 5.3 GB/s | ~5% | * | Marvell PXA1928 (A53) | In-order dual-issue | 1.9 GB/s | 1.9 GB/s | 0% | * | Apple M1 | 4 NEON/8 micro-ops | 37.3 GB/s | 36.1 GB/s | ~-3% | * * It also seems to fix some bad codegen on GCC, making it almost as fast as clang. * * When using WASM SIMD128, if this is 2 or 6, SIMDe will scalarize 2 of the lanes meaning * it effectively becomes worse 4. * * @see XXH3_accumulate_512_neon() */ # ifndef XXH3_NEON_LANES # if (defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) \ && !defined(__APPLE__) && XXH_SIZE_OPT <= 0 # define XXH3_NEON_LANES 6 # else # define XXH3_NEON_LANES XXH_ACC_NB # endif # endif #endif /* XXH_VECTOR == XXH_NEON */ /* * VSX and Z Vector helpers. * * This is very messy, and any pull requests to clean this up are welcome. * * There are a lot of problems with supporting VSX and s390x, due to * inconsistent intrinsics, spotty coverage, and multiple endiannesses. */ #if XXH_VECTOR == XXH_VSX /* Annoyingly, these headers _may_ define three macros: `bool`, `vector`, * and `pixel`. This is a problem for obvious reasons. * * These keywords are unnecessary; the spec literally says they are * equivalent to `__bool`, `__vector`, and `__pixel` and may be undef'd * after including the header. * * We use pragma push_macro/pop_macro to keep the namespace clean. */ # pragma push_macro("bool") # pragma push_macro("vector") # pragma push_macro("pixel") /* silence potential macro redefined warnings */ # undef bool # undef vector # undef pixel # if defined(__s390x__) # include # else # include # endif /* Restore the original macro values, if applicable. */ # pragma pop_macro("pixel") # pragma pop_macro("vector") # pragma pop_macro("bool") typedef __vector unsigned long long xxh_u64x2; typedef __vector unsigned char xxh_u8x16; typedef __vector unsigned xxh_u32x4; /* * UGLY HACK: Similar to aarch64 macOS GCC, s390x GCC has the same aliasing issue. */ typedef xxh_u64x2 xxh_aliasing_u64x2 XXH_ALIASING; # ifndef XXH_VSX_BE # if defined(__BIG_ENDIAN__) \ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) # define XXH_VSX_BE 1 # elif defined(__VEC_ELEMENT_REG_ORDER__) && __VEC_ELEMENT_REG_ORDER__ == __ORDER_BIG_ENDIAN__ # warning "-maltivec=be is not recommended. Please use native endianness." # define XXH_VSX_BE 1 # else # define XXH_VSX_BE 0 # endif # endif /* !defined(XXH_VSX_BE) */ # if XXH_VSX_BE # if defined(__POWER9_VECTOR__) || (defined(__clang__) && defined(__s390x__)) # define XXH_vec_revb vec_revb # else /*! * A polyfill for POWER9's vec_revb(). */ XXH_FORCE_INLINE xxh_u64x2 XXH_vec_revb(xxh_u64x2 val) { xxh_u8x16 const vByteSwap = { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08 }; return vec_perm(val, val, vByteSwap); } # endif # endif /* XXH_VSX_BE */ /*! * Performs an unaligned vector load and byte swaps it on big endian. */ XXH_FORCE_INLINE xxh_u64x2 XXH_vec_loadu(const void *ptr) { xxh_u64x2 ret; XXH_memcpy(&ret, ptr, sizeof(xxh_u64x2)); # if XXH_VSX_BE ret = XXH_vec_revb(ret); # endif return ret; } /* * vec_mulo and vec_mule are very problematic intrinsics on PowerPC * * These intrinsics weren't added until GCC 8, despite existing for a while, * and they are endian dependent. Also, their meaning swap depending on version. * */ # if defined(__s390x__) /* s390x is always big endian, no issue on this platform */ # define XXH_vec_mulo vec_mulo # define XXH_vec_mule vec_mule # elif defined(__clang__) && XXH_HAS_BUILTIN(__builtin_altivec_vmuleuw) && !defined(__ibmxl__) /* Clang has a better way to control this, we can just use the builtin which doesn't swap. */ /* The IBM XL Compiler (which defined __clang__) only implements the vec_* operations */ # define XXH_vec_mulo __builtin_altivec_vmulouw # define XXH_vec_mule __builtin_altivec_vmuleuw # else /* gcc needs inline assembly */ /* Adapted from https://github.com/google/highwayhash/blob/master/highwayhash/hh_vsx.h. */ XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mulo(xxh_u32x4 a, xxh_u32x4 b) { xxh_u64x2 result; __asm__("vmulouw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b)); return result; } XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mule(xxh_u32x4 a, xxh_u32x4 b) { xxh_u64x2 result; __asm__("vmuleuw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b)); return result; } # endif /* XXH_vec_mulo, XXH_vec_mule */ #endif /* XXH_VECTOR == XXH_VSX */ #if XXH_VECTOR == XXH_SVE #define ACCRND(acc, offset) \ do { \ svuint64_t input_vec = svld1_u64(mask, xinput + offset); \ svuint64_t secret_vec = svld1_u64(mask, xsecret + offset); \ svuint64_t mixed = sveor_u64_x(mask, secret_vec, input_vec); \ svuint64_t swapped = svtbl_u64(input_vec, kSwap); \ svuint64_t mixed_lo = svextw_u64_x(mask, mixed); \ svuint64_t mixed_hi = svlsr_n_u64_x(mask, mixed, 32); \ svuint64_t mul = svmad_u64_x(mask, mixed_lo, mixed_hi, swapped); \ acc = svadd_u64_x(mask, acc, mul); \ } while (0) #endif /* XXH_VECTOR == XXH_SVE */ /* prefetch * can be disabled, by declaring XXH_NO_PREFETCH build macro */ #if defined(XXH_NO_PREFETCH) # define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */ #else # if XXH_SIZE_OPT >= 1 # define XXH_PREFETCH(ptr) (void)(ptr) # elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) /* _mm_prefetch() not defined outside of x86/x64 */ # include /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */ # define XXH_PREFETCH(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0) # elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) ) # define XXH_PREFETCH(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 3 /* locality */) # else # define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */ # endif #endif /* XXH_NO_PREFETCH */ /* ========================================== * XXH3 default settings * ========================================== */ #define XXH_SECRET_DEFAULT_SIZE 192 /* minimum XXH3_SECRET_SIZE_MIN */ #if (XXH_SECRET_DEFAULT_SIZE < XXH3_SECRET_SIZE_MIN) # error "default keyset is not large enough" #endif /*! * @internal * @def XXH3_kSecret * @brief Pseudorandom secret taken directly from FARSH. */ XXH_ALIGN(64) static const xxh_u8 XXH3_kSecret[XXH_SECRET_DEFAULT_SIZE] = { 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c, 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f, 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21, 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c, 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3, 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8, 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d, 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64, 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb, 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e, 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce, 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e, }; static const xxh_u64 PRIME_MX1 = 0x165667919E3779F9ULL; /*!< 0b0001011001010110011001111001000110011110001101110111100111111001 */ static const xxh_u64 PRIME_MX2 = 0x9FB21C651E98DF25ULL; /*!< 0b1001111110110010000111000110010100011110100110001101111100100101 */ #ifdef XXH_OLD_NAMES # define kSecret XXH3_kSecret #endif #ifdef XXH_DOXYGEN /*! * @brief Calculates a 32-bit to 64-bit long multiply. * * Implemented as a macro. * * Wraps `__emulu` on MSVC x86 because it tends to call `__allmul` when it doesn't * need to (but it shouldn't need to anyways, it is about 7 instructions to do * a 64x64 multiply...). Since we know that this will _always_ emit `MULL`, we * use that instead of the normal method. * * If you are compiling for platforms like Thumb-1 and don't have a better option, * you may also want to write your own long multiply routine here. * * @param x, y Numbers to be multiplied * @return 64-bit product of the low 32 bits of @p x and @p y. */ XXH_FORCE_INLINE xxh_u64 XXH_mult32to64(xxh_u64 x, xxh_u64 y) { return (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF); } #elif defined(_MSC_VER) && defined(_M_IX86) # define XXH_mult32to64(x, y) __emulu((unsigned)(x), (unsigned)(y)) #else /* * Downcast + upcast is usually better than masking on older compilers like * GCC 4.2 (especially 32-bit ones), all without affecting newer compilers. * * The other method, (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF), will AND both operands * and perform a full 64x64 multiply -- entirely redundant on 32-bit. */ # define XXH_mult32to64(x, y) ((xxh_u64)(xxh_u32)(x) * (xxh_u64)(xxh_u32)(y)) #endif /*! * @brief Calculates a 64->128-bit long multiply. * * Uses `__uint128_t` and `_umul128` if available, otherwise uses a scalar * version. * * @param lhs , rhs The 64-bit integers to be multiplied * @return The 128-bit result represented in an @ref XXH128_hash_t. */ static XXH128_hash_t XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs) { /* * GCC/Clang __uint128_t method. * * On most 64-bit targets, GCC and Clang define a __uint128_t type. * This is usually the best way as it usually uses a native long 64-bit * multiply, such as MULQ on x86_64 or MUL + UMULH on aarch64. * * Usually. * * Despite being a 32-bit platform, Clang (and emscripten) define this type * despite not having the arithmetic for it. This results in a laggy * compiler builtin call which calculates a full 128-bit multiply. * In that case it is best to use the portable one. * https://github.com/Cyan4973/xxHash/issues/211#issuecomment-515575677 */ #if (defined(__GNUC__) || defined(__clang__)) && !defined(__wasm__) \ && defined(__SIZEOF_INT128__) \ || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) __uint128_t const product = (__uint128_t)lhs * (__uint128_t)rhs; XXH128_hash_t r128; r128.low64 = (xxh_u64)(product); r128.high64 = (xxh_u64)(product >> 64); return r128; /* * MSVC for x64's _umul128 method. * * xxh_u64 _umul128(xxh_u64 Multiplier, xxh_u64 Multiplicand, xxh_u64 *HighProduct); * * This compiles to single operand MUL on x64. */ #elif (defined(_M_X64) || defined(_M_IA64)) && !defined(_M_ARM64EC) #ifndef _MSC_VER # pragma intrinsic(_umul128) #endif xxh_u64 product_high; xxh_u64 const product_low = _umul128(lhs, rhs, &product_high); XXH128_hash_t r128; r128.low64 = product_low; r128.high64 = product_high; return r128; /* * MSVC for ARM64's __umulh method. * * This compiles to the same MUL + UMULH as GCC/Clang's __uint128_t method. */ #elif defined(_M_ARM64) || defined(_M_ARM64EC) #ifndef _MSC_VER # pragma intrinsic(__umulh) #endif XXH128_hash_t r128; r128.low64 = lhs * rhs; r128.high64 = __umulh(lhs, rhs); return r128; #else /* * Portable scalar method. Optimized for 32-bit and 64-bit ALUs. * * This is a fast and simple grade school multiply, which is shown below * with base 10 arithmetic instead of base 0x100000000. * * 9 3 // D2 lhs = 93 * x 7 5 // D2 rhs = 75 * ---------- * 1 5 // D2 lo_lo = (93 % 10) * (75 % 10) = 15 * 4 5 | // D2 hi_lo = (93 / 10) * (75 % 10) = 45 * 2 1 | // D2 lo_hi = (93 % 10) * (75 / 10) = 21 * + 6 3 | | // D2 hi_hi = (93 / 10) * (75 / 10) = 63 * --------- * 2 7 | // D2 cross = (15 / 10) + (45 % 10) + 21 = 27 * + 6 7 | | // D2 upper = (27 / 10) + (45 / 10) + 63 = 67 * --------- * 6 9 7 5 // D4 res = (27 * 10) + (15 % 10) + (67 * 100) = 6975 * * The reasons for adding the products like this are: * 1. It avoids manual carry tracking. Just like how * (9 * 9) + 9 + 9 = 99, the same applies with this for UINT64_MAX. * This avoids a lot of complexity. * * 2. It hints for, and on Clang, compiles to, the powerful UMAAL * instruction available in ARM's Digital Signal Processing extension * in 32-bit ARMv6 and later, which is shown below: * * void UMAAL(xxh_u32 *RdLo, xxh_u32 *RdHi, xxh_u32 Rn, xxh_u32 Rm) * { * xxh_u64 product = (xxh_u64)*RdLo * (xxh_u64)*RdHi + Rn + Rm; * *RdLo = (xxh_u32)(product & 0xFFFFFFFF); * *RdHi = (xxh_u32)(product >> 32); * } * * This instruction was designed for efficient long multiplication, and * allows this to be calculated in only 4 instructions at speeds * comparable to some 64-bit ALUs. * * 3. It isn't terrible on other platforms. Usually this will be a couple * of 32-bit ADD/ADCs. */ /* First calculate all of the cross products. */ xxh_u64 const lo_lo = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs & 0xFFFFFFFF); xxh_u64 const hi_lo = XXH_mult32to64(lhs >> 32, rhs & 0xFFFFFFFF); xxh_u64 const lo_hi = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs >> 32); xxh_u64 const hi_hi = XXH_mult32to64(lhs >> 32, rhs >> 32); /* Now add the products together. These will never overflow. */ xxh_u64 const cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi; xxh_u64 const upper = (hi_lo >> 32) + (cross >> 32) + hi_hi; xxh_u64 const lower = (cross << 32) | (lo_lo & 0xFFFFFFFF); XXH128_hash_t r128; r128.low64 = lower; r128.high64 = upper; return r128; #endif } /*! * @brief Calculates a 64-bit to 128-bit multiply, then XOR folds it. * * The reason for the separate function is to prevent passing too many structs * around by value. This will hopefully inline the multiply, but we don't force it. * * @param lhs , rhs The 64-bit integers to multiply * @return The low 64 bits of the product XOR'd by the high 64 bits. * @see XXH_mult64to128() */ static xxh_u64 XXH3_mul128_fold64(xxh_u64 lhs, xxh_u64 rhs) { XXH128_hash_t product = XXH_mult64to128(lhs, rhs); return product.low64 ^ product.high64; } /*! Seems to produce slightly better code on GCC for some reason. */ XXH_FORCE_INLINE XXH_CONSTF xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift) { XXH_ASSERT(0 <= shift && shift < 64); return v64 ^ (v64 >> shift); } /* * This is a fast avalanche stage, * suitable when input bits are already partially mixed */ static XXH64_hash_t XXH3_avalanche(xxh_u64 h64) { h64 = XXH_xorshift64(h64, 37); h64 *= PRIME_MX1; h64 = XXH_xorshift64(h64, 32); return h64; } /* * This is a stronger avalanche, * inspired by Pelle Evensen's rrmxmx * preferable when input has not been previously mixed */ static XXH64_hash_t XXH3_rrmxmx(xxh_u64 h64, xxh_u64 len) { /* this mix is inspired by Pelle Evensen's rrmxmx */ h64 ^= XXH_rotl64(h64, 49) ^ XXH_rotl64(h64, 24); h64 *= PRIME_MX2; h64 ^= (h64 >> 35) + len ; h64 *= PRIME_MX2; return XXH_xorshift64(h64, 28); } /* ========================================== * Short keys * ========================================== * One of the shortcomings of XXH32 and XXH64 was that their performance was * sub-optimal on short lengths. It used an iterative algorithm which strongly * favored lengths that were a multiple of 4 or 8. * * Instead of iterating over individual inputs, we use a set of single shot * functions which piece together a range of lengths and operate in constant time. * * Additionally, the number of multiplies has been significantly reduced. This * reduces latency, especially when emulating 64-bit multiplies on 32-bit. * * Depending on the platform, this may or may not be faster than XXH32, but it * is almost guaranteed to be faster than XXH64. */ /* * At very short lengths, there isn't enough input to fully hide secrets, or use * the entire secret. * * There is also only a limited amount of mixing we can do before significantly * impacting performance. * * Therefore, we use different sections of the secret and always mix two secret * samples with an XOR. This should have no effect on performance on the * seedless or withSeed variants because everything _should_ be constant folded * by modern compilers. * * The XOR mixing hides individual parts of the secret and increases entropy. * * This adds an extra layer of strength for custom secrets. */ XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_1to3_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); XXH_ASSERT(1 <= len && len <= 3); XXH_ASSERT(secret != NULL); /* * len = 1: combined = { input[0], 0x01, input[0], input[0] } * len = 2: combined = { input[1], 0x02, input[0], input[1] } * len = 3: combined = { input[2], 0x03, input[0], input[1] } */ { xxh_u8 const c1 = input[0]; xxh_u8 const c2 = input[len >> 1]; xxh_u8 const c3 = input[len - 1]; xxh_u32 const combined = ((xxh_u32)c1 << 16) | ((xxh_u32)c2 << 24) | ((xxh_u32)c3 << 0) | ((xxh_u32)len << 8); xxh_u64 const bitflip = (XXH_readLE32(secret) ^ XXH_readLE32(secret+4)) + seed; xxh_u64 const keyed = (xxh_u64)combined ^ bitflip; return XXH64_avalanche(keyed); } } XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_4to8_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); XXH_ASSERT(secret != NULL); XXH_ASSERT(4 <= len && len <= 8); seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32; { xxh_u32 const input1 = XXH_readLE32(input); xxh_u32 const input2 = XXH_readLE32(input + len - 4); xxh_u64 const bitflip = (XXH_readLE64(secret+8) ^ XXH_readLE64(secret+16)) - seed; xxh_u64 const input64 = input2 + (((xxh_u64)input1) << 32); xxh_u64 const keyed = input64 ^ bitflip; return XXH3_rrmxmx(keyed, len); } } XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_9to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); XXH_ASSERT(secret != NULL); XXH_ASSERT(9 <= len && len <= 16); { xxh_u64 const bitflip1 = (XXH_readLE64(secret+24) ^ XXH_readLE64(secret+32)) + seed; xxh_u64 const bitflip2 = (XXH_readLE64(secret+40) ^ XXH_readLE64(secret+48)) - seed; xxh_u64 const input_lo = XXH_readLE64(input) ^ bitflip1; xxh_u64 const input_hi = XXH_readLE64(input + len - 8) ^ bitflip2; xxh_u64 const acc = len + XXH_swap64(input_lo) + input_hi + XXH3_mul128_fold64(input_lo, input_hi); return XXH3_avalanche(acc); } } XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_0to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(len <= 16); { if (XXH_likely(len > 8)) return XXH3_len_9to16_64b(input, len, secret, seed); if (XXH_likely(len >= 4)) return XXH3_len_4to8_64b(input, len, secret, seed); if (len) return XXH3_len_1to3_64b(input, len, secret, seed); return XXH64_avalanche(seed ^ (XXH_readLE64(secret+56) ^ XXH_readLE64(secret+64))); } } /* * DISCLAIMER: There are known *seed-dependent* multicollisions here due to * multiplication by zero, affecting hashes of lengths 17 to 240. * * However, they are very unlikely. * * Keep this in mind when using the unseeded XXH3_64bits() variant: As with all * unseeded non-cryptographic hashes, it does not attempt to defend itself * against specially crafted inputs, only random inputs. * * Compared to classic UMAC where a 1 in 2^31 chance of 4 consecutive bytes * cancelling out the secret is taken an arbitrary number of times (addressed * in XXH3_accumulate_512), this collision is very unlikely with random inputs * and/or proper seeding: * * This only has a 1 in 2^63 chance of 8 consecutive bytes cancelling out, in a * function that is only called up to 16 times per hash with up to 240 bytes of * input. * * This is not too bad for a non-cryptographic hash function, especially with * only 64 bit outputs. * * The 128-bit variant (which trades some speed for strength) is NOT affected * by this, although it is always a good idea to use a proper seed if you care * about strength. */ XXH_FORCE_INLINE xxh_u64 XXH3_mix16B(const xxh_u8* XXH_RESTRICT input, const xxh_u8* XXH_RESTRICT secret, xxh_u64 seed64) { #if defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \ && defined(__i386__) && defined(__SSE2__) /* x86 + SSE2 */ \ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable like XXH32 hack */ /* * UGLY HACK: * GCC for x86 tends to autovectorize the 128-bit multiply, resulting in * slower code. * * By forcing seed64 into a register, we disrupt the cost model and * cause it to scalarize. See `XXH32_round()` * * FIXME: Clang's output is still _much_ faster -- On an AMD Ryzen 3600, * XXH3_64bits @ len=240 runs at 4.6 GB/s with Clang 9, but 3.3 GB/s on * GCC 9.2, despite both emitting scalar code. * * GCC generates much better scalar code than Clang for the rest of XXH3, * which is why finding a more optimal codepath is an interest. */ XXH_COMPILER_GUARD(seed64); #endif { xxh_u64 const input_lo = XXH_readLE64(input); xxh_u64 const input_hi = XXH_readLE64(input+8); return XXH3_mul128_fold64( input_lo ^ (XXH_readLE64(secret) + seed64), input_hi ^ (XXH_readLE64(secret+8) - seed64) ); } } /* For mid range keys, XXH3 uses a Mum-hash variant. */ XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH64_hash_t seed) { XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize; XXH_ASSERT(16 < len && len <= 128); { xxh_u64 acc = len * XXH_PRIME64_1; #if XXH_SIZE_OPT >= 1 /* Smaller and cleaner, but slightly slower. */ unsigned int i = (unsigned int)(len - 1) / 32; do { acc += XXH3_mix16B(input+16 * i, secret+32*i, seed); acc += XXH3_mix16B(input+len-16*(i+1), secret+32*i+16, seed); } while (i-- != 0); #else if (len > 32) { if (len > 64) { if (len > 96) { acc += XXH3_mix16B(input+48, secret+96, seed); acc += XXH3_mix16B(input+len-64, secret+112, seed); } acc += XXH3_mix16B(input+32, secret+64, seed); acc += XXH3_mix16B(input+len-48, secret+80, seed); } acc += XXH3_mix16B(input+16, secret+32, seed); acc += XXH3_mix16B(input+len-32, secret+48, seed); } acc += XXH3_mix16B(input+0, secret+0, seed); acc += XXH3_mix16B(input+len-16, secret+16, seed); #endif return XXH3_avalanche(acc); } } XXH_NO_INLINE XXH_PUREF XXH64_hash_t XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH64_hash_t seed) { XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize; XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX); #define XXH3_MIDSIZE_STARTOFFSET 3 #define XXH3_MIDSIZE_LASTOFFSET 17 { xxh_u64 acc = len * XXH_PRIME64_1; xxh_u64 acc_end; unsigned int const nbRounds = (unsigned int)len / 16; unsigned int i; XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX); for (i=0; i<8; i++) { acc += XXH3_mix16B(input+(16*i), secret+(16*i), seed); } /* last bytes */ acc_end = XXH3_mix16B(input + len - 16, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET, seed); XXH_ASSERT(nbRounds >= 8); acc = XXH3_avalanche(acc); #if defined(__clang__) /* Clang */ \ && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable */ /* * UGLY HACK: * Clang for ARMv7-A tries to vectorize this loop, similar to GCC x86. * In everywhere else, it uses scalar code. * * For 64->128-bit multiplies, even if the NEON was 100% optimal, it * would still be slower than UMAAL (see XXH_mult64to128). * * Unfortunately, Clang doesn't handle the long multiplies properly and * converts them to the nonexistent "vmulq_u64" intrinsic, which is then * scalarized into an ugly mess of VMOV.32 instructions. * * This mess is difficult to avoid without turning autovectorization * off completely, but they are usually relatively minor and/or not * worth it to fix. * * This loop is the easiest to fix, as unlike XXH32, this pragma * _actually works_ because it is a loop vectorization instead of an * SLP vectorization. */ #pragma clang loop vectorize(disable) #endif for (i=8 ; i < nbRounds; i++) { /* * Prevents clang for unrolling the acc loop and interleaving with this one. */ XXH_COMPILER_GUARD(acc); acc_end += XXH3_mix16B(input+(16*i), secret+(16*(i-8)) + XXH3_MIDSIZE_STARTOFFSET, seed); } return XXH3_avalanche(acc + acc_end); } } /* ======= Long Keys ======= */ #define XXH_STRIPE_LEN 64 #define XXH_SECRET_CONSUME_RATE 8 /* nb of secret bytes consumed at each accumulation */ #define XXH_ACC_NB (XXH_STRIPE_LEN / sizeof(xxh_u64)) #ifdef XXH_OLD_NAMES # define STRIPE_LEN XXH_STRIPE_LEN # define ACC_NB XXH_ACC_NB #endif #ifndef XXH_PREFETCH_DIST # ifdef __clang__ # define XXH_PREFETCH_DIST 320 # else # if (XXH_VECTOR == XXH_AVX512) # define XXH_PREFETCH_DIST 512 # else # define XXH_PREFETCH_DIST 384 # endif # endif /* __clang__ */ #endif /* XXH_PREFETCH_DIST */ /* * These macros are to generate an XXH3_accumulate() function. * The two arguments select the name suffix and target attribute. * * The name of this symbol is XXH3_accumulate_() and it calls * XXH3_accumulate_512_(). * * It may be useful to hand implement this function if the compiler fails to * optimize the inline function. */ #define XXH3_ACCUMULATE_TEMPLATE(name) \ void \ XXH3_accumulate_##name(xxh_u64* XXH_RESTRICT acc, \ const xxh_u8* XXH_RESTRICT input, \ const xxh_u8* XXH_RESTRICT secret, \ size_t nbStripes) \ { \ size_t n; \ for (n = 0; n < nbStripes; n++ ) { \ const xxh_u8* const in = input + n*XXH_STRIPE_LEN; \ XXH_PREFETCH(in + XXH_PREFETCH_DIST); \ XXH3_accumulate_512_##name( \ acc, \ in, \ secret + n*XXH_SECRET_CONSUME_RATE); \ } \ } XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64) { if (!XXH_CPU_LITTLE_ENDIAN) v64 = XXH_swap64(v64); XXH_memcpy(dst, &v64, sizeof(v64)); } /* Several intrinsic functions below are supposed to accept __int64 as argument, * as documented in https://software.intel.com/sites/landingpage/IntrinsicsGuide/ . * However, several environments do not define __int64 type, * requiring a workaround. */ #if !defined (__VMS) \ && (defined (__cplusplus) \ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) ) typedef int64_t xxh_i64; #else /* the following type must have a width of 64-bit */ typedef long long xxh_i64; #endif /* * XXH3_accumulate_512 is the tightest loop for long inputs, and it is the most optimized. * * It is a hardened version of UMAC, based off of FARSH's implementation. * * This was chosen because it adapts quite well to 32-bit, 64-bit, and SIMD * implementations, and it is ridiculously fast. * * We harden it by mixing the original input to the accumulators as well as the product. * * This means that in the (relatively likely) case of a multiply by zero, the * original input is preserved. * * On 128-bit inputs, we swap 64-bit pairs when we add the input to improve * cross-pollination, as otherwise the upper and lower halves would be * essentially independent. * * This doesn't matter on 64-bit hashes since they all get merged together in * the end, so we skip the extra step. * * Both XXH3_64bits and XXH3_128bits use this subroutine. */ #if (XXH_VECTOR == XXH_AVX512) \ || (defined(XXH_DISPATCH_AVX512) && XXH_DISPATCH_AVX512 != 0) #ifndef XXH_TARGET_AVX512 # define XXH_TARGET_AVX512 /* disable attribute target */ #endif XXH_FORCE_INLINE XXH_TARGET_AVX512 void XXH3_accumulate_512_avx512(void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { __m512i* const xacc = (__m512i *) acc; XXH_ASSERT((((size_t)acc) & 63) == 0); XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i)); { /* data_vec = input[0]; */ __m512i const data_vec = _mm512_loadu_si512 (input); /* key_vec = secret[0]; */ __m512i const key_vec = _mm512_loadu_si512 (secret); /* data_key = data_vec ^ key_vec; */ __m512i const data_key = _mm512_xor_si512 (data_vec, key_vec); /* data_key_lo = data_key >> 32; */ __m512i const data_key_lo = _mm512_srli_epi64 (data_key, 32); /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ __m512i const product = _mm512_mul_epu32 (data_key, data_key_lo); /* xacc[0] += swap(data_vec); */ __m512i const data_swap = _mm512_shuffle_epi32(data_vec, (_MM_PERM_ENUM)_MM_SHUFFLE(1, 0, 3, 2)); __m512i const sum = _mm512_add_epi64(*xacc, data_swap); /* xacc[0] += product; */ *xacc = _mm512_add_epi64(product, sum); } } XXH_FORCE_INLINE XXH_TARGET_AVX512 XXH3_ACCUMULATE_TEMPLATE(avx512) /* * XXH3_scrambleAcc: Scrambles the accumulators to improve mixing. * * Multiplication isn't perfect, as explained by Google in HighwayHash: * * // Multiplication mixes/scrambles bytes 0-7 of the 64-bit result to * // varying degrees. In descending order of goodness, bytes * // 3 4 2 5 1 6 0 7 have quality 228 224 164 160 100 96 36 32. * // As expected, the upper and lower bytes are much worse. * * Source: https://github.com/google/highwayhash/blob/0aaf66b/highwayhash/hh_avx2.h#L291 * * Since our algorithm uses a pseudorandom secret to add some variance into the * mix, we don't need to (or want to) mix as often or as much as HighwayHash does. * * This isn't as tight as XXH3_accumulate, but still written in SIMD to avoid * extraction. * * Both XXH3_64bits and XXH3_128bits use this subroutine. */ XXH_FORCE_INLINE XXH_TARGET_AVX512 void XXH3_scrambleAcc_avx512(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 63) == 0); XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i)); { __m512i* const xacc = (__m512i*) acc; const __m512i prime32 = _mm512_set1_epi32((int)XXH_PRIME32_1); /* xacc[0] ^= (xacc[0] >> 47) */ __m512i const acc_vec = *xacc; __m512i const shifted = _mm512_srli_epi64 (acc_vec, 47); /* xacc[0] ^= secret; */ __m512i const key_vec = _mm512_loadu_si512 (secret); __m512i const data_key = _mm512_ternarylogic_epi32(key_vec, acc_vec, shifted, 0x96 /* key_vec ^ acc_vec ^ shifted */); /* xacc[0] *= XXH_PRIME32_1; */ __m512i const data_key_hi = _mm512_srli_epi64 (data_key, 32); __m512i const prod_lo = _mm512_mul_epu32 (data_key, prime32); __m512i const prod_hi = _mm512_mul_epu32 (data_key_hi, prime32); *xacc = _mm512_add_epi64(prod_lo, _mm512_slli_epi64(prod_hi, 32)); } } XXH_FORCE_INLINE XXH_TARGET_AVX512 void XXH3_initCustomSecret_avx512(void* XXH_RESTRICT customSecret, xxh_u64 seed64) { XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 63) == 0); XXH_STATIC_ASSERT(XXH_SEC_ALIGN == 64); XXH_ASSERT(((size_t)customSecret & 63) == 0); (void)(&XXH_writeLE64); { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m512i); __m512i const seed_pos = _mm512_set1_epi64((xxh_i64)seed64); __m512i const seed = _mm512_mask_sub_epi64(seed_pos, 0xAA, _mm512_set1_epi8(0), seed_pos); const __m512i* const src = (const __m512i*) ((const void*) XXH3_kSecret); __m512i* const dest = ( __m512i*) customSecret; int i; XXH_ASSERT(((size_t)src & 63) == 0); /* control alignment */ XXH_ASSERT(((size_t)dest & 63) == 0); for (i=0; i < nbRounds; ++i) { dest[i] = _mm512_add_epi64(_mm512_load_si512(src + i), seed); } } } #endif #if (XXH_VECTOR == XXH_AVX2) \ || (defined(XXH_DISPATCH_AVX2) && XXH_DISPATCH_AVX2 != 0) #ifndef XXH_TARGET_AVX2 # define XXH_TARGET_AVX2 /* disable attribute target */ #endif XXH_FORCE_INLINE XXH_TARGET_AVX2 void XXH3_accumulate_512_avx2( void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 31) == 0); { __m256i* const xacc = (__m256i *) acc; /* Unaligned. This is mainly for pointer arithmetic, and because * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */ const __m256i* const xinput = (const __m256i *) input; /* Unaligned. This is mainly for pointer arithmetic, and because * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */ const __m256i* const xsecret = (const __m256i *) secret; size_t i; for (i=0; i < XXH_STRIPE_LEN/sizeof(__m256i); i++) { /* data_vec = xinput[i]; */ __m256i const data_vec = _mm256_loadu_si256 (xinput+i); /* key_vec = xsecret[i]; */ __m256i const key_vec = _mm256_loadu_si256 (xsecret+i); /* data_key = data_vec ^ key_vec; */ __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec); /* data_key_lo = data_key >> 32; */ __m256i const data_key_lo = _mm256_srli_epi64 (data_key, 32); /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ __m256i const product = _mm256_mul_epu32 (data_key, data_key_lo); /* xacc[i] += swap(data_vec); */ __m256i const data_swap = _mm256_shuffle_epi32(data_vec, _MM_SHUFFLE(1, 0, 3, 2)); __m256i const sum = _mm256_add_epi64(xacc[i], data_swap); /* xacc[i] += product; */ xacc[i] = _mm256_add_epi64(product, sum); } } } XXH_FORCE_INLINE XXH_TARGET_AVX2 XXH3_ACCUMULATE_TEMPLATE(avx2) XXH_FORCE_INLINE XXH_TARGET_AVX2 void XXH3_scrambleAcc_avx2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 31) == 0); { __m256i* const xacc = (__m256i*) acc; /* Unaligned. This is mainly for pointer arithmetic, and because * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */ const __m256i* const xsecret = (const __m256i *) secret; const __m256i prime32 = _mm256_set1_epi32((int)XXH_PRIME32_1); size_t i; for (i=0; i < XXH_STRIPE_LEN/sizeof(__m256i); i++) { /* xacc[i] ^= (xacc[i] >> 47) */ __m256i const acc_vec = xacc[i]; __m256i const shifted = _mm256_srli_epi64 (acc_vec, 47); __m256i const data_vec = _mm256_xor_si256 (acc_vec, shifted); /* xacc[i] ^= xsecret; */ __m256i const key_vec = _mm256_loadu_si256 (xsecret+i); __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec); /* xacc[i] *= XXH_PRIME32_1; */ __m256i const data_key_hi = _mm256_srli_epi64 (data_key, 32); __m256i const prod_lo = _mm256_mul_epu32 (data_key, prime32); __m256i const prod_hi = _mm256_mul_epu32 (data_key_hi, prime32); xacc[i] = _mm256_add_epi64(prod_lo, _mm256_slli_epi64(prod_hi, 32)); } } } XXH_FORCE_INLINE XXH_TARGET_AVX2 void XXH3_initCustomSecret_avx2(void* XXH_RESTRICT customSecret, xxh_u64 seed64) { XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 31) == 0); XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE / sizeof(__m256i)) == 6); XXH_STATIC_ASSERT(XXH_SEC_ALIGN <= 64); (void)(&XXH_writeLE64); XXH_PREFETCH(customSecret); { __m256i const seed = _mm256_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64, (xxh_i64)(0U - seed64), (xxh_i64)seed64); const __m256i* const src = (const __m256i*) ((const void*) XXH3_kSecret); __m256i* dest = ( __m256i*) customSecret; # if defined(__GNUC__) || defined(__clang__) /* * On GCC & Clang, marking 'dest' as modified will cause the compiler: * - do not extract the secret from sse registers in the internal loop * - use less common registers, and avoid pushing these reg into stack */ XXH_COMPILER_GUARD(dest); # endif XXH_ASSERT(((size_t)src & 31) == 0); /* control alignment */ XXH_ASSERT(((size_t)dest & 31) == 0); /* GCC -O2 need unroll loop manually */ dest[0] = _mm256_add_epi64(_mm256_load_si256(src+0), seed); dest[1] = _mm256_add_epi64(_mm256_load_si256(src+1), seed); dest[2] = _mm256_add_epi64(_mm256_load_si256(src+2), seed); dest[3] = _mm256_add_epi64(_mm256_load_si256(src+3), seed); dest[4] = _mm256_add_epi64(_mm256_load_si256(src+4), seed); dest[5] = _mm256_add_epi64(_mm256_load_si256(src+5), seed); } } #endif /* x86dispatch always generates SSE2 */ #if (XXH_VECTOR == XXH_SSE2) || defined(XXH_X86DISPATCH) #ifndef XXH_TARGET_SSE2 # define XXH_TARGET_SSE2 /* disable attribute target */ #endif XXH_FORCE_INLINE XXH_TARGET_SSE2 void XXH3_accumulate_512_sse2( void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { /* SSE2 is just a half-scale version of the AVX2 version. */ XXH_ASSERT((((size_t)acc) & 15) == 0); { __m128i* const xacc = (__m128i *) acc; /* Unaligned. This is mainly for pointer arithmetic, and because * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */ const __m128i* const xinput = (const __m128i *) input; /* Unaligned. This is mainly for pointer arithmetic, and because * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */ const __m128i* const xsecret = (const __m128i *) secret; size_t i; for (i=0; i < XXH_STRIPE_LEN/sizeof(__m128i); i++) { /* data_vec = xinput[i]; */ __m128i const data_vec = _mm_loadu_si128 (xinput+i); /* key_vec = xsecret[i]; */ __m128i const key_vec = _mm_loadu_si128 (xsecret+i); /* data_key = data_vec ^ key_vec; */ __m128i const data_key = _mm_xor_si128 (data_vec, key_vec); /* data_key_lo = data_key >> 32; */ __m128i const data_key_lo = _mm_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1)); /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ __m128i const product = _mm_mul_epu32 (data_key, data_key_lo); /* xacc[i] += swap(data_vec); */ __m128i const data_swap = _mm_shuffle_epi32(data_vec, _MM_SHUFFLE(1,0,3,2)); __m128i const sum = _mm_add_epi64(xacc[i], data_swap); /* xacc[i] += product; */ xacc[i] = _mm_add_epi64(product, sum); } } } XXH_FORCE_INLINE XXH_TARGET_SSE2 XXH3_ACCUMULATE_TEMPLATE(sse2) XXH_FORCE_INLINE XXH_TARGET_SSE2 void XXH3_scrambleAcc_sse2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 15) == 0); { __m128i* const xacc = (__m128i*) acc; /* Unaligned. This is mainly for pointer arithmetic, and because * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */ const __m128i* const xsecret = (const __m128i *) secret; const __m128i prime32 = _mm_set1_epi32((int)XXH_PRIME32_1); size_t i; for (i=0; i < XXH_STRIPE_LEN/sizeof(__m128i); i++) { /* xacc[i] ^= (xacc[i] >> 47) */ __m128i const acc_vec = xacc[i]; __m128i const shifted = _mm_srli_epi64 (acc_vec, 47); __m128i const data_vec = _mm_xor_si128 (acc_vec, shifted); /* xacc[i] ^= xsecret[i]; */ __m128i const key_vec = _mm_loadu_si128 (xsecret+i); __m128i const data_key = _mm_xor_si128 (data_vec, key_vec); /* xacc[i] *= XXH_PRIME32_1; */ __m128i const data_key_hi = _mm_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1)); __m128i const prod_lo = _mm_mul_epu32 (data_key, prime32); __m128i const prod_hi = _mm_mul_epu32 (data_key_hi, prime32); xacc[i] = _mm_add_epi64(prod_lo, _mm_slli_epi64(prod_hi, 32)); } } } XXH_FORCE_INLINE XXH_TARGET_SSE2 void XXH3_initCustomSecret_sse2(void* XXH_RESTRICT customSecret, xxh_u64 seed64) { XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0); (void)(&XXH_writeLE64); { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m128i); # if defined(_MSC_VER) && defined(_M_IX86) && _MSC_VER <= 1900 /* MSVC 32bit mode does not support _mm_set_epi64x before 2015 * and some specific variants of 2015 may also lack it */ /* Cast to unsigned 64-bit first to avoid signed arithmetic issues */ xxh_u64 const seed64_unsigned = (xxh_u64)seed64; xxh_u64 const neg_seed64 = (xxh_u64)(0ULL - seed64_unsigned); __m128i const seed = _mm_set_epi32( (int)(neg_seed64 >> 32), /* high 32 bits of negated seed */ (int)(neg_seed64), /* low 32 bits of negated seed */ (int)(seed64_unsigned >> 32), /* high 32 bits of original seed */ (int)(seed64_unsigned) /* low 32 bits of original seed */ ); # else __m128i const seed = _mm_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64); # endif int i; const void* const src16 = XXH3_kSecret; __m128i* dst16 = (__m128i*) customSecret; # if defined(__GNUC__) || defined(__clang__) /* * On GCC & Clang, marking 'dest' as modified will cause the compiler: * - do not extract the secret from sse registers in the internal loop * - use less common registers, and avoid pushing these reg into stack */ XXH_COMPILER_GUARD(dst16); # endif XXH_ASSERT(((size_t)src16 & 15) == 0); /* control alignment */ XXH_ASSERT(((size_t)dst16 & 15) == 0); for (i=0; i < nbRounds; ++i) { dst16[i] = _mm_add_epi64(_mm_load_si128((const __m128i *)src16+i), seed); } } } #endif #if (XXH_VECTOR == XXH_NEON) /* forward declarations for the scalar routines */ XXH_FORCE_INLINE void XXH3_scalarRound(void* XXH_RESTRICT acc, void const* XXH_RESTRICT input, void const* XXH_RESTRICT secret, size_t lane); XXH_FORCE_INLINE void XXH3_scalarScrambleRound(void* XXH_RESTRICT acc, void const* XXH_RESTRICT secret, size_t lane); /*! * @internal * @brief The bulk processing loop for NEON and WASM SIMD128. * * The NEON code path is actually partially scalar when running on AArch64. This * is to optimize the pipelining and can have up to 15% speedup depending on the * CPU, and it also mitigates some GCC codegen issues. * * @see XXH3_NEON_LANES for configuring this and details about this optimization. * * NEON's 32-bit to 64-bit long multiply takes a half vector of 32-bit * integers instead of the other platforms which mask full 64-bit vectors, * so the setup is more complicated than just shifting right. * * Additionally, there is an optimization for 4 lanes at once noted below. * * Since, as stated, the most optimal amount of lanes for Cortexes is 6, * there needs to be *three* versions of the accumulate operation used * for the remaining 2 lanes. * * WASM's SIMD128 uses SIMDe's arm_neon.h polyfill because the intrinsics overlap * nearly perfectly. */ XXH_FORCE_INLINE void XXH3_accumulate_512_neon( void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 15) == 0); XXH_STATIC_ASSERT(XXH3_NEON_LANES > 0 && XXH3_NEON_LANES <= XXH_ACC_NB && XXH3_NEON_LANES % 2 == 0); { /* GCC for darwin arm64 does not like aliasing here */ xxh_aliasing_uint64x2_t* const xacc = (xxh_aliasing_uint64x2_t*) acc; /* We don't use a uint32x4_t pointer because it causes bus errors on ARMv7. */ uint8_t const* xinput = (const uint8_t *) input; uint8_t const* xsecret = (const uint8_t *) secret; size_t i; #ifdef __wasm_simd128__ /* * On WASM SIMD128, Clang emits direct address loads when XXH3_kSecret * is constant propagated, which results in it converting it to this * inside the loop: * * a = v128.load(XXH3_kSecret + 0 + $secret_offset, offset = 0) * b = v128.load(XXH3_kSecret + 16 + $secret_offset, offset = 0) * ... * * This requires a full 32-bit address immediate (and therefore a 6 byte * instruction) as well as an add for each offset. * * Putting an asm guard prevents it from folding (at the cost of losing * the alignment hint), and uses the free offset in `v128.load` instead * of adding secret_offset each time which overall reduces code size by * about a kilobyte and improves performance. */ XXH_COMPILER_GUARD(xsecret); #endif /* Scalar lanes use the normal scalarRound routine */ for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) { XXH3_scalarRound(acc, input, secret, i); } i = 0; /* 4 NEON lanes at a time. */ for (; i+1 < XXH3_NEON_LANES / 2; i+=2) { /* data_vec = xinput[i]; */ uint64x2_t data_vec_1 = XXH_vld1q_u64(xinput + (i * 16)); uint64x2_t data_vec_2 = XXH_vld1q_u64(xinput + ((i+1) * 16)); /* key_vec = xsecret[i]; */ uint64x2_t key_vec_1 = XXH_vld1q_u64(xsecret + (i * 16)); uint64x2_t key_vec_2 = XXH_vld1q_u64(xsecret + ((i+1) * 16)); /* data_swap = swap(data_vec) */ uint64x2_t data_swap_1 = vextq_u64(data_vec_1, data_vec_1, 1); uint64x2_t data_swap_2 = vextq_u64(data_vec_2, data_vec_2, 1); /* data_key = data_vec ^ key_vec; */ uint64x2_t data_key_1 = veorq_u64(data_vec_1, key_vec_1); uint64x2_t data_key_2 = veorq_u64(data_vec_2, key_vec_2); /* * If we reinterpret the 64x2 vectors as 32x4 vectors, we can use a * de-interleave operation for 4 lanes in 1 step with `vuzpq_u32` to * get one vector with the low 32 bits of each lane, and one vector * with the high 32 bits of each lane. * * The intrinsic returns a double vector because the original ARMv7-a * instruction modified both arguments in place. AArch64 and SIMD128 emit * two instructions from this intrinsic. * * [ dk11L | dk11H | dk12L | dk12H ] -> [ dk11L | dk12L | dk21L | dk22L ] * [ dk21L | dk21H | dk22L | dk22H ] -> [ dk11H | dk12H | dk21H | dk22H ] */ uint32x4x2_t unzipped = vuzpq_u32( vreinterpretq_u32_u64(data_key_1), vreinterpretq_u32_u64(data_key_2) ); /* data_key_lo = data_key & 0xFFFFFFFF */ uint32x4_t data_key_lo = unzipped.val[0]; /* data_key_hi = data_key >> 32 */ uint32x4_t data_key_hi = unzipped.val[1]; /* * Then, we can split the vectors horizontally and multiply which, as for most * widening intrinsics, have a variant that works on both high half vectors * for free on AArch64. A similar instruction is available on SIMD128. * * sum = data_swap + (u64x2) data_key_lo * (u64x2) data_key_hi */ uint64x2_t sum_1 = XXH_vmlal_low_u32(data_swap_1, data_key_lo, data_key_hi); uint64x2_t sum_2 = XXH_vmlal_high_u32(data_swap_2, data_key_lo, data_key_hi); /* * Clang reorders * a += b * c; // umlal swap.2d, dkl.2s, dkh.2s * c += a; // add acc.2d, acc.2d, swap.2d * to * c += a; // add acc.2d, acc.2d, swap.2d * c += b * c; // umlal acc.2d, dkl.2s, dkh.2s * * While it would make sense in theory since the addition is faster, * for reasons likely related to umlal being limited to certain NEON * pipelines, this is worse. A compiler guard fixes this. */ XXH_COMPILER_GUARD_CLANG_NEON(sum_1); XXH_COMPILER_GUARD_CLANG_NEON(sum_2); /* xacc[i] = acc_vec + sum; */ xacc[i] = vaddq_u64(xacc[i], sum_1); xacc[i+1] = vaddq_u64(xacc[i+1], sum_2); } /* Operate on the remaining NEON lanes 2 at a time. */ for (; i < XXH3_NEON_LANES / 2; i++) { /* data_vec = xinput[i]; */ uint64x2_t data_vec = XXH_vld1q_u64(xinput + (i * 16)); /* key_vec = xsecret[i]; */ uint64x2_t key_vec = XXH_vld1q_u64(xsecret + (i * 16)); /* acc_vec_2 = swap(data_vec) */ uint64x2_t data_swap = vextq_u64(data_vec, data_vec, 1); /* data_key = data_vec ^ key_vec; */ uint64x2_t data_key = veorq_u64(data_vec, key_vec); /* For two lanes, just use VMOVN and VSHRN. */ /* data_key_lo = data_key & 0xFFFFFFFF; */ uint32x2_t data_key_lo = vmovn_u64(data_key); /* data_key_hi = data_key >> 32; */ uint32x2_t data_key_hi = vshrn_n_u64(data_key, 32); /* sum = data_swap + (u64x2) data_key_lo * (u64x2) data_key_hi; */ uint64x2_t sum = vmlal_u32(data_swap, data_key_lo, data_key_hi); /* Same Clang workaround as before */ XXH_COMPILER_GUARD_CLANG_NEON(sum); /* xacc[i] = acc_vec + sum; */ xacc[i] = vaddq_u64 (xacc[i], sum); } } } XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(neon) XXH_FORCE_INLINE void XXH3_scrambleAcc_neon(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 15) == 0); { xxh_aliasing_uint64x2_t* xacc = (xxh_aliasing_uint64x2_t*) acc; uint8_t const* xsecret = (uint8_t const*) secret; size_t i; /* WASM uses operator overloads and doesn't need these. */ #ifndef __wasm_simd128__ /* { prime32_1, prime32_1 } */ uint32x2_t const kPrimeLo = vdup_n_u32(XXH_PRIME32_1); /* { 0, prime32_1, 0, prime32_1 } */ uint32x4_t const kPrimeHi = vreinterpretq_u32_u64(vdupq_n_u64((xxh_u64)XXH_PRIME32_1 << 32)); #endif /* AArch64 uses both scalar and neon at the same time */ for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) { XXH3_scalarScrambleRound(acc, secret, i); } for (i=0; i < XXH3_NEON_LANES / 2; i++) { /* xacc[i] ^= (xacc[i] >> 47); */ uint64x2_t acc_vec = xacc[i]; uint64x2_t shifted = vshrq_n_u64(acc_vec, 47); uint64x2_t data_vec = veorq_u64(acc_vec, shifted); /* xacc[i] ^= xsecret[i]; */ uint64x2_t key_vec = XXH_vld1q_u64(xsecret + (i * 16)); uint64x2_t data_key = veorq_u64(data_vec, key_vec); /* xacc[i] *= XXH_PRIME32_1 */ #ifdef __wasm_simd128__ /* SIMD128 has multiply by u64x2, use it instead of expanding and scalarizing */ xacc[i] = data_key * XXH_PRIME32_1; #else /* * Expanded version with portable NEON intrinsics * * lo(x) * lo(y) + (hi(x) * lo(y) << 32) * * prod_hi = hi(data_key) * lo(prime) << 32 * * Since we only need 32 bits of this multiply a trick can be used, reinterpreting the vector * as a uint32x4_t and multiplying by { 0, prime, 0, prime } to cancel out the unwanted bits * and avoid the shift. */ uint32x4_t prod_hi = vmulq_u32 (vreinterpretq_u32_u64(data_key), kPrimeHi); /* Extract low bits for vmlal_u32 */ uint32x2_t data_key_lo = vmovn_u64(data_key); /* xacc[i] = prod_hi + lo(data_key) * XXH_PRIME32_1; */ xacc[i] = vmlal_u32(vreinterpretq_u64_u32(prod_hi), data_key_lo, kPrimeLo); #endif } } } #endif #if (XXH_VECTOR == XXH_VSX) XXH_FORCE_INLINE void XXH3_accumulate_512_vsx( void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { /* presumed aligned */ xxh_aliasing_u64x2* const xacc = (xxh_aliasing_u64x2*) acc; xxh_u8 const* const xinput = (xxh_u8 const*) input; /* no alignment restriction */ xxh_u8 const* const xsecret = (xxh_u8 const*) secret; /* no alignment restriction */ xxh_u64x2 const v32 = { 32, 32 }; size_t i; for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) { /* data_vec = xinput[i]; */ xxh_u64x2 const data_vec = XXH_vec_loadu(xinput + 16*i); /* key_vec = xsecret[i]; */ xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + 16*i); xxh_u64x2 const data_key = data_vec ^ key_vec; /* shuffled = (data_key << 32) | (data_key >> 32); */ xxh_u32x4 const shuffled = (xxh_u32x4)vec_rl(data_key, v32); /* product = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)shuffled & 0xFFFFFFFF); */ xxh_u64x2 const product = XXH_vec_mulo((xxh_u32x4)data_key, shuffled); /* acc_vec = xacc[i]; */ xxh_u64x2 acc_vec = xacc[i]; acc_vec += product; /* swap high and low halves */ #ifdef __s390x__ acc_vec += vec_permi(data_vec, data_vec, 2); #else acc_vec += vec_xxpermdi(data_vec, data_vec, 2); #endif xacc[i] = acc_vec; } } XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(vsx) XXH_FORCE_INLINE void XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 15) == 0); { xxh_aliasing_u64x2* const xacc = (xxh_aliasing_u64x2*) acc; const xxh_u8* const xsecret = (const xxh_u8*) secret; /* constants */ xxh_u64x2 const v32 = { 32, 32 }; xxh_u64x2 const v47 = { 47, 47 }; xxh_u32x4 const prime = { XXH_PRIME32_1, XXH_PRIME32_1, XXH_PRIME32_1, XXH_PRIME32_1 }; size_t i; for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) { /* xacc[i] ^= (xacc[i] >> 47); */ xxh_u64x2 const acc_vec = xacc[i]; xxh_u64x2 const data_vec = acc_vec ^ (acc_vec >> v47); /* xacc[i] ^= xsecret[i]; */ xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + 16*i); xxh_u64x2 const data_key = data_vec ^ key_vec; /* xacc[i] *= XXH_PRIME32_1 */ /* prod_lo = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)prime & 0xFFFFFFFF); */ xxh_u64x2 const prod_even = XXH_vec_mule((xxh_u32x4)data_key, prime); /* prod_hi = ((xxh_u64x2)data_key >> 32) * ((xxh_u64x2)prime >> 32); */ xxh_u64x2 const prod_odd = XXH_vec_mulo((xxh_u32x4)data_key, prime); xacc[i] = prod_odd + (prod_even << v32); } } } #endif #if (XXH_VECTOR == XXH_SVE) XXH_FORCE_INLINE void XXH3_accumulate_512_sve( void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { uint64_t *xacc = (uint64_t *)acc; const uint64_t *xinput = (const uint64_t *)(const void *)input; const uint64_t *xsecret = (const uint64_t *)(const void *)secret; svuint64_t kSwap = sveor_n_u64_z(svptrue_b64(), svindex_u64(0, 1), 1); uint64_t element_count = svcntd(); if (element_count >= 8) { svbool_t mask = svptrue_pat_b64(SV_VL8); svuint64_t vacc = svld1_u64(mask, xacc); ACCRND(vacc, 0); svst1_u64(mask, xacc, vacc); } else if (element_count == 2) { /* sve128 */ svbool_t mask = svptrue_pat_b64(SV_VL2); svuint64_t acc0 = svld1_u64(mask, xacc + 0); svuint64_t acc1 = svld1_u64(mask, xacc + 2); svuint64_t acc2 = svld1_u64(mask, xacc + 4); svuint64_t acc3 = svld1_u64(mask, xacc + 6); ACCRND(acc0, 0); ACCRND(acc1, 2); ACCRND(acc2, 4); ACCRND(acc3, 6); svst1_u64(mask, xacc + 0, acc0); svst1_u64(mask, xacc + 2, acc1); svst1_u64(mask, xacc + 4, acc2); svst1_u64(mask, xacc + 6, acc3); } else { svbool_t mask = svptrue_pat_b64(SV_VL4); svuint64_t acc0 = svld1_u64(mask, xacc + 0); svuint64_t acc1 = svld1_u64(mask, xacc + 4); ACCRND(acc0, 0); ACCRND(acc1, 4); svst1_u64(mask, xacc + 0, acc0); svst1_u64(mask, xacc + 4, acc1); } } XXH_FORCE_INLINE void XXH3_accumulate_sve(xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT input, const xxh_u8* XXH_RESTRICT secret, size_t nbStripes) { if (nbStripes != 0) { uint64_t *xacc = (uint64_t *)acc; const uint64_t *xinput = (const uint64_t *)(const void *)input; const uint64_t *xsecret = (const uint64_t *)(const void *)secret; svuint64_t kSwap = sveor_n_u64_z(svptrue_b64(), svindex_u64(0, 1), 1); uint64_t element_count = svcntd(); if (element_count >= 8) { svbool_t mask = svptrue_pat_b64(SV_VL8); svuint64_t vacc = svld1_u64(mask, xacc + 0); do { /* svprfd(svbool_t, void *, enum svfprop); */ svprfd(mask, xinput + 128, SV_PLDL1STRM); ACCRND(vacc, 0); xinput += 8; xsecret += 1; nbStripes--; } while (nbStripes != 0); svst1_u64(mask, xacc + 0, vacc); } else if (element_count == 2) { /* sve128 */ svbool_t mask = svptrue_pat_b64(SV_VL2); svuint64_t acc0 = svld1_u64(mask, xacc + 0); svuint64_t acc1 = svld1_u64(mask, xacc + 2); svuint64_t acc2 = svld1_u64(mask, xacc + 4); svuint64_t acc3 = svld1_u64(mask, xacc + 6); do { svprfd(mask, xinput + 128, SV_PLDL1STRM); ACCRND(acc0, 0); ACCRND(acc1, 2); ACCRND(acc2, 4); ACCRND(acc3, 6); xinput += 8; xsecret += 1; nbStripes--; } while (nbStripes != 0); svst1_u64(mask, xacc + 0, acc0); svst1_u64(mask, xacc + 2, acc1); svst1_u64(mask, xacc + 4, acc2); svst1_u64(mask, xacc + 6, acc3); } else { svbool_t mask = svptrue_pat_b64(SV_VL4); svuint64_t acc0 = svld1_u64(mask, xacc + 0); svuint64_t acc1 = svld1_u64(mask, xacc + 4); do { svprfd(mask, xinput + 128, SV_PLDL1STRM); ACCRND(acc0, 0); ACCRND(acc1, 4); xinput += 8; xsecret += 1; nbStripes--; } while (nbStripes != 0); svst1_u64(mask, xacc + 0, acc0); svst1_u64(mask, xacc + 4, acc1); } } } #endif #if (XXH_VECTOR == XXH_LSX) #define _LSX_SHUFFLE(z, y, x, w) (((z) << 6) | ((y) << 4) | ((x) << 2) | (w)) XXH_FORCE_INLINE void XXH3_accumulate_512_lsx( void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 15) == 0); { __m128i* const xacc = (__m128i *) acc; const __m128i* const xinput = (const __m128i *) input; const __m128i* const xsecret = (const __m128i *) secret; size_t i; for (i = 0; i < XXH_STRIPE_LEN / sizeof(__m128i); i++) { /* data_vec = xinput[i]; */ __m128i const data_vec = __lsx_vld(xinput + i, 0); /* key_vec = xsecret[i]; */ __m128i const key_vec = __lsx_vld(xsecret + i, 0); /* data_key = data_vec ^ key_vec; */ __m128i const data_key = __lsx_vxor_v(data_vec, key_vec); /* data_key_lo = data_key >> 32; */ __m128i const data_key_lo = __lsx_vsrli_d(data_key, 32); // __m128i const data_key_lo = __lsx_vsrli_d(data_key, 32); /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ __m128i const product = __lsx_vmulwev_d_wu(data_key, data_key_lo); /* xacc[i] += swap(data_vec); */ __m128i const data_swap = __lsx_vshuf4i_w(data_vec, _LSX_SHUFFLE(1, 0, 3, 2)); __m128i const sum = __lsx_vadd_d(xacc[i], data_swap); /* xacc[i] += product; */ xacc[i] = __lsx_vadd_d(product, sum); } } } XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(lsx) XXH_FORCE_INLINE void XXH3_scrambleAcc_lsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 15) == 0); { __m128i* const xacc = (__m128i*) acc; const __m128i* const xsecret = (const __m128i *) secret; const __m128i prime32 = __lsx_vreplgr2vr_d(XXH_PRIME32_1); size_t i; for (i = 0; i < XXH_STRIPE_LEN / sizeof(__m128i); i++) { /* xacc[i] ^= (xacc[i] >> 47) */ __m128i const acc_vec = xacc[i]; __m128i const shifted = __lsx_vsrli_d(acc_vec, 47); __m128i const data_vec = __lsx_vxor_v(acc_vec, shifted); /* xacc[i] ^= xsecret[i]; */ __m128i const key_vec = __lsx_vld(xsecret + i, 0); __m128i const data_key = __lsx_vxor_v(data_vec, key_vec); /* xacc[i] *= XXH_PRIME32_1; */ xacc[i] = __lsx_vmul_d(data_key, prime32); } } } #endif #if (XXH_VECTOR == XXH_LASX) #define _LASX_SHUFFLE(z, y, x, w) (((z) << 6) | ((y) << 4) | ((x) << 2) | (w)) XXH_FORCE_INLINE void XXH3_accumulate_512_lasx( void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 31) == 0); { size_t i; __m256i* const xacc = (__m256i *) acc; const __m256i* const xinput = (const __m256i *) input; const __m256i* const xsecret = (const __m256i *) secret; for (i = 0; i < XXH_STRIPE_LEN / sizeof(__m256i); i++) { /* data_vec = xinput[i]; */ __m256i const data_vec = __lasx_xvld(xinput + i, 0); /* key_vec = xsecret[i]; */ __m256i const key_vec = __lasx_xvld(xsecret + i, 0); /* data_key = data_vec ^ key_vec; */ __m256i const data_key = __lasx_xvxor_v(data_vec, key_vec); /* data_key_lo = data_key >> 32; */ __m256i const data_key_lo = __lasx_xvsrli_d(data_key, 32); // __m256i const data_key_lo = __lasx_xvsrli_d(data_key, 32); /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */ __m256i const product = __lasx_xvmulwev_d_wu(data_key, data_key_lo); /* xacc[i] += swap(data_vec); */ __m256i const data_swap = __lasx_xvshuf4i_w(data_vec, _LASX_SHUFFLE(1, 0, 3, 2)); __m256i const sum = __lasx_xvadd_d(xacc[i], data_swap); /* xacc[i] += product; */ xacc[i] = __lasx_xvadd_d(product, sum); } } } XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(lasx) XXH_FORCE_INLINE void XXH3_scrambleAcc_lasx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 31) == 0); { __m256i* const xacc = (__m256i*) acc; const __m256i* const xsecret = (const __m256i *) secret; const __m256i prime32 = __lasx_xvreplgr2vr_d(XXH_PRIME32_1); size_t i; for (i = 0; i < XXH_STRIPE_LEN / sizeof(__m256i); i++) { /* xacc[i] ^= (xacc[i] >> 47) */ __m256i const acc_vec = xacc[i]; __m256i const shifted = __lasx_xvsrli_d(acc_vec, 47); __m256i const data_vec = __lasx_xvxor_v(acc_vec, shifted); /* xacc[i] ^= xsecret[i]; */ __m256i const key_vec = __lasx_xvld(xsecret + i, 0); __m256i const data_key = __lasx_xvxor_v(data_vec, key_vec); /* xacc[i] *= XXH_PRIME32_1; */ xacc[i] = __lasx_xvmul_d(data_key, prime32); } } } #endif #if (XXH_VECTOR == XXH_RVV) #define XXH_CONCAT2(X, Y) X ## Y #define XXH_CONCAT(X, Y) XXH_CONCAT2(X, Y) #if ((defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 13) || \ (defined(__clang__) && __clang_major__ < 16)) #define XXH_RVOP(op) op #define XXH_RVCAST(op) XXH_CONCAT(vreinterpret_v_, op) #else #define XXH_RVOP(op) XXH_CONCAT(__riscv_, op) #define XXH_RVCAST(op) XXH_CONCAT(__riscv_vreinterpret_v_, op) #endif XXH_FORCE_INLINE void XXH3_accumulate_512_rvv( void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 63) == 0); { // Try to set vector lenght to 512 bits. // If this length is unavailable, then maximum available will be used size_t vl = XXH_RVOP(vsetvl_e64m2)(8); uint64_t* xacc = (uint64_t*) acc; const uint64_t* xinput = (const uint64_t*) input; const uint64_t* xsecret = (const uint64_t*) secret; static const uint64_t swap_mask[16] = {1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14}; vuint64m2_t xswap_mask = XXH_RVOP(vle64_v_u64m2)(swap_mask, vl); size_t i; for (i = 0; i < XXH_STRIPE_LEN/8; i += vl) { /* data_vec = xinput[i]; */ vuint64m2_t data_vec = XXH_RVCAST(u8m2_u64m2)(XXH_RVOP(vle8_v_u8m2)((const uint8_t*)(xinput + i), vl * 8)); /* key_vec = xsecret[i]; */ vuint64m2_t key_vec = XXH_RVCAST(u8m2_u64m2)(XXH_RVOP(vle8_v_u8m2)((const uint8_t*)(xsecret + i), vl * 8)); /* acc_vec = xacc[i]; */ vuint64m2_t acc_vec = XXH_RVOP(vle64_v_u64m2)(xacc + i, vl); /* data_key = data_vec ^ key_vec; */ vuint64m2_t data_key = XXH_RVOP(vxor_vv_u64m2)(data_vec, key_vec, vl); /* data_key_hi = data_key >> 32; */ vuint64m2_t data_key_hi = XXH_RVOP(vsrl_vx_u64m2)(data_key, 32, vl); /* data_key_lo = data_key & 0xffffffff; */ vuint64m2_t data_key_lo = XXH_RVOP(vand_vx_u64m2)(data_key, 0xffffffff, vl); /* swap high and low halves */ vuint64m2_t data_swap = XXH_RVOP(vrgather_vv_u64m2)(data_vec, xswap_mask, vl); /* acc_vec += data_key_lo * data_key_hi; */ acc_vec = XXH_RVOP(vmacc_vv_u64m2)(acc_vec, data_key_lo, data_key_hi, vl); /* acc_vec += data_swap; */ acc_vec = XXH_RVOP(vadd_vv_u64m2)(acc_vec, data_swap, vl); /* xacc[i] = acc_vec; */ XXH_RVOP(vse64_v_u64m2)(xacc + i, acc_vec, vl); } } } XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(rvv) XXH_FORCE_INLINE void XXH3_scrambleAcc_rvv(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { XXH_ASSERT((((size_t)acc) & 15) == 0); { size_t count = XXH_STRIPE_LEN/8; uint64_t* xacc = (uint64_t*)acc; const uint8_t* xsecret = (const uint8_t *)secret; size_t vl; for (; count > 0; count -= vl, xacc += vl, xsecret += vl*8) { vl = XXH_RVOP(vsetvl_e64m2)(count); { /* key_vec = xsecret[i]; */ vuint64m2_t key_vec = XXH_RVCAST(u8m2_u64m2)(XXH_RVOP(vle8_v_u8m2)(xsecret, vl*8)); /* acc_vec = xacc[i]; */ vuint64m2_t acc_vec = XXH_RVOP(vle64_v_u64m2)(xacc, vl); /* acc_vec ^= acc_vec >> 47; */ vuint64m2_t vsrl = XXH_RVOP(vsrl_vx_u64m2)(acc_vec, 47, vl); acc_vec = XXH_RVOP(vxor_vv_u64m2)(acc_vec, vsrl, vl); /* acc_vec ^= key_vec; */ acc_vec = XXH_RVOP(vxor_vv_u64m2)(acc_vec, key_vec, vl); /* acc_vec *= XXH_PRIME32_1; */ acc_vec = XXH_RVOP(vmul_vx_u64m2)(acc_vec, XXH_PRIME32_1, vl); /* xacc[i] *= acc_vec; */ XXH_RVOP(vse64_v_u64m2)(xacc, acc_vec, vl); } } } } XXH_FORCE_INLINE void XXH3_initCustomSecret_rvv(void* XXH_RESTRICT customSecret, xxh_u64 seed64) { XXH_STATIC_ASSERT(XXH_SEC_ALIGN >= 8); XXH_ASSERT(((size_t)customSecret & 7) == 0); (void)(&XXH_writeLE64); { size_t count = XXH_SECRET_DEFAULT_SIZE/8; size_t vl; size_t VLMAX = XXH_RVOP(vsetvlmax_e64m2)(); int64_t* cSecret = (int64_t*)customSecret; const int64_t* kSecret = (const int64_t*)(const void*)XXH3_kSecret; #if __riscv_v_intrinsic >= 1000000 // ratified v1.0 intrinics version vbool32_t mneg = XXH_RVCAST(u8m1_b32)( XXH_RVOP(vmv_v_x_u8m1)(0xaa, XXH_RVOP(vsetvlmax_e8m1)())); #else // support pre-ratification intrinics, which lack mask to vector casts size_t vlmax = XXH_RVOP(vsetvlmax_e8m1)(); vbool32_t mneg = XXH_RVOP(vmseq_vx_u8mf4_b32)( XXH_RVOP(vand_vx_u8mf4)( XXH_RVOP(vid_v_u8mf4)(vlmax), 1, vlmax), 1, vlmax); #endif vint64m2_t seed = XXH_RVOP(vmv_v_x_i64m2)((int64_t)seed64, VLMAX); seed = XXH_RVOP(vneg_v_i64m2_mu)(mneg, seed, seed, VLMAX); for (; count > 0; count -= vl, cSecret += vl, kSecret += vl) { /* make sure vl=VLMAX until last iteration */ vl = XXH_RVOP(vsetvl_e64m2)(count < VLMAX ? count : VLMAX); { vint64m2_t src = XXH_RVOP(vle64_v_i64m2)(kSecret, vl); vint64m2_t res = XXH_RVOP(vadd_vv_i64m2)(src, seed, vl); XXH_RVOP(vse64_v_i64m2)(cSecret, res, vl); } } } } #endif /* scalar variants - universal */ #if defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) /* * In XXH3_scalarRound(), GCC and Clang have a similar codegen issue, where they * emit an excess mask and a full 64-bit multiply-add (MADD X-form). * * While this might not seem like much, as AArch64 is a 64-bit architecture, only * big Cortex designs have a full 64-bit multiplier. * * On the little cores, the smaller 32-bit multiplier is used, and full 64-bit * multiplies expand to 2-3 multiplies in microcode. This has a major penalty * of up to 4 latency cycles and 2 stall cycles in the multiply pipeline. * * Thankfully, AArch64 still provides the 32-bit long multiply-add (UMADDL) which does * not have this penalty and does the mask automatically. */ XXH_FORCE_INLINE xxh_u64 XXH_mult32to64_add64(xxh_u64 lhs, xxh_u64 rhs, xxh_u64 acc) { xxh_u64 ret; /* note: %x = 64-bit register, %w = 32-bit register */ __asm__("umaddl %x0, %w1, %w2, %x3" : "=r" (ret) : "r" (lhs), "r" (rhs), "r" (acc)); return ret; } #else XXH_FORCE_INLINE xxh_u64 XXH_mult32to64_add64(xxh_u64 lhs, xxh_u64 rhs, xxh_u64 acc) { return XXH_mult32to64((xxh_u32)lhs, (xxh_u32)rhs) + acc; } #endif /*! * @internal * @brief Scalar round for @ref XXH3_accumulate_512_scalar(). * * This is extracted to its own function because the NEON path uses a combination * of NEON and scalar. */ XXH_FORCE_INLINE void XXH3_scalarRound(void* XXH_RESTRICT acc, void const* XXH_RESTRICT input, void const* XXH_RESTRICT secret, size_t lane) { xxh_u64* xacc = (xxh_u64*) acc; xxh_u8 const* xinput = (xxh_u8 const*) input; xxh_u8 const* xsecret = (xxh_u8 const*) secret; XXH_ASSERT(lane < XXH_ACC_NB); XXH_ASSERT(((size_t)acc & (XXH_ACC_ALIGN-1)) == 0); { xxh_u64 const data_val = XXH_readLE64(xinput + lane * 8); xxh_u64 const data_key = data_val ^ XXH_readLE64(xsecret + lane * 8); xacc[lane ^ 1] += data_val; /* swap adjacent lanes */ xacc[lane] = XXH_mult32to64_add64(data_key /* & 0xFFFFFFFF */, data_key >> 32, xacc[lane]); } } /*! * @internal * @brief Processes a 64 byte block of data using the scalar path. */ XXH_FORCE_INLINE void XXH3_accumulate_512_scalar(void* XXH_RESTRICT acc, const void* XXH_RESTRICT input, const void* XXH_RESTRICT secret) { size_t i; /* ARM GCC refuses to unroll this loop, resulting in a 24% slowdown on ARMv6. */ #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 8 \ && (defined(__arm__) || defined(__thumb2__)) \ && defined(__ARM_FEATURE_UNALIGNED) /* no unaligned access just wastes bytes */ \ && XXH_SIZE_OPT <= 0 # pragma GCC unroll 8 #endif for (i=0; i < XXH_ACC_NB; i++) { XXH3_scalarRound(acc, input, secret, i); } } XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(scalar) /*! * @internal * @brief Scalar scramble step for @ref XXH3_scrambleAcc_scalar(). * * This is extracted to its own function because the NEON path uses a combination * of NEON and scalar. */ XXH_FORCE_INLINE void XXH3_scalarScrambleRound(void* XXH_RESTRICT acc, void const* XXH_RESTRICT secret, size_t lane) { xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned */ const xxh_u8* const xsecret = (const xxh_u8*) secret; /* no alignment restriction */ XXH_ASSERT((((size_t)acc) & (XXH_ACC_ALIGN-1)) == 0); XXH_ASSERT(lane < XXH_ACC_NB); { xxh_u64 const key64 = XXH_readLE64(xsecret + lane * 8); xxh_u64 acc64 = xacc[lane]; acc64 = XXH_xorshift64(acc64, 47); acc64 ^= key64; acc64 *= XXH_PRIME32_1; xacc[lane] = acc64; } } /*! * @internal * @brief Scrambles the accumulators after a large chunk has been read */ XXH_FORCE_INLINE void XXH3_scrambleAcc_scalar(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret) { size_t i; for (i=0; i < XXH_ACC_NB; i++) { XXH3_scalarScrambleRound(acc, secret, i); } } XXH_FORCE_INLINE void XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64) { /* * We need a separate pointer for the hack below, * which requires a non-const pointer. * Any decent compiler will optimize this out otherwise. */ const xxh_u8* kSecretPtr = XXH3_kSecret; XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0); #if defined(__GNUC__) && defined(__aarch64__) /* * UGLY HACK: * GCC and Clang generate a bunch of MOV/MOVK pairs for aarch64, and they are * placed sequentially, in order, at the top of the unrolled loop. * * While MOVK is great for generating constants (2 cycles for a 64-bit * constant compared to 4 cycles for LDR), it fights for bandwidth with * the arithmetic instructions. * * I L S * MOVK * MOVK * MOVK * MOVK * ADD * SUB STR * STR * By forcing loads from memory (as the asm line causes the compiler to assume * that XXH3_kSecretPtr has been changed), the pipelines are used more * efficiently: * I L S * LDR * ADD LDR * SUB STR * STR * * See XXH3_NEON_LANES for details on the pipeline. * * XXH3_64bits_withSeed, len == 256, Snapdragon 835 * without hack: 2654.4 MB/s * with hack: 3202.9 MB/s */ XXH_COMPILER_GUARD(kSecretPtr); #endif { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / 16; int i; for (i=0; i < nbRounds; i++) { /* * The asm hack causes the compiler to assume that kSecretPtr aliases with * customSecret, and on aarch64, this prevented LDP from merging two * loads together for free. Putting the loads together before the stores * properly generates LDP. */ xxh_u64 lo = XXH_readLE64(kSecretPtr + 16*i) + seed64; xxh_u64 hi = XXH_readLE64(kSecretPtr + 16*i + 8) - seed64; XXH_writeLE64((xxh_u8*)customSecret + 16*i, lo); XXH_writeLE64((xxh_u8*)customSecret + 16*i + 8, hi); } } } typedef void (*XXH3_f_accumulate)(xxh_u64* XXH_RESTRICT, const xxh_u8* XXH_RESTRICT, const xxh_u8* XXH_RESTRICT, size_t); typedef void (*XXH3_f_scrambleAcc)(void* XXH_RESTRICT, const void*); typedef void (*XXH3_f_initCustomSecret)(void* XXH_RESTRICT, xxh_u64); #if (XXH_VECTOR == XXH_AVX512) #define XXH3_accumulate_512 XXH3_accumulate_512_avx512 #define XXH3_accumulate XXH3_accumulate_avx512 #define XXH3_scrambleAcc XXH3_scrambleAcc_avx512 #define XXH3_initCustomSecret XXH3_initCustomSecret_avx512 #elif (XXH_VECTOR == XXH_AVX2) #define XXH3_accumulate_512 XXH3_accumulate_512_avx2 #define XXH3_accumulate XXH3_accumulate_avx2 #define XXH3_scrambleAcc XXH3_scrambleAcc_avx2 #define XXH3_initCustomSecret XXH3_initCustomSecret_avx2 #elif (XXH_VECTOR == XXH_SSE2) #define XXH3_accumulate_512 XXH3_accumulate_512_sse2 #define XXH3_accumulate XXH3_accumulate_sse2 #define XXH3_scrambleAcc XXH3_scrambleAcc_sse2 #define XXH3_initCustomSecret XXH3_initCustomSecret_sse2 #elif (XXH_VECTOR == XXH_NEON) #define XXH3_accumulate_512 XXH3_accumulate_512_neon #define XXH3_accumulate XXH3_accumulate_neon #define XXH3_scrambleAcc XXH3_scrambleAcc_neon #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar #elif (XXH_VECTOR == XXH_VSX) #define XXH3_accumulate_512 XXH3_accumulate_512_vsx #define XXH3_accumulate XXH3_accumulate_vsx #define XXH3_scrambleAcc XXH3_scrambleAcc_vsx #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar #elif (XXH_VECTOR == XXH_SVE) #define XXH3_accumulate_512 XXH3_accumulate_512_sve #define XXH3_accumulate XXH3_accumulate_sve #define XXH3_scrambleAcc XXH3_scrambleAcc_scalar #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar #elif (XXH_VECTOR == XXH_LASX) #define XXH3_accumulate_512 XXH3_accumulate_512_lasx #define XXH3_accumulate XXH3_accumulate_lasx #define XXH3_scrambleAcc XXH3_scrambleAcc_lasx #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar #elif (XXH_VECTOR == XXH_LSX) #define XXH3_accumulate_512 XXH3_accumulate_512_lsx #define XXH3_accumulate XXH3_accumulate_lsx #define XXH3_scrambleAcc XXH3_scrambleAcc_lsx #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar #elif (XXH_VECTOR == XXH_RVV) #define XXH3_accumulate_512 XXH3_accumulate_512_rvv #define XXH3_accumulate XXH3_accumulate_rvv #define XXH3_scrambleAcc XXH3_scrambleAcc_rvv #define XXH3_initCustomSecret XXH3_initCustomSecret_rvv #else /* scalar */ #define XXH3_accumulate_512 XXH3_accumulate_512_scalar #define XXH3_accumulate XXH3_accumulate_scalar #define XXH3_scrambleAcc XXH3_scrambleAcc_scalar #define XXH3_initCustomSecret XXH3_initCustomSecret_scalar #endif #if XXH_SIZE_OPT >= 1 /* don't do SIMD for initialization */ # undef XXH3_initCustomSecret # define XXH3_initCustomSecret XXH3_initCustomSecret_scalar #endif XXH_FORCE_INLINE void XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { size_t const nbStripesPerBlock = (secretSize - XXH_STRIPE_LEN) / XXH_SECRET_CONSUME_RATE; size_t const block_len = XXH_STRIPE_LEN * nbStripesPerBlock; size_t const nb_blocks = (len - 1) / block_len; size_t n; XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); for (n = 0; n < nb_blocks; n++) { f_acc(acc, input + n*block_len, secret, nbStripesPerBlock); f_scramble(acc, secret + secretSize - XXH_STRIPE_LEN); } /* last partial block */ XXH_ASSERT(len > XXH_STRIPE_LEN); { size_t const nbStripes = ((len - 1) - (block_len * nb_blocks)) / XXH_STRIPE_LEN; XXH_ASSERT(nbStripes <= (secretSize / XXH_SECRET_CONSUME_RATE)); f_acc(acc, input + nb_blocks*block_len, secret, nbStripes); /* last stripe */ { const xxh_u8* const p = input + len - XXH_STRIPE_LEN; #define XXH_SECRET_LASTACC_START 7 /* not aligned on 8, last secret is different from acc & scrambler */ XXH3_accumulate_512(acc, p, secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_LASTACC_START); } } } XXH_FORCE_INLINE xxh_u64 XXH3_mix2Accs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret) { return XXH3_mul128_fold64( acc[0] ^ XXH_readLE64(secret), acc[1] ^ XXH_readLE64(secret+8) ); } static XXH_PUREF XXH64_hash_t XXH3_mergeAccs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret, xxh_u64 start) { xxh_u64 result64 = start; size_t i = 0; for (i = 0; i < 4; i++) { result64 += XXH3_mix2Accs(acc+2*i, secret + 16*i); #if defined(__clang__) /* Clang */ \ && (defined(__arm__) || defined(__thumb__)) /* ARMv7 */ \ && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable */ /* * UGLY HACK: * Prevent autovectorization on Clang ARMv7-a. Exact same problem as * the one in XXH3_len_129to240_64b. Speeds up shorter keys > 240b. * XXH3_64bits, len == 256, Snapdragon 835: * without hack: 2063.7 MB/s * with hack: 2560.7 MB/s */ XXH_COMPILER_GUARD(result64); #endif } return XXH3_avalanche(result64); } /* do not align on 8, so that the secret is different from the accumulator */ #define XXH_SECRET_MERGEACCS_START 11 static XXH_PUREF XXH64_hash_t XXH3_finalizeLong_64b(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret, xxh_u64 len) { return XXH3_mergeAccs(acc, secret + XXH_SECRET_MERGEACCS_START, len * XXH_PRIME64_1); } #define XXH3_INIT_ACC { XXH_PRIME32_3, XXH_PRIME64_1, XXH_PRIME64_2, XXH_PRIME64_3, \ XXH_PRIME64_4, XXH_PRIME32_2, XXH_PRIME64_5, XXH_PRIME32_1 } XXH_FORCE_INLINE XXH64_hash_t XXH3_hashLong_64b_internal(const void* XXH_RESTRICT input, size_t len, const void* XXH_RESTRICT secret, size_t secretSize, XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC; XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, f_acc, f_scramble); /* converge into final hash */ XXH_STATIC_ASSERT(sizeof(acc) == 64); XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START); return XXH3_finalizeLong_64b(acc, (const xxh_u8*)secret, (xxh_u64)len); } /* * It's important for performance to transmit secret's size (when it's static) * so that the compiler can properly optimize the vectorized loop. * This makes a big performance difference for "medium" keys (<1 KB) when using AVX instruction set. * When the secret size is unknown, or on GCC 12 where the mix of NO_INLINE and FORCE_INLINE * breaks -Og, this is XXH_NO_INLINE. */ XXH3_WITH_SECRET_INLINE XXH64_hash_t XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen) { (void)seed64; return XXH3_hashLong_64b_internal(input, len, secret, secretLen, XXH3_accumulate, XXH3_scrambleAcc); } /* * It's preferable for performance that XXH3_hashLong is not inlined, * as it results in a smaller function for small data, easier to the instruction cache. * Note that inside this no_inline function, we do inline the internal loop, * and provide a statically defined secret size to allow optimization of vector loop. */ XXH_NO_INLINE XXH_PUREF XXH64_hash_t XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen) { (void)seed64; (void)secret; (void)secretLen; return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate, XXH3_scrambleAcc); } /* * XXH3_hashLong_64b_withSeed(): * Generate a custom key based on alteration of default XXH3_kSecret with the seed, * and then use this key for long mode hashing. * * This operation is decently fast but nonetheless costs a little bit of time. * Try to avoid it whenever possible (typically when seed==0). * * It's important for performance that XXH3_hashLong is not inlined. Not sure * why (uop cache maybe?), but the difference is large and easily measurable. */ XXH_FORCE_INLINE XXH64_hash_t XXH3_hashLong_64b_withSeed_internal(const void* input, size_t len, XXH64_hash_t seed, XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble, XXH3_f_initCustomSecret f_initSec) { #if XXH_SIZE_OPT <= 0 if (seed == 0) return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), f_acc, f_scramble); #endif { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE]; f_initSec(secret, seed); return XXH3_hashLong_64b_internal(input, len, secret, sizeof(secret), f_acc, f_scramble); } } /* * It's important for performance that XXH3_hashLong is not inlined. */ XXH_NO_INLINE XXH64_hash_t XXH3_hashLong_64b_withSeed(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed, const xxh_u8* XXH_RESTRICT secret, size_t secretLen) { (void)secret; (void)secretLen; return XXH3_hashLong_64b_withSeed_internal(input, len, seed, XXH3_accumulate, XXH3_scrambleAcc, XXH3_initCustomSecret); } typedef XXH64_hash_t (*XXH3_hashLong64_f)(const void* XXH_RESTRICT, size_t, XXH64_hash_t, const xxh_u8* XXH_RESTRICT, size_t); XXH_FORCE_INLINE XXH64_hash_t XXH3_64bits_internal(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen, XXH3_hashLong64_f f_hashLong) { XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN); /* * If an action is to be taken if `secretLen` condition is not respected, * it should be done here. * For now, it's a contract pre-condition. * Adding a check and a branch here would cost performance at every hash. * Also, note that function signature doesn't offer room to return an error. */ if (len <= 16) return XXH3_len_0to16_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, seed64); if (len <= 128) return XXH3_len_17to128_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64); if (len <= XXH3_MIDSIZE_MAX) return XXH3_len_129to240_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64); return f_hashLong(input, len, seed64, (const xxh_u8*)secret, secretLen); } /* === Public entry point === */ /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(XXH_NOESCAPE const void* input, size_t length) { return XXH3_64bits_internal(input, length, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecret(XXH_NOESCAPE const void* input, size_t length, XXH_NOESCAPE const void* secret, size_t secretSize) { return XXH3_64bits_internal(input, length, 0, secret, secretSize, XXH3_hashLong_64b_withSecret); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSeed(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed) { return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed); } XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t length, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed) { if (length <= XXH3_MIDSIZE_MAX) return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL); return XXH3_hashLong_64b_withSecret(input, length, seed, (const xxh_u8*)secret, secretSize); } /* === XXH3 streaming === */ #ifndef XXH_NO_STREAM /* * Malloc's a pointer that is always aligned to @align. * * This must be freed with `XXH_alignedFree()`. * * malloc typically guarantees 16 byte alignment on 64-bit systems and 8 byte * alignment on 32-bit. This isn't enough for the 32 byte aligned loads in AVX2 * or on 32-bit, the 16 byte aligned loads in SSE2 and NEON. * * This underalignment previously caused a rather obvious crash which went * completely unnoticed due to XXH3_createState() not actually being tested. * Credit to RedSpah for noticing this bug. * * The alignment is done manually: Functions like posix_memalign or _mm_malloc * are avoided: To maintain portability, we would have to write a fallback * like this anyways, and besides, testing for the existence of library * functions without relying on external build tools is impossible. * * The method is simple: Overallocate, manually align, and store the offset * to the original behind the returned pointer. * * Align must be a power of 2 and 8 <= align <= 128. */ static XXH_MALLOCF void* XXH_alignedMalloc(size_t s, size_t align) { XXH_ASSERT(align <= 128 && align >= 8); /* range check */ XXH_ASSERT((align & (align-1)) == 0); /* power of 2 */ XXH_ASSERT(s != 0 && s < (s + align)); /* empty/overflow */ { /* Overallocate to make room for manual realignment and an offset byte */ xxh_u8* base = (xxh_u8*)XXH_malloc(s + align); if (base != NULL) { /* * Get the offset needed to align this pointer. * * Even if the returned pointer is aligned, there will always be * at least one byte to store the offset to the original pointer. */ size_t offset = align - ((size_t)base & (align - 1)); /* base % align */ /* Add the offset for the now-aligned pointer */ xxh_u8* ptr = base + offset; XXH_ASSERT((size_t)ptr % align == 0); /* Store the offset immediately before the returned pointer. */ ptr[-1] = (xxh_u8)offset; return ptr; } return NULL; } } /* * Frees an aligned pointer allocated by XXH_alignedMalloc(). Don't pass * normal malloc'd pointers, XXH_alignedMalloc has a specific data layout. */ static void XXH_alignedFree(void* p) { if (p != NULL) { xxh_u8* ptr = (xxh_u8*)p; /* Get the offset byte we added in XXH_malloc. */ xxh_u8 offset = ptr[-1]; /* Free the original malloc'd pointer */ xxh_u8* base = ptr - offset; XXH_free(base); } } /*! @ingroup XXH3_family */ /*! * @brief Allocate an @ref XXH3_state_t. * * @return An allocated pointer of @ref XXH3_state_t on success. * @return `NULL` on failure. * * @note Must be freed with XXH3_freeState(). * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void) { XXH3_state_t* const state = (XXH3_state_t*)XXH_alignedMalloc(sizeof(XXH3_state_t), 64); if (state==NULL) return NULL; XXH3_INITSTATE(state); return state; } /*! @ingroup XXH3_family */ /*! * @brief Frees an @ref XXH3_state_t. * * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState(). * * @return @ref XXH_OK. * * @note Must be allocated with XXH3_createState(). * * @see @ref streaming_example "Streaming Example" */ XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr) { XXH_alignedFree(statePtr); return XXH_OK; } /*! @ingroup XXH3_family */ XXH_PUBLIC_API void XXH3_copyState(XXH_NOESCAPE XXH3_state_t* dst_state, XXH_NOESCAPE const XXH3_state_t* src_state) { XXH_memcpy(dst_state, src_state, sizeof(*dst_state)); } static void XXH3_reset_internal(XXH3_state_t* statePtr, XXH64_hash_t seed, const void* secret, size_t secretSize) { size_t const initStart = offsetof(XXH3_state_t, bufferedSize); size_t const initLength = offsetof(XXH3_state_t, nbStripesPerBlock) - initStart; XXH_ASSERT(offsetof(XXH3_state_t, nbStripesPerBlock) > initStart); XXH_ASSERT(statePtr != NULL); /* set members from bufferedSize to nbStripesPerBlock (excluded) to 0 */ XXH_memset((char*)statePtr + initStart, 0, initLength); statePtr->acc[0] = XXH_PRIME32_3; statePtr->acc[1] = XXH_PRIME64_1; statePtr->acc[2] = XXH_PRIME64_2; statePtr->acc[3] = XXH_PRIME64_3; statePtr->acc[4] = XXH_PRIME64_4; statePtr->acc[5] = XXH_PRIME32_2; statePtr->acc[6] = XXH_PRIME64_5; statePtr->acc[7] = XXH_PRIME32_1; statePtr->seed = seed; statePtr->useSeed = (seed != 0); statePtr->extSecret = (const unsigned char*)secret; XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); statePtr->secretLimit = secretSize - XXH_STRIPE_LEN; statePtr->nbStripesPerBlock = statePtr->secretLimit / XXH_SECRET_CONSUME_RATE; } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr) { if (statePtr == NULL) return XXH_ERROR; XXH3_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE); return XXH_OK; } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize) { if (statePtr == NULL) return XXH_ERROR; XXH3_reset_internal(statePtr, 0, secret, secretSize); if (secret == NULL) return XXH_ERROR; if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR; return XXH_OK; } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed) { if (statePtr == NULL) return XXH_ERROR; if (seed==0) return XXH3_64bits_reset(statePtr); if ((seed != statePtr->seed) || (statePtr->extSecret != NULL)) XXH3_initCustomSecret(statePtr->customSecret, seed); XXH3_reset_internal(statePtr, seed, NULL, XXH_SECRET_DEFAULT_SIZE); return XXH_OK; } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64) { if (statePtr == NULL) return XXH_ERROR; if (secret == NULL) return XXH_ERROR; if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR; XXH3_reset_internal(statePtr, seed64, secret, secretSize); statePtr->useSeed = 1; /* always, even if seed64==0 */ return XXH_OK; } /*! * @internal * @brief Processes a large input for XXH3_update() and XXH3_digest_long(). * * Unlike XXH3_hashLong_internal_loop(), this can process data that overlaps a block. * * @param acc Pointer to the 8 accumulator lanes * @param nbStripesSoFarPtr In/out pointer to the number of leftover stripes in the block* * @param nbStripesPerBlock Number of stripes in a block * @param input Input pointer * @param nbStripes Number of stripes to process * @param secret Secret pointer * @param secretLimit Offset of the last block in @p secret * @param f_acc Pointer to an XXH3_accumulate implementation * @param f_scramble Pointer to an XXH3_scrambleAcc implementation * @return Pointer past the end of @p input after processing */ XXH_FORCE_INLINE const xxh_u8 * XXH3_consumeStripes(xxh_u64* XXH_RESTRICT acc, size_t* XXH_RESTRICT nbStripesSoFarPtr, size_t nbStripesPerBlock, const xxh_u8* XXH_RESTRICT input, size_t nbStripes, const xxh_u8* XXH_RESTRICT secret, size_t secretLimit, XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { const xxh_u8* initialSecret = secret + *nbStripesSoFarPtr * XXH_SECRET_CONSUME_RATE; /* Process full blocks */ if (nbStripes >= (nbStripesPerBlock - *nbStripesSoFarPtr)) { /* Process the initial partial block... */ size_t nbStripesThisIter = nbStripesPerBlock - *nbStripesSoFarPtr; do { /* Accumulate and scramble */ f_acc(acc, input, initialSecret, nbStripesThisIter); f_scramble(acc, secret + secretLimit); input += nbStripesThisIter * XXH_STRIPE_LEN; nbStripes -= nbStripesThisIter; /* Then continue the loop with the full block size */ nbStripesThisIter = nbStripesPerBlock; initialSecret = secret; } while (nbStripes >= nbStripesPerBlock); *nbStripesSoFarPtr = 0; } /* Process a partial block */ if (nbStripes > 0) { f_acc(acc, input, initialSecret, nbStripes); input += nbStripes * XXH_STRIPE_LEN; *nbStripesSoFarPtr += nbStripes; } /* Return end pointer */ return input; } #ifndef XXH3_STREAM_USE_STACK # if XXH_SIZE_OPT <= 0 && !defined(__clang__) /* clang doesn't need additional stack space */ # define XXH3_STREAM_USE_STACK 1 # endif #endif /* This function accepts f_acc and f_scramble as function pointers, * making it possible to implement multiple variants with different acc & scramble stages. * This is notably useful to implement multiple vector variants with different intrinsics. */ XXH_FORCE_INLINE XXH_errorcode XXH3_update(XXH3_state_t* XXH_RESTRICT const state, const xxh_u8* XXH_RESTRICT input, size_t len, XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { if (input==NULL) { XXH_ASSERT(len == 0); return XXH_OK; } XXH_ASSERT(state != NULL); state->totalLen += len; /* small input : just fill in tmp buffer */ XXH_ASSERT(state->bufferedSize <= XXH3_INTERNALBUFFER_SIZE); if (len <= XXH3_INTERNALBUFFER_SIZE - state->bufferedSize) { XXH_memcpy(state->buffer + state->bufferedSize, input, len); state->bufferedSize += (XXH32_hash_t)len; return XXH_OK; } { const xxh_u8* const bEnd = input + len; const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret; #if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1 /* For some reason, gcc and MSVC seem to suffer greatly * when operating accumulators directly into state. * Operating into stack space seems to enable proper optimization. * clang, on the other hand, doesn't seem to need this trick */ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[8]; XXH_memcpy(acc, state->acc, sizeof(acc)); #else xxh_u64* XXH_RESTRICT const acc = state->acc; #endif /* total input is now > XXH3_INTERNALBUFFER_SIZE */ #define XXH3_INTERNALBUFFER_STRIPES (XXH3_INTERNALBUFFER_SIZE / XXH_STRIPE_LEN) XXH_STATIC_ASSERT(XXH3_INTERNALBUFFER_SIZE % XXH_STRIPE_LEN == 0); /* clean multiple */ /* * Internal buffer is partially filled (always, except at beginning) * Complete it, then consume it. */ if (state->bufferedSize) { size_t const loadSize = XXH3_INTERNALBUFFER_SIZE - state->bufferedSize; XXH_memcpy(state->buffer + state->bufferedSize, input, loadSize); input += loadSize; XXH3_consumeStripes(acc, &state->nbStripesSoFar, state->nbStripesPerBlock, state->buffer, XXH3_INTERNALBUFFER_STRIPES, secret, state->secretLimit, f_acc, f_scramble); state->bufferedSize = 0; } XXH_ASSERT(input < bEnd); if (bEnd - input > XXH3_INTERNALBUFFER_SIZE) { size_t nbStripes = (size_t)(bEnd - 1 - input) / XXH_STRIPE_LEN; input = XXH3_consumeStripes(acc, &state->nbStripesSoFar, state->nbStripesPerBlock, input, nbStripes, secret, state->secretLimit, f_acc, f_scramble); XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN); } /* Some remaining input (always) : buffer it */ XXH_ASSERT(input < bEnd); XXH_ASSERT(bEnd - input <= XXH3_INTERNALBUFFER_SIZE); XXH_ASSERT(state->bufferedSize == 0); XXH_memcpy(state->buffer, input, (size_t)(bEnd-input)); state->bufferedSize = (XXH32_hash_t)(bEnd-input); #if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1 /* save stack accumulators into state */ XXH_memcpy(state->acc, acc, sizeof(acc)); #endif } return XXH_OK; } /* * Both XXH3_64bits_update and XXH3_128bits_update use this routine. */ XXH_NO_INLINE XXH_errorcode XXH3_update_regular(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len) { return XXH3_update(state, (const xxh_u8*)input, len, XXH3_accumulate, XXH3_scrambleAcc); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len) { return XXH3_update_regular(state, input, len); } XXH_FORCE_INLINE void XXH3_digest_long (XXH64_hash_t* acc, const XXH3_state_t* state, const unsigned char* secret) { xxh_u8 lastStripe[XXH_STRIPE_LEN]; const xxh_u8* lastStripePtr; /* * Digest on a local copy. This way, the state remains unaltered, and it can * continue ingesting more input afterwards. */ XXH_memcpy(acc, state->acc, sizeof(state->acc)); if (state->bufferedSize >= XXH_STRIPE_LEN) { /* Consume remaining stripes then point to remaining data in buffer */ size_t const nbStripes = (state->bufferedSize - 1) / XXH_STRIPE_LEN; size_t nbStripesSoFar = state->nbStripesSoFar; XXH3_consumeStripes(acc, &nbStripesSoFar, state->nbStripesPerBlock, state->buffer, nbStripes, secret, state->secretLimit, XXH3_accumulate, XXH3_scrambleAcc); lastStripePtr = state->buffer + state->bufferedSize - XXH_STRIPE_LEN; } else { /* bufferedSize < XXH_STRIPE_LEN */ /* Copy to temp buffer */ size_t const catchupSize = XXH_STRIPE_LEN - state->bufferedSize; XXH_ASSERT(state->bufferedSize > 0); /* there is always some input buffered */ XXH_memcpy(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize); XXH_memcpy(lastStripe + catchupSize, state->buffer, state->bufferedSize); lastStripePtr = lastStripe; } /* Last stripe */ XXH3_accumulate_512(acc, lastStripePtr, secret + state->secretLimit - XXH_SECRET_LASTACC_START); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (XXH_NOESCAPE const XXH3_state_t* state) { const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret; if (state->totalLen > XXH3_MIDSIZE_MAX) { XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[XXH_ACC_NB]; XXH3_digest_long(acc, state, secret); return XXH3_finalizeLong_64b(acc, secret, (xxh_u64)state->totalLen); } /* totalLen <= XXH3_MIDSIZE_MAX: digesting a short input */ if (state->useSeed) return XXH3_64bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed); return XXH3_64bits_withSecret(state->buffer, (size_t)(state->totalLen), secret, state->secretLimit + XXH_STRIPE_LEN); } #endif /* !XXH_NO_STREAM */ /* ========================================== * XXH3 128 bits (a.k.a XXH128) * ========================================== * XXH3's 128-bit variant has better mixing and strength than the 64-bit variant, * even without counting the significantly larger output size. * * For example, extra steps are taken to avoid the seed-dependent collisions * in 17-240 byte inputs (See XXH3_mix16B and XXH128_mix32B). * * This strength naturally comes at the cost of some speed, especially on short * lengths. Note that longer hashes are about as fast as the 64-bit version * due to it using only a slight modification of the 64-bit loop. * * XXH128 is also more oriented towards 64-bit machines. It is still extremely * fast for a _128-bit_ hash on 32-bit (it usually clears XXH64). */ XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_1to3_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { /* A doubled version of 1to3_64b with different constants. */ XXH_ASSERT(input != NULL); XXH_ASSERT(1 <= len && len <= 3); XXH_ASSERT(secret != NULL); /* * len = 1: combinedl = { input[0], 0x01, input[0], input[0] } * len = 2: combinedl = { input[1], 0x02, input[0], input[1] } * len = 3: combinedl = { input[2], 0x03, input[0], input[1] } */ { xxh_u8 const c1 = input[0]; xxh_u8 const c2 = input[len >> 1]; xxh_u8 const c3 = input[len - 1]; xxh_u32 const combinedl = ((xxh_u32)c1 <<16) | ((xxh_u32)c2 << 24) | ((xxh_u32)c3 << 0) | ((xxh_u32)len << 8); xxh_u32 const combinedh = XXH_rotl32(XXH_swap32(combinedl), 13); xxh_u64 const bitflipl = (XXH_readLE32(secret) ^ XXH_readLE32(secret+4)) + seed; xxh_u64 const bitfliph = (XXH_readLE32(secret+8) ^ XXH_readLE32(secret+12)) - seed; xxh_u64 const keyed_lo = (xxh_u64)combinedl ^ bitflipl; xxh_u64 const keyed_hi = (xxh_u64)combinedh ^ bitfliph; XXH128_hash_t h128; h128.low64 = XXH64_avalanche(keyed_lo); h128.high64 = XXH64_avalanche(keyed_hi); return h128; } } XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_4to8_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); XXH_ASSERT(secret != NULL); XXH_ASSERT(4 <= len && len <= 8); seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32; { xxh_u32 const input_lo = XXH_readLE32(input); xxh_u32 const input_hi = XXH_readLE32(input + len - 4); xxh_u64 const input_64 = input_lo + ((xxh_u64)input_hi << 32); xxh_u64 const bitflip = (XXH_readLE64(secret+16) ^ XXH_readLE64(secret+24)) + seed; xxh_u64 const keyed = input_64 ^ bitflip; /* Shift len to the left to ensure it is even, this avoids even multiplies. */ XXH128_hash_t m128 = XXH_mult64to128(keyed, XXH_PRIME64_1 + (len << 2)); m128.high64 += (m128.low64 << 1); m128.low64 ^= (m128.high64 >> 3); m128.low64 = XXH_xorshift64(m128.low64, 35); m128.low64 *= PRIME_MX2; m128.low64 = XXH_xorshift64(m128.low64, 28); m128.high64 = XXH3_avalanche(m128.high64); return m128; } } XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_9to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(input != NULL); XXH_ASSERT(secret != NULL); XXH_ASSERT(9 <= len && len <= 16); { xxh_u64 const bitflipl = (XXH_readLE64(secret+32) ^ XXH_readLE64(secret+40)) - seed; xxh_u64 const bitfliph = (XXH_readLE64(secret+48) ^ XXH_readLE64(secret+56)) + seed; xxh_u64 const input_lo = XXH_readLE64(input); xxh_u64 input_hi = XXH_readLE64(input + len - 8); XXH128_hash_t m128 = XXH_mult64to128(input_lo ^ input_hi ^ bitflipl, XXH_PRIME64_1); /* * Put len in the middle of m128 to ensure that the length gets mixed to * both the low and high bits in the 128x64 multiply below. */ m128.low64 += (xxh_u64)(len - 1) << 54; input_hi ^= bitfliph; /* * Add the high 32 bits of input_hi to the high 32 bits of m128, then * add the long product of the low 32 bits of input_hi and XXH_PRIME32_2 to * the high 64 bits of m128. * * The best approach to this operation is different on 32-bit and 64-bit. */ if (sizeof(void *) < sizeof(xxh_u64)) { /* 32-bit */ /* * 32-bit optimized version, which is more readable. * * On 32-bit, it removes an ADC and delays a dependency between the two * halves of m128.high64, but it generates an extra mask on 64-bit. */ m128.high64 += (input_hi & 0xFFFFFFFF00000000ULL) + XXH_mult32to64((xxh_u32)input_hi, XXH_PRIME32_2); } else { /* * 64-bit optimized (albeit more confusing) version. * * Uses some properties of addition and multiplication to remove the mask: * * Let: * a = input_hi.lo = (input_hi & 0x00000000FFFFFFFF) * b = input_hi.hi = (input_hi & 0xFFFFFFFF00000000) * c = XXH_PRIME32_2 * * a + (b * c) * Inverse Property: x + y - x == y * a + (b * (1 + c - 1)) * Distributive Property: x * (y + z) == (x * y) + (x * z) * a + (b * 1) + (b * (c - 1)) * Identity Property: x * 1 == x * a + b + (b * (c - 1)) * * Substitute a, b, and c: * input_hi.hi + input_hi.lo + ((xxh_u64)input_hi.lo * (XXH_PRIME32_2 - 1)) * * Since input_hi.hi + input_hi.lo == input_hi, we get this: * input_hi + ((xxh_u64)input_hi.lo * (XXH_PRIME32_2 - 1)) */ m128.high64 += input_hi + XXH_mult32to64((xxh_u32)input_hi, XXH_PRIME32_2 - 1); } /* m128 ^= XXH_swap64(m128 >> 64); */ m128.low64 ^= XXH_swap64(m128.high64); { /* 128x64 multiply: h128 = m128 * XXH_PRIME64_2; */ XXH128_hash_t h128 = XXH_mult64to128(m128.low64, XXH_PRIME64_2); h128.high64 += m128.high64 * XXH_PRIME64_2; h128.low64 = XXH3_avalanche(h128.low64); h128.high64 = XXH3_avalanche(h128.high64); return h128; } } } /* * Assumption: `secret` size is >= XXH3_SECRET_SIZE_MIN */ XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_0to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed) { XXH_ASSERT(len <= 16); { if (len > 8) return XXH3_len_9to16_128b(input, len, secret, seed); if (len >= 4) return XXH3_len_4to8_128b(input, len, secret, seed); if (len) return XXH3_len_1to3_128b(input, len, secret, seed); { XXH128_hash_t h128; xxh_u64 const bitflipl = XXH_readLE64(secret+64) ^ XXH_readLE64(secret+72); xxh_u64 const bitfliph = XXH_readLE64(secret+80) ^ XXH_readLE64(secret+88); h128.low64 = XXH64_avalanche(seed ^ bitflipl); h128.high64 = XXH64_avalanche( seed ^ bitfliph); return h128; } } } /* * A bit slower than XXH3_mix16B, but handles multiply by zero better. */ XXH_FORCE_INLINE XXH128_hash_t XXH128_mix32B(XXH128_hash_t acc, const xxh_u8* input_1, const xxh_u8* input_2, const xxh_u8* secret, XXH64_hash_t seed) { acc.low64 += XXH3_mix16B (input_1, secret+0, seed); acc.low64 ^= XXH_readLE64(input_2) + XXH_readLE64(input_2 + 8); acc.high64 += XXH3_mix16B (input_2, secret+16, seed); acc.high64 ^= XXH_readLE64(input_1) + XXH_readLE64(input_1 + 8); return acc; } XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH64_hash_t seed) { XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize; XXH_ASSERT(16 < len && len <= 128); { XXH128_hash_t acc; acc.low64 = len * XXH_PRIME64_1; acc.high64 = 0; #if XXH_SIZE_OPT >= 1 { /* Smaller, but slightly slower. */ unsigned int i = (unsigned int)(len - 1) / 32; do { acc = XXH128_mix32B(acc, input+16*i, input+len-16*(i+1), secret+32*i, seed); } while (i-- != 0); } #else if (len > 32) { if (len > 64) { if (len > 96) { acc = XXH128_mix32B(acc, input+48, input+len-64, secret+96, seed); } acc = XXH128_mix32B(acc, input+32, input+len-48, secret+64, seed); } acc = XXH128_mix32B(acc, input+16, input+len-32, secret+32, seed); } acc = XXH128_mix32B(acc, input, input+len-16, secret, seed); #endif { XXH128_hash_t h128; h128.low64 = acc.low64 + acc.high64; h128.high64 = (acc.low64 * XXH_PRIME64_1) + (acc.high64 * XXH_PRIME64_4) + ((len - seed) * XXH_PRIME64_2); h128.low64 = XXH3_avalanche(h128.low64); h128.high64 = (XXH64_hash_t)0 - XXH3_avalanche(h128.high64); return h128; } } } XXH_NO_INLINE XXH_PUREF XXH128_hash_t XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH64_hash_t seed) { XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize; XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX); { XXH128_hash_t acc; unsigned i; acc.low64 = len * XXH_PRIME64_1; acc.high64 = 0; /* * We set as `i` as offset + 32. We do this so that unchanged * `len` can be used as upper bound. This reaches a sweet spot * where both x86 and aarch64 get simple agen and good codegen * for the loop. */ for (i = 32; i < 160; i += 32) { acc = XXH128_mix32B(acc, input + i - 32, input + i - 16, secret + i - 32, seed); } acc.low64 = XXH3_avalanche(acc.low64); acc.high64 = XXH3_avalanche(acc.high64); /* * NB: `i <= len` will duplicate the last 32-bytes if * len % 32 was zero. This is an unfortunate necessity to keep * the hash result stable. */ for (i=160; i <= len; i += 32) { acc = XXH128_mix32B(acc, input + i - 32, input + i - 16, secret + XXH3_MIDSIZE_STARTOFFSET + i - 160, seed); } /* last bytes */ acc = XXH128_mix32B(acc, input + len - 16, input + len - 32, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET - 16, (XXH64_hash_t)0 - seed); { XXH128_hash_t h128; h128.low64 = acc.low64 + acc.high64; h128.high64 = (acc.low64 * XXH_PRIME64_1) + (acc.high64 * XXH_PRIME64_4) + ((len - seed) * XXH_PRIME64_2); h128.low64 = XXH3_avalanche(h128.low64); h128.high64 = (XXH64_hash_t)0 - XXH3_avalanche(h128.high64); return h128; } } } static XXH_PUREF XXH128_hash_t XXH3_finalizeLong_128b(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, xxh_u64 len) { XXH128_hash_t h128; h128.low64 = XXH3_finalizeLong_64b(acc, secret, len); h128.high64 = XXH3_mergeAccs(acc, secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_MERGEACCS_START, ~(len * XXH_PRIME64_2)); return h128; } XXH_FORCE_INLINE XXH128_hash_t XXH3_hashLong_128b_internal(const void* XXH_RESTRICT input, size_t len, const xxh_u8* XXH_RESTRICT secret, size_t secretSize, XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble) { XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC; XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, secret, secretSize, f_acc, f_scramble); /* converge into final hash */ XXH_STATIC_ASSERT(sizeof(acc) == 64); XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START); return XXH3_finalizeLong_128b(acc, secret, secretSize, (xxh_u64)len); } /* * It's important for performance that XXH3_hashLong() is not inlined. */ XXH_NO_INLINE XXH_PUREF XXH128_hash_t XXH3_hashLong_128b_default(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen) { (void)seed64; (void)secret; (void)secretLen; return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate, XXH3_scrambleAcc); } /* * It's important for performance to pass @p secretLen (when it's static) * to the compiler, so that it can properly optimize the vectorized loop. * * When the secret size is unknown, or on GCC 12 where the mix of NO_INLINE and FORCE_INLINE * breaks -Og, this is XXH_NO_INLINE. */ XXH3_WITH_SECRET_INLINE XXH128_hash_t XXH3_hashLong_128b_withSecret(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen) { (void)seed64; return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen, XXH3_accumulate, XXH3_scrambleAcc); } XXH_FORCE_INLINE XXH128_hash_t XXH3_hashLong_128b_withSeed_internal(const void* XXH_RESTRICT input, size_t len, XXH64_hash_t seed64, XXH3_f_accumulate f_acc, XXH3_f_scrambleAcc f_scramble, XXH3_f_initCustomSecret f_initSec) { if (seed64 == 0) return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), f_acc, f_scramble); { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE]; f_initSec(secret, seed64); return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, sizeof(secret), f_acc, f_scramble); } } /* * It's important for performance that XXH3_hashLong is not inlined. */ XXH_NO_INLINE XXH128_hash_t XXH3_hashLong_128b_withSeed(const void* input, size_t len, XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen) { (void)secret; (void)secretLen; return XXH3_hashLong_128b_withSeed_internal(input, len, seed64, XXH3_accumulate, XXH3_scrambleAcc, XXH3_initCustomSecret); } typedef XXH128_hash_t (*XXH3_hashLong128_f)(const void* XXH_RESTRICT, size_t, XXH64_hash_t, const void* XXH_RESTRICT, size_t); XXH_FORCE_INLINE XXH128_hash_t XXH3_128bits_internal(const void* input, size_t len, XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen, XXH3_hashLong128_f f_hl128) { XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN); /* * If an action is to be taken if `secret` conditions are not respected, * it should be done here. * For now, it's a contract pre-condition. * Adding a check and a branch here would cost performance at every hash. */ if (len <= 16) return XXH3_len_0to16_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, seed64); if (len <= 128) return XXH3_len_17to128_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64); if (len <= XXH3_MIDSIZE_MAX) return XXH3_len_129to240_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64); return f_hl128(input, len, seed64, secret, secretLen); } /* === Public XXH128 API === */ /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(XXH_NOESCAPE const void* input, size_t len) { return XXH3_128bits_internal(input, len, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_default); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecret(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize) { return XXH3_128bits_internal(input, len, 0, (const xxh_u8*)secret, secretSize, XXH3_hashLong_128b_withSecret); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSeed(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed) { return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_128b_withSeed); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed) { if (len <= XXH3_MIDSIZE_MAX) return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL); return XXH3_hashLong_128b_withSecret(input, len, seed, secret, secretSize); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t XXH128(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed) { return XXH3_128bits_withSeed(input, len, seed); } /* === XXH3 128-bit streaming === */ #ifndef XXH_NO_STREAM /* * All initialization and update functions are identical to 64-bit streaming variant. * The only difference is the finalization routine. */ /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr) { return XXH3_64bits_reset(statePtr); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize) { return XXH3_64bits_reset_withSecret(statePtr, secret, secretSize); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed) { return XXH3_64bits_reset_withSeed(statePtr, seed); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed) { return XXH3_64bits_reset_withSecretandSeed(statePtr, secret, secretSize, seed); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len) { return XXH3_update_regular(state, input, len); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (XXH_NOESCAPE const XXH3_state_t* state) { const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret; if (state->totalLen > XXH3_MIDSIZE_MAX) { XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[XXH_ACC_NB]; XXH3_digest_long(acc, state, secret); XXH_ASSERT(state->secretLimit + XXH_STRIPE_LEN >= sizeof(acc) + XXH_SECRET_MERGEACCS_START); return XXH3_finalizeLong_128b(acc, secret, state->secretLimit + XXH_STRIPE_LEN, (xxh_u64)state->totalLen); } /* len <= XXH3_MIDSIZE_MAX : short code */ if (state->useSeed) return XXH3_128bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed); return XXH3_128bits_withSecret(state->buffer, (size_t)(state->totalLen), secret, state->secretLimit + XXH_STRIPE_LEN); } #endif /* !XXH_NO_STREAM */ /* 128-bit utility functions */ /* return : 1 is equal, 0 if different */ /*! @ingroup XXH3_family */ XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2) { /* note : XXH128_hash_t is compact, it has no padding byte */ return !(XXH_memcmp(&h1, &h2, sizeof(h1))); } /* This prototype is compatible with stdlib's qsort(). * @return : >0 if *h128_1 > *h128_2 * <0 if *h128_1 < *h128_2 * =0 if *h128_1 == *h128_2 */ /*! @ingroup XXH3_family */ XXH_PUBLIC_API int XXH128_cmp(XXH_NOESCAPE const void* h128_1, XXH_NOESCAPE const void* h128_2) { XXH128_hash_t const h1 = *(const XXH128_hash_t*)h128_1; XXH128_hash_t const h2 = *(const XXH128_hash_t*)h128_2; int const hcmp = (h1.high64 > h2.high64) - (h2.high64 > h1.high64); /* note : bets that, in most cases, hash values are different */ if (hcmp) return hcmp; return (h1.low64 > h2.low64) - (h2.low64 > h1.low64); } /*====== Canonical representation ======*/ /*! @ingroup XXH3_family */ XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH_NOESCAPE XXH128_canonical_t* dst, XXH128_hash_t hash) { XXH_STATIC_ASSERT(sizeof(XXH128_canonical_t) == sizeof(XXH128_hash_t)); if (XXH_CPU_LITTLE_ENDIAN) { hash.high64 = XXH_swap64(hash.high64); hash.low64 = XXH_swap64(hash.low64); } XXH_memcpy(dst, &hash.high64, sizeof(hash.high64)); XXH_memcpy((char*)dst + sizeof(hash.high64), &hash.low64, sizeof(hash.low64)); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH128_hash_t XXH128_hashFromCanonical(XXH_NOESCAPE const XXH128_canonical_t* src) { XXH128_hash_t h; h.high64 = XXH_readBE64(src); h.low64 = XXH_readBE64(src->digest + 8); return h; } /* ========================================== * Secret generators * ========================================== */ #define XXH_MIN(x, y) (((x) > (y)) ? (y) : (x)) XXH_FORCE_INLINE void XXH3_combine16(void* dst, XXH128_hash_t h128) { XXH_writeLE64( dst, XXH_readLE64(dst) ^ h128.low64 ); XXH_writeLE64( (char*)dst+8, XXH_readLE64((char*)dst+8) ^ h128.high64 ); } /*! @ingroup XXH3_family */ XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(XXH_NOESCAPE void* secretBuffer, size_t secretSize, XXH_NOESCAPE const void* customSeed, size_t customSeedSize) { #if (XXH_DEBUGLEVEL >= 1) XXH_ASSERT(secretBuffer != NULL); XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); #else /* production mode, assert() are disabled */ if (secretBuffer == NULL) return XXH_ERROR; if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR; #endif if (customSeedSize == 0) { customSeed = XXH3_kSecret; customSeedSize = XXH_SECRET_DEFAULT_SIZE; } #if (XXH_DEBUGLEVEL >= 1) XXH_ASSERT(customSeed != NULL); #else if (customSeed == NULL) return XXH_ERROR; #endif /* Fill secretBuffer with a copy of customSeed - repeat as needed */ { size_t pos = 0; while (pos < secretSize) { size_t const toCopy = XXH_MIN((secretSize - pos), customSeedSize); XXH_memcpy((char*)secretBuffer + pos, customSeed, toCopy); pos += toCopy; } } { size_t const nbSeg16 = secretSize / 16; size_t n; XXH128_canonical_t scrambler; XXH128_canonicalFromHash(&scrambler, XXH128(customSeed, customSeedSize, 0)); for (n=0; n 0 || next < srcLen) { if (bitsLeft < 5) { if (next < srcLen) { buffer <<= 8; buffer |= src[next++] & 0xFF; bitsLeft += 8; } else { int pad = 5 - bitsLeft; buffer <<= pad; bitsLeft += pad; } } int index = 0x1F & (buffer >> (bitsLeft - 5)); bitsLeft -= 5; dst[count++] = base32_table[index]; } } ================================================ FILE: src/aiotieba/helper/crypto/src/crc/crc32.c ================================================ /* ** The crc32 is licensed under the Apache License, Version 2.0, and a copy of the license is included in this file. ** ** Author: Wang Yaofu voipman@qq.com ** Description: The source file of class crc32. ** CRC32 implementation according to IEEE standards. ** Polynomials are represented in LSB-first form ** following parameters: ** Width : 32 bit ** Poly : 0xEDB88320 ** Output for "123456789" : 0xCBF43926 */ #include "crc/crc32.h" static uint32_t tbc_crc32Table[] = { 0x00000000L, 0x77073096L, 0xEE0E612CL, 0x990951BAL, 0x076DC419L, 0x706AF48FL, 0xE963A535L, 0x9E6495A3L, 0x0EDB8832L, 0x79DCB8A4L, 0xE0D5E91EL, 0x97D2D988L, 0x09B64C2BL, 0x7EB17CBDL, 0xE7B82D07L, 0x90BF1D91L, 0x1DB71064L, 0x6AB020F2L, 0xF3B97148L, 0x84BE41DEL, 0x1ADAD47DL, 0x6DDDE4EBL, 0xF4D4B551L, 0x83D385C7L, 0x136C9856L, 0x646BA8C0L, 0xFD62F97AL, 0x8A65C9ECL, 0x14015C4FL, 0x63066CD9L, 0xFA0F3D63L, 0x8D080DF5L, 0x3B6E20C8L, 0x4C69105EL, 0xD56041E4L, 0xA2677172L, 0x3C03E4D1L, 0x4B04D447L, 0xD20D85FDL, 0xA50AB56BL, 0x35B5A8FAL, 0x42B2986CL, 0xDBBBC9D6L, 0xACBCF940L, 0x32D86CE3L, 0x45DF5C75L, 0xDCD60DCFL, 0xABD13D59L, 0x26D930ACL, 0x51DE003AL, 0xC8D75180L, 0xBFD06116L, 0x21B4F4B5L, 0x56B3C423L, 0xCFBA9599L, 0xB8BDA50FL, 0x2802B89EL, 0x5F058808L, 0xC60CD9B2L, 0xB10BE924L, 0x2F6F7C87L, 0x58684C11L, 0xC1611DABL, 0xB6662D3DL, 0x76DC4190L, 0x01DB7106L, 0x98D220BCL, 0xEFD5102AL, 0x71B18589L, 0x06B6B51FL, 0x9FBFE4A5L, 0xE8B8D433L, 0x7807C9A2L, 0x0F00F934L, 0x9609A88EL, 0xE10E9818L, 0x7F6A0DBBL, 0x086D3D2DL, 0x91646C97L, 0xE6635C01L, 0x6B6B51F4L, 0x1C6C6162L, 0x856530D8L, 0xF262004EL, 0x6C0695EDL, 0x1B01A57BL, 0x8208F4C1L, 0xF50FC457L, 0x65B0D9C6L, 0x12B7E950L, 0x8BBEB8EAL, 0xFCB9887CL, 0x62DD1DDFL, 0x15DA2D49L, 0x8CD37CF3L, 0xFBD44C65L, 0x4DB26158L, 0x3AB551CEL, 0xA3BC0074L, 0xD4BB30E2L, 0x4ADFA541L, 0x3DD895D7L, 0xA4D1C46DL, 0xD3D6F4FBL, 0x4369E96AL, 0x346ED9FCL, 0xAD678846L, 0xDA60B8D0L, 0x44042D73L, 0x33031DE5L, 0xAA0A4C5FL, 0xDD0D7CC9L, 0x5005713CL, 0x270241AAL, 0xBE0B1010L, 0xC90C2086L, 0x5768B525L, 0x206F85B3L, 0xB966D409L, 0xCE61E49FL, 0x5EDEF90EL, 0x29D9C998L, 0xB0D09822L, 0xC7D7A8B4L, 0x59B33D17L, 0x2EB40D81L, 0xB7BD5C3BL, 0xC0BA6CADL, 0xEDB88320L, 0x9ABFB3B6L, 0x03B6E20CL, 0x74B1D29AL, 0xEAD54739L, 0x9DD277AFL, 0x04DB2615L, 0x73DC1683L, 0xE3630B12L, 0x94643B84L, 0x0D6D6A3EL, 0x7A6A5AA8L, 0xE40ECF0BL, 0x9309FF9DL, 0x0A00AE27L, 0x7D079EB1L, 0xF00F9344L, 0x8708A3D2L, 0x1E01F268L, 0x6906C2FEL, 0xF762575DL, 0x806567CBL, 0x196C3671L, 0x6E6B06E7L, 0xFED41B76L, 0x89D32BE0L, 0x10DA7A5AL, 0x67DD4ACCL, 0xF9B9DF6FL, 0x8EBEEFF9L, 0x17B7BE43L, 0x60B08ED5L, 0xD6D6A3E8L, 0xA1D1937EL, 0x38D8C2C4L, 0x4FDFF252L, 0xD1BB67F1L, 0xA6BC5767L, 0x3FB506DDL, 0x48B2364BL, 0xD80D2BDAL, 0xAF0A1B4CL, 0x36034AF6L, 0x41047A60L, 0xDF60EFC3L, 0xA867DF55L, 0x316E8EEFL, 0x4669BE79L, 0xCB61B38CL, 0xBC66831AL, 0x256FD2A0L, 0x5268E236L, 0xCC0C7795L, 0xBB0B4703L, 0x220216B9L, 0x5505262FL, 0xC5BA3BBEL, 0xB2BD0B28L, 0x2BB45A92L, 0x5CB36A04L, 0xC2D7FFA7L, 0xB5D0CF31L, 0x2CD99E8BL, 0x5BDEAE1DL, 0x9B64C2B0L, 0xEC63F226L, 0x756AA39CL, 0x026D930AL, 0x9C0906A9L, 0xEB0E363FL, 0x72076785L, 0x05005713L, 0x95BF4A82L, 0xE2B87A14L, 0x7BB12BAEL, 0x0CB61B38L, 0x92D28E9BL, 0xE5D5BE0DL, 0x7CDCEFB7L, 0x0BDBDF21L, 0x86D3D2D4L, 0xF1D4E242L, 0x68DDB3F8L, 0x1FDA836EL, 0x81BE16CDL, 0xF6B9265BL, 0x6FB077E1L, 0x18B74777L, 0x88085AE6L, 0xFF0F6A70L, 0x66063BCAL, 0x11010B5CL, 0x8F659EFFL, 0xF862AE69L, 0x616BFFD3L, 0x166CCF45L, 0xA00AE278L, 0xD70DD2EEL, 0x4E048354L, 0x3903B3C2L, 0xA7672661L, 0xD06016F7L, 0x4969474DL, 0x3E6E77DBL, 0xAED16A4AL, 0xD9D65ADCL, 0x40DF0B66L, 0x37D83BF0L, 0xA9BCAE53L, 0xDEBB9EC5L, 0x47B2CF7FL, 0x30B5FFE9L, 0xBDBDF21CL, 0xCABAC28AL, 0x53B39330L, 0x24B4A3A6L, 0xBAD03605L, 0xCDD70693L, 0x54DE5729L, 0x23D967BFL, 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L, 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL}; uint32_t tbc_crc32(const unsigned char* src, size_t srcLen, uint32_t prev_val) { uint32_t crc32_val = ~prev_val; unsigned char* cursor = (unsigned char*)src; while (srcLen--) { crc32_val = (crc32_val >> 8) ^ tbc_crc32Table[(crc32_val & 0xFF) ^ *cursor++]; } return ~crc32_val; } ================================================ FILE: src/aiotieba/helper/crypto/src/mbedtls/md5.c ================================================ /* * RFC 1321 compliant MD5 implementation * * Copyright The Mbed TLS Contributors * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * The MD5 algorithm was designed by Ron Rivest in 1991. * * http://www.ietf.org/rfc/rfc1321.txt */ #include "mbedtls/common.h" #include "mbedtls/md5.h" void mbedtls_md5_init(mbedtls_md5_context *ctx) { memset(ctx, 0, sizeof(mbedtls_md5_context)); } /* * MD5 context setup */ void mbedtls_md5_starts(mbedtls_md5_context *ctx) { ctx->total[0] = 0; ctx->total[1] = 0; ctx->state[0] = 0x67452301; ctx->state[1] = 0xEFCDAB89; ctx->state[2] = 0x98BADCFE; ctx->state[3] = 0x10325476; } void mbedtls_internal_md5_process(mbedtls_md5_context *ctx, const unsigned char data[64]) { struct { uint32_t X[16], A, B, C, D; } local; local.X[0] = MBEDTLS_GET_UINT32_LE(data, 0); local.X[1] = MBEDTLS_GET_UINT32_LE(data, 4); local.X[2] = MBEDTLS_GET_UINT32_LE(data, 8); local.X[3] = MBEDTLS_GET_UINT32_LE(data, 12); local.X[4] = MBEDTLS_GET_UINT32_LE(data, 16); local.X[5] = MBEDTLS_GET_UINT32_LE(data, 20); local.X[6] = MBEDTLS_GET_UINT32_LE(data, 24); local.X[7] = MBEDTLS_GET_UINT32_LE(data, 28); local.X[8] = MBEDTLS_GET_UINT32_LE(data, 32); local.X[9] = MBEDTLS_GET_UINT32_LE(data, 36); local.X[10] = MBEDTLS_GET_UINT32_LE(data, 40); local.X[11] = MBEDTLS_GET_UINT32_LE(data, 44); local.X[12] = MBEDTLS_GET_UINT32_LE(data, 48); local.X[13] = MBEDTLS_GET_UINT32_LE(data, 52); local.X[14] = MBEDTLS_GET_UINT32_LE(data, 56); local.X[15] = MBEDTLS_GET_UINT32_LE(data, 60); #define S(x, n) \ (((x) << (n)) | (((x) & 0xFFFFFFFF) >> (32 - (n)))) #define P(a, b, c, d, k, s, t) \ do \ { \ (a) += F((b), (c), (d)) + local.X[(k)] + (t); \ (a) = S((a), (s)) + (b); \ } while (0) local.A = ctx->state[0]; local.B = ctx->state[1]; local.C = ctx->state[2]; local.D = ctx->state[3]; #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) P(local.A, local.B, local.C, local.D, 0, 7, 0xD76AA478); P(local.D, local.A, local.B, local.C, 1, 12, 0xE8C7B756); P(local.C, local.D, local.A, local.B, 2, 17, 0x242070DB); P(local.B, local.C, local.D, local.A, 3, 22, 0xC1BDCEEE); P(local.A, local.B, local.C, local.D, 4, 7, 0xF57C0FAF); P(local.D, local.A, local.B, local.C, 5, 12, 0x4787C62A); P(local.C, local.D, local.A, local.B, 6, 17, 0xA8304613); P(local.B, local.C, local.D, local.A, 7, 22, 0xFD469501); P(local.A, local.B, local.C, local.D, 8, 7, 0x698098D8); P(local.D, local.A, local.B, local.C, 9, 12, 0x8B44F7AF); P(local.C, local.D, local.A, local.B, 10, 17, 0xFFFF5BB1); P(local.B, local.C, local.D, local.A, 11, 22, 0x895CD7BE); P(local.A, local.B, local.C, local.D, 12, 7, 0x6B901122); P(local.D, local.A, local.B, local.C, 13, 12, 0xFD987193); P(local.C, local.D, local.A, local.B, 14, 17, 0xA679438E); P(local.B, local.C, local.D, local.A, 15, 22, 0x49B40821); #undef F #define F(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) P(local.A, local.B, local.C, local.D, 1, 5, 0xF61E2562); P(local.D, local.A, local.B, local.C, 6, 9, 0xC040B340); P(local.C, local.D, local.A, local.B, 11, 14, 0x265E5A51); P(local.B, local.C, local.D, local.A, 0, 20, 0xE9B6C7AA); P(local.A, local.B, local.C, local.D, 5, 5, 0xD62F105D); P(local.D, local.A, local.B, local.C, 10, 9, 0x02441453); P(local.C, local.D, local.A, local.B, 15, 14, 0xD8A1E681); P(local.B, local.C, local.D, local.A, 4, 20, 0xE7D3FBC8); P(local.A, local.B, local.C, local.D, 9, 5, 0x21E1CDE6); P(local.D, local.A, local.B, local.C, 14, 9, 0xC33707D6); P(local.C, local.D, local.A, local.B, 3, 14, 0xF4D50D87); P(local.B, local.C, local.D, local.A, 8, 20, 0x455A14ED); P(local.A, local.B, local.C, local.D, 13, 5, 0xA9E3E905); P(local.D, local.A, local.B, local.C, 2, 9, 0xFCEFA3F8); P(local.C, local.D, local.A, local.B, 7, 14, 0x676F02D9); P(local.B, local.C, local.D, local.A, 12, 20, 0x8D2A4C8A); #undef F #define F(x, y, z) ((x) ^ (y) ^ (z)) P(local.A, local.B, local.C, local.D, 5, 4, 0xFFFA3942); P(local.D, local.A, local.B, local.C, 8, 11, 0x8771F681); P(local.C, local.D, local.A, local.B, 11, 16, 0x6D9D6122); P(local.B, local.C, local.D, local.A, 14, 23, 0xFDE5380C); P(local.A, local.B, local.C, local.D, 1, 4, 0xA4BEEA44); P(local.D, local.A, local.B, local.C, 4, 11, 0x4BDECFA9); P(local.C, local.D, local.A, local.B, 7, 16, 0xF6BB4B60); P(local.B, local.C, local.D, local.A, 10, 23, 0xBEBFBC70); P(local.A, local.B, local.C, local.D, 13, 4, 0x289B7EC6); P(local.D, local.A, local.B, local.C, 0, 11, 0xEAA127FA); P(local.C, local.D, local.A, local.B, 3, 16, 0xD4EF3085); P(local.B, local.C, local.D, local.A, 6, 23, 0x04881D05); P(local.A, local.B, local.C, local.D, 9, 4, 0xD9D4D039); P(local.D, local.A, local.B, local.C, 12, 11, 0xE6DB99E5); P(local.C, local.D, local.A, local.B, 15, 16, 0x1FA27CF8); P(local.B, local.C, local.D, local.A, 2, 23, 0xC4AC5665); #undef F #define F(x, y, z) ((y) ^ ((x) | ~(z))) P(local.A, local.B, local.C, local.D, 0, 6, 0xF4292244); P(local.D, local.A, local.B, local.C, 7, 10, 0x432AFF97); P(local.C, local.D, local.A, local.B, 14, 15, 0xAB9423A7); P(local.B, local.C, local.D, local.A, 5, 21, 0xFC93A039); P(local.A, local.B, local.C, local.D, 12, 6, 0x655B59C3); P(local.D, local.A, local.B, local.C, 3, 10, 0x8F0CCC92); P(local.C, local.D, local.A, local.B, 10, 15, 0xFFEFF47D); P(local.B, local.C, local.D, local.A, 1, 21, 0x85845DD1); P(local.A, local.B, local.C, local.D, 8, 6, 0x6FA87E4F); P(local.D, local.A, local.B, local.C, 15, 10, 0xFE2CE6E0); P(local.C, local.D, local.A, local.B, 6, 15, 0xA3014314); P(local.B, local.C, local.D, local.A, 13, 21, 0x4E0811A1); P(local.A, local.B, local.C, local.D, 4, 6, 0xF7537E82); P(local.D, local.A, local.B, local.C, 11, 10, 0xBD3AF235); P(local.C, local.D, local.A, local.B, 2, 15, 0x2AD7D2BB); P(local.B, local.C, local.D, local.A, 9, 21, 0xEB86D391); #undef F ctx->state[0] += local.A; ctx->state[1] += local.B; ctx->state[2] += local.C; ctx->state[3] += local.D; } /* * MD5 process buffer */ void mbedtls_md5_update(mbedtls_md5_context *ctx, const unsigned char *input, size_t ilen) { size_t fill; uint32_t left; if (ilen == 0) { return; } left = ctx->total[0] & 0x3F; fill = 64 - left; ctx->total[0] += (uint32_t) ilen; ctx->total[0] &= 0xFFFFFFFF; if (ctx->total[0] < (uint32_t) ilen) { ctx->total[1]++; } if (left && ilen >= fill) { memcpy((void *) (ctx->buffer + left), input, fill); mbedtls_internal_md5_process(ctx, ctx->buffer); input += fill; ilen -= fill; left = 0; } while (ilen >= 64) { mbedtls_internal_md5_process(ctx, input); input += 64; ilen -= 64; } if (ilen > 0) { memcpy((void *) (ctx->buffer + left), input, ilen); } } /* * MD5 final digest */ void mbedtls_md5_finish(mbedtls_md5_context *ctx, unsigned char output[16]) { uint32_t used; uint32_t high, low; /* * Add padding: 0x80 then 0x00 until 8 bytes remain for the length */ used = ctx->total[0] & 0x3F; ctx->buffer[used++] = 0x80; if (used <= 56) { /* Enough room for padding + length in current block */ memset(ctx->buffer + used, 0, 56 - used); } else { /* We'll need an extra block */ memset(ctx->buffer + used, 0, 64 - used); mbedtls_internal_md5_process(ctx, ctx->buffer); memset(ctx->buffer, 0, 56); } /* * Add message length */ high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); low = (ctx->total[0] << 3); MBEDTLS_PUT_UINT32_LE(low, ctx->buffer, 56); MBEDTLS_PUT_UINT32_LE(high, ctx->buffer, 60); mbedtls_internal_md5_process(ctx, ctx->buffer); /* * Output final state */ MBEDTLS_PUT_UINT32_LE(ctx->state[0], output, 0); MBEDTLS_PUT_UINT32_LE(ctx->state[1], output, 4); MBEDTLS_PUT_UINT32_LE(ctx->state[2], output, 8); MBEDTLS_PUT_UINT32_LE(ctx->state[3], output, 12); } /* * output = MD5( input buffer ) */ void mbedtls_md5(const unsigned char *input, size_t ilen, unsigned char output[16]) { mbedtls_md5_context ctx; mbedtls_md5_init(&ctx); mbedtls_md5_starts(&ctx); mbedtls_md5_update(&ctx, input, ilen); mbedtls_md5_finish(&ctx, output); } ================================================ FILE: src/aiotieba/helper/crypto/src/mbedtls/sha1.c ================================================ /* * FIPS-180-1 compliant SHA-1 implementation * * Copyright The Mbed TLS Contributors * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * The SHA-1 standard was published by NIST in 1993. * * http://www.itl.nist.gov/fipspubs/fip180-1.htm */ #include "mbedtls/common.h" #include "mbedtls/sha1.h" void mbedtls_sha1_init(mbedtls_sha1_context *ctx) { memset(ctx, 0, sizeof(mbedtls_sha1_context)); } /* * SHA-1 context setup */ void mbedtls_sha1_starts(mbedtls_sha1_context *ctx) { ctx->total[0] = 0; ctx->total[1] = 0; ctx->state[0] = 0x67452301; ctx->state[1] = 0xEFCDAB89; ctx->state[2] = 0x98BADCFE; ctx->state[3] = 0x10325476; ctx->state[4] = 0xC3D2E1F0; } void mbedtls_internal_sha1_process(mbedtls_sha1_context *ctx, const unsigned char data[64]) { struct { uint32_t temp, W[16], A, B, C, D, E; } local; local.W[0] = MBEDTLS_GET_UINT32_BE(data, 0); local.W[1] = MBEDTLS_GET_UINT32_BE(data, 4); local.W[2] = MBEDTLS_GET_UINT32_BE(data, 8); local.W[3] = MBEDTLS_GET_UINT32_BE(data, 12); local.W[4] = MBEDTLS_GET_UINT32_BE(data, 16); local.W[5] = MBEDTLS_GET_UINT32_BE(data, 20); local.W[6] = MBEDTLS_GET_UINT32_BE(data, 24); local.W[7] = MBEDTLS_GET_UINT32_BE(data, 28); local.W[8] = MBEDTLS_GET_UINT32_BE(data, 32); local.W[9] = MBEDTLS_GET_UINT32_BE(data, 36); local.W[10] = MBEDTLS_GET_UINT32_BE(data, 40); local.W[11] = MBEDTLS_GET_UINT32_BE(data, 44); local.W[12] = MBEDTLS_GET_UINT32_BE(data, 48); local.W[13] = MBEDTLS_GET_UINT32_BE(data, 52); local.W[14] = MBEDTLS_GET_UINT32_BE(data, 56); local.W[15] = MBEDTLS_GET_UINT32_BE(data, 60); #define S(x, n) (((x) << (n)) | (((x) & 0xFFFFFFFF) >> (32 - (n)))) #define R(t) \ ( \ local.temp = local.W[((t) - 3) & 0x0F] ^ \ local.W[((t) - 8) & 0x0F] ^ \ local.W[((t) - 14) & 0x0F] ^ \ local.W[(t) & 0x0F], \ (local.W[(t) & 0x0F] = S(local.temp, 1)) \ ) #define P(a, b, c, d, e, x) \ do \ { \ (e) += S((a), 5) + F((b), (c), (d)) + K + (x); \ (b) = S((b), 30); \ } while (0) local.A = ctx->state[0]; local.B = ctx->state[1]; local.C = ctx->state[2]; local.D = ctx->state[3]; local.E = ctx->state[4]; #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define K 0x5A827999 P(local.A, local.B, local.C, local.D, local.E, local.W[0]); P(local.E, local.A, local.B, local.C, local.D, local.W[1]); P(local.D, local.E, local.A, local.B, local.C, local.W[2]); P(local.C, local.D, local.E, local.A, local.B, local.W[3]); P(local.B, local.C, local.D, local.E, local.A, local.W[4]); P(local.A, local.B, local.C, local.D, local.E, local.W[5]); P(local.E, local.A, local.B, local.C, local.D, local.W[6]); P(local.D, local.E, local.A, local.B, local.C, local.W[7]); P(local.C, local.D, local.E, local.A, local.B, local.W[8]); P(local.B, local.C, local.D, local.E, local.A, local.W[9]); P(local.A, local.B, local.C, local.D, local.E, local.W[10]); P(local.E, local.A, local.B, local.C, local.D, local.W[11]); P(local.D, local.E, local.A, local.B, local.C, local.W[12]); P(local.C, local.D, local.E, local.A, local.B, local.W[13]); P(local.B, local.C, local.D, local.E, local.A, local.W[14]); P(local.A, local.B, local.C, local.D, local.E, local.W[15]); P(local.E, local.A, local.B, local.C, local.D, R(16)); P(local.D, local.E, local.A, local.B, local.C, R(17)); P(local.C, local.D, local.E, local.A, local.B, R(18)); P(local.B, local.C, local.D, local.E, local.A, R(19)); #undef K #undef F #define F(x, y, z) ((x) ^ (y) ^ (z)) #define K 0x6ED9EBA1 P(local.A, local.B, local.C, local.D, local.E, R(20)); P(local.E, local.A, local.B, local.C, local.D, R(21)); P(local.D, local.E, local.A, local.B, local.C, R(22)); P(local.C, local.D, local.E, local.A, local.B, R(23)); P(local.B, local.C, local.D, local.E, local.A, R(24)); P(local.A, local.B, local.C, local.D, local.E, R(25)); P(local.E, local.A, local.B, local.C, local.D, R(26)); P(local.D, local.E, local.A, local.B, local.C, R(27)); P(local.C, local.D, local.E, local.A, local.B, R(28)); P(local.B, local.C, local.D, local.E, local.A, R(29)); P(local.A, local.B, local.C, local.D, local.E, R(30)); P(local.E, local.A, local.B, local.C, local.D, R(31)); P(local.D, local.E, local.A, local.B, local.C, R(32)); P(local.C, local.D, local.E, local.A, local.B, R(33)); P(local.B, local.C, local.D, local.E, local.A, R(34)); P(local.A, local.B, local.C, local.D, local.E, R(35)); P(local.E, local.A, local.B, local.C, local.D, R(36)); P(local.D, local.E, local.A, local.B, local.C, R(37)); P(local.C, local.D, local.E, local.A, local.B, R(38)); P(local.B, local.C, local.D, local.E, local.A, R(39)); #undef K #undef F #define F(x, y, z) (((x) & (y)) | ((z) & ((x) | (y)))) #define K 0x8F1BBCDC P(local.A, local.B, local.C, local.D, local.E, R(40)); P(local.E, local.A, local.B, local.C, local.D, R(41)); P(local.D, local.E, local.A, local.B, local.C, R(42)); P(local.C, local.D, local.E, local.A, local.B, R(43)); P(local.B, local.C, local.D, local.E, local.A, R(44)); P(local.A, local.B, local.C, local.D, local.E, R(45)); P(local.E, local.A, local.B, local.C, local.D, R(46)); P(local.D, local.E, local.A, local.B, local.C, R(47)); P(local.C, local.D, local.E, local.A, local.B, R(48)); P(local.B, local.C, local.D, local.E, local.A, R(49)); P(local.A, local.B, local.C, local.D, local.E, R(50)); P(local.E, local.A, local.B, local.C, local.D, R(51)); P(local.D, local.E, local.A, local.B, local.C, R(52)); P(local.C, local.D, local.E, local.A, local.B, R(53)); P(local.B, local.C, local.D, local.E, local.A, R(54)); P(local.A, local.B, local.C, local.D, local.E, R(55)); P(local.E, local.A, local.B, local.C, local.D, R(56)); P(local.D, local.E, local.A, local.B, local.C, R(57)); P(local.C, local.D, local.E, local.A, local.B, R(58)); P(local.B, local.C, local.D, local.E, local.A, R(59)); #undef K #undef F #define F(x, y, z) ((x) ^ (y) ^ (z)) #define K 0xCA62C1D6 P(local.A, local.B, local.C, local.D, local.E, R(60)); P(local.E, local.A, local.B, local.C, local.D, R(61)); P(local.D, local.E, local.A, local.B, local.C, R(62)); P(local.C, local.D, local.E, local.A, local.B, R(63)); P(local.B, local.C, local.D, local.E, local.A, R(64)); P(local.A, local.B, local.C, local.D, local.E, R(65)); P(local.E, local.A, local.B, local.C, local.D, R(66)); P(local.D, local.E, local.A, local.B, local.C, R(67)); P(local.C, local.D, local.E, local.A, local.B, R(68)); P(local.B, local.C, local.D, local.E, local.A, R(69)); P(local.A, local.B, local.C, local.D, local.E, R(70)); P(local.E, local.A, local.B, local.C, local.D, R(71)); P(local.D, local.E, local.A, local.B, local.C, R(72)); P(local.C, local.D, local.E, local.A, local.B, R(73)); P(local.B, local.C, local.D, local.E, local.A, R(74)); P(local.A, local.B, local.C, local.D, local.E, R(75)); P(local.E, local.A, local.B, local.C, local.D, R(76)); P(local.D, local.E, local.A, local.B, local.C, R(77)); P(local.C, local.D, local.E, local.A, local.B, R(78)); P(local.B, local.C, local.D, local.E, local.A, R(79)); #undef K #undef F ctx->state[0] += local.A; ctx->state[1] += local.B; ctx->state[2] += local.C; ctx->state[3] += local.D; ctx->state[4] += local.E; } /* * SHA-1 process buffer */ void mbedtls_sha1_update(mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen) { size_t fill; uint32_t left; if (ilen == 0) { return; } left = ctx->total[0] & 0x3F; fill = 64 - left; ctx->total[0] += (uint32_t) ilen; ctx->total[0] &= 0xFFFFFFFF; if (ctx->total[0] < (uint32_t) ilen) { ctx->total[1]++; } if (left && ilen >= fill) { memcpy((void *) (ctx->buffer + left), input, fill); mbedtls_internal_sha1_process(ctx, ctx->buffer); input += fill; ilen -= fill; left = 0; } while (ilen >= 64) { mbedtls_internal_sha1_process(ctx, input); input += 64; ilen -= 64; } if (ilen > 0) { memcpy((void *) (ctx->buffer + left), input, ilen); } } /* * SHA-1 final digest */ void mbedtls_sha1_finish(mbedtls_sha1_context *ctx, unsigned char output[20]) { uint32_t used; uint32_t high, low; /* * Add padding: 0x80 then 0x00 until 8 bytes remain for the length */ used = ctx->total[0] & 0x3F; ctx->buffer[used++] = 0x80; if (used <= 56) { /* Enough room for padding + length in current block */ memset(ctx->buffer + used, 0, 56 - used); } else { /* We'll need an extra block */ memset(ctx->buffer + used, 0, 64 - used); mbedtls_internal_sha1_process(ctx, ctx->buffer); memset(ctx->buffer, 0, 56); } /* * Add message length */ high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); low = (ctx->total[0] << 3); MBEDTLS_PUT_UINT32_BE(high, ctx->buffer, 56); MBEDTLS_PUT_UINT32_BE(low, ctx->buffer, 60); mbedtls_internal_sha1_process(ctx, ctx->buffer); /* * Output final state */ MBEDTLS_PUT_UINT32_BE(ctx->state[0], output, 0); MBEDTLS_PUT_UINT32_BE(ctx->state[1], output, 4); MBEDTLS_PUT_UINT32_BE(ctx->state[2], output, 8); MBEDTLS_PUT_UINT32_BE(ctx->state[3], output, 12); MBEDTLS_PUT_UINT32_BE(ctx->state[4], output, 16); } /* * output = SHA-1( input buffer ) */ void mbedtls_sha1(const unsigned char *input, size_t ilen, unsigned char output[20]) { mbedtls_sha1_context ctx; mbedtls_sha1_init(&ctx); mbedtls_sha1_starts(&ctx); mbedtls_sha1_update(&ctx, input, ilen); mbedtls_sha1_finish(&ctx, output); } ================================================ FILE: src/aiotieba/helper/crypto/src/tbcrypto/bb64.c ================================================ #include #include #include "tbcrypto/bb64.h" #define LAST_IND(x, part_type) (sizeof(x) / sizeof(part_type) - 1) #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN # define LOW_IND(x, part_type) LAST_IND(x, part_type) # define HIGH_IND(x, part_type) 0 #else # define HIGH_IND(x, part_type) LAST_IND(x, part_type) # define LOW_IND(x, part_type) 0 #endif #define LOBYTE(x) BYTEn(x, LOW_IND(x, unsigned char)) #define HIBYTE(x) BYTEn(x, HIGH_IND(x, unsigned char)) #define BYTEn(x, n) (*((unsigned char *)&(x) + n)) #define BYTE1(x) BYTEn(x, 1) #define BYTE2(x) BYTEn(x, 2) unsigned char *B6417 = "qogjOuCRNkfil5p4SQ3LAmxGKZTdesvB6z_YPahMI9t80rJyHW1DEwFbc7nUVX2-"; unsigned int BB64ResultLen(int srcLen) { return 4 * ((srcLen + 2) / 3u) + 2; } unsigned char *B6411(unsigned char *result, unsigned char a2, int a3) // int result { while (a3) { --a3; *(unsigned char *)(result + a3) = a2; } return result; } unsigned char *B6412(unsigned char *result, const unsigned char *a2, int a3) { while (a3) { --a3; *(unsigned char *)(result + a3) = *(unsigned char *)(a2 + a3); } return result; } unsigned char *B6413(int *a1, int a2, int a3) { int v3; // r6 int v4; // r8 int v7; // r7 unsigned char *result; // r0 int i; // r3 v3 = a2 & 0x3F; v4 = 64 - v3; v7 = (a2 << (32 - 5) | (a2 >> 5)); B6412((unsigned char *)(a1 + 33), &B6417[v3], v4); result = B6412((unsigned char *)a1 + v4 + 132, B6417, v3); *a1 = v7 ^ (758653732 << (v7 & 0xF)); if (a3) { result = B6411((unsigned char *)(a1 + 1), 64, 128); for (i = 0; i != 64; ++i) *((unsigned char *)a1 + *((unsigned char *)a1 + i + 132) + 4) = i; } return result; } int *B6419(const int *a1, int *a2, unsigned int a3) { unsigned int v3; // r3 int v4; // r4 int *result; // r0 v3 = a3 >> 2; v4 = *a1; for (result = a2; result != &a2[a3 >> 2]; ++result) *result = v4 ^ (*result << (32 - 3) | (*result >> 3)); if (v3 < (a3 + 3) >> 2) a2[v3] ^= v4; return result; } unsigned char *B6414(unsigned char *a1, const unsigned char *a2, int *a3) { unsigned int v3; // r5 unsigned int v4; // r4 unsigned int v5; // r3 unsigned char *v6; // r1 unsigned char *result; // r0 int v8; // [sp+4h] [bp-14h] v3 = (unsigned int)*a2; v4 = (unsigned int)a2[1]; LOBYTE(v8) = *(unsigned char *)((v3 & 0x3F) + a1 + 132); BYTE1(v8) = *(unsigned char *)((v4 & 0x3F) + a1 + 132); v5 = (unsigned int)a2[2]; v6 = (v5 & 0x3F) + a1; result = a1 + ((4 * (v4 >> 6)) | (16 * (v3 >> 6)) | (v5 >> 6)); BYTE2(v8) = *(unsigned char *)(v6 + 132); HIBYTE(v8) = *(unsigned char *)(result + 132); *a3 = v8; return result; } unsigned int GC02(unsigned char *preResultArray, unsigned int inputArrayLen, int mode) { unsigned int v5; // r9 unsigned int v6; // r5 unsigned int v7; // r8 unsigned int v8; // r7 unsigned char *v9; // r11 unsigned char *v10; // r10 int i; // r8 unsigned int result; // r0 unsigned char v13[4] = {0}; // [sp+Ch] [bp-F4h] BYREF unsigned char v14[196]; // [sp+10h] [bp-F0h] BYREF if (!inputArrayLen || !preResultArray) return -1; v5 = inputArrayLen % 3; v6 = inputArrayLen / 3; v7 = 4 * (inputArrayLen / 3); B6413((int *)v14, mode, 0); B6419((int *)v14, (int *)preResultArray, inputArrayLen); if (v5) { B6412(v13, preResultArray + 3 * v6, v5); B6414(v14, (unsigned char *)v13, (int *)(preResultArray + 3 * v6 + v6)); v8 = v7 + 4; } else { v8 = 4 * (inputArrayLen / 3); } v9 = preResultArray + 3 * v6; v10 = preResultArray + v7 - 4; for (i = 0;; ++i) { v9 -= 3; if (i == v6) break; B6414(v14, v9, (int *)(v10 - 4 * i)); } *(unsigned char *)(preResultArray + v8) = v5 + 65; result = v8 + 2; *(unsigned char *)(preResultArray + v8 + 1) = 0; return result; } void tbc_BB64Encode(const unsigned char *inputArray, int srcLen, int mode, unsigned char *dst) { signed int inputArrayLen; // r8 void *v12; // r5 inputArrayLen = srcLen; // GetArrayLength unsigned int resultLen = BB64ResultLen(inputArrayLen); v12 = (unsigned char *)malloc(resultLen); memset(v12, 0, resultLen); memcpy(v12, inputArray, inputArrayLen); memcpy(dst, v12, resultLen * sizeof(unsigned char)); // memcpy free(v12); } ================================================ FILE: src/aiotieba/helper/crypto/src/tbcrypto/cuid.c ================================================ #include #include #include "base32/base32.h" #include "crc/crc32.h" #include "mbedtls/md5.h" #include "mbedtls/sha1.h" #define XXH_INLINE_ALL #include "xxHash/xxhash.h" #include "tbcrypto/const.h" #include "tbcrypto/cuid.h" #define HASHER_NUM 4 #define STEP_SIZE 5 #define HASH_SIZE_IN_BIT 32 static const char CUID2_PERFIX[] = {'c', 'o', 'm', '.', 'b', 'a', 'i', 'd', 'u'}; static const char CUID3_PERFIX[] = {'c', 'o', 'm', '.', 'h', 'e', 'l', 'i', 'o', 's'}; static void __tbc_update(uint64_t* sec, uint64_t hashVal, uint64_t start, bool flag) { uint64_t end = start + HASH_SIZE_IN_BIT; uint64_t secTemp = *sec; uint64_t var9 = ((uint64_t)1 << end) - 1; uint64_t var5 = (var9 & *sec) >> start; if (flag) { var5 ^= hashVal; } else { var5 &= hashVal; } for (uint64_t i = 0; i < HASH_SIZE_IN_BIT; i++) { uint64_t opIdx = start + i; if (var5 & (uint64_t)1 << i) { secTemp |= (uint64_t)1 << opIdx; } else { secTemp &= ~((uint64_t)1 << opIdx); } } *sec = secTemp; } static void __tbc_writeBuffer(unsigned char* buffer, const uint64_t sec) { uint64_t tmpSec = sec; for (uint64_t i = 0; i < STEP_SIZE; i++) { buffer[i] = (unsigned char)((uint64_t)UINT8_MAX & tmpSec); tmpSec >>= 8; } } void tbc_heliosHash(const unsigned char* src, size_t srcSize, unsigned char* dst) { // init uint32_t crc32Val; uint32_t xxhash32Val; uint64_t sec = ((uint64_t)1 << 40) - 1; // equals to `-1L>>>-40L` in java unsigned char buffer[HASHER_NUM * STEP_SIZE]; memset(buffer, -1, STEP_SIZE); // Now buffer is [-1 * 5, ...] // 1st hash with CRC32 crc32Val = tbc_crc32(src, srcSize, 0); crc32Val = tbc_crc32(buffer, STEP_SIZE, crc32Val); __tbc_update(&sec, (uint64_t)crc32Val, 8, false); __tbc_writeBuffer(buffer + STEP_SIZE, sec); // Now buffer is [-1 * 5, crcrc, ...] // 2nd hash with xxHash32 XXH32_state_t xxState4StepTwo, xxState4StepThree; XXH32_reset(&xxState4StepTwo, 0); XXH32_update(&xxState4StepTwo, src, srcSize); XXH32_update(&xxState4StepTwo, buffer, STEP_SIZE * 2); XXH32_copyState(&xxState4StepThree, &xxState4StepTwo); xxhash32Val = XXH32_digest(&xxState4StepTwo); __tbc_update(&sec, xxhash32Val, 0, true); __tbc_writeBuffer(buffer + STEP_SIZE * 2, sec); // Now buffer is [-1[5], crc[5], xxxxx, ...] // 3rd hash with xxHash32 XXH32_update(&xxState4StepThree, buffer + STEP_SIZE * 2, STEP_SIZE); xxhash32Val = XXH32_digest(&xxState4StepThree); __tbc_update(&sec, xxhash32Val, 1, true); __tbc_writeBuffer(buffer + STEP_SIZE * 3, sec); // Now buffer is [-1[5], crc[5], xx[5], xx[5]] // 4th hash with CRC32 crc32Val = tbc_crc32(buffer + STEP_SIZE, STEP_SIZE * 3, crc32Val); __tbc_update(&sec, crc32Val, 7, true); // write to dst __tbc_writeBuffer(dst, sec); } void tbc_cuid_galaxy2(const unsigned char* androidID, unsigned char* dst) { // step 1: build src buffer and compute md5 unsigned char md5Buffer[sizeof(CUID2_PERFIX) + TBC_ANDROID_ID_SIZE]; size_t buffOffset = 0; memcpy(md5Buffer, CUID2_PERFIX, sizeof(CUID2_PERFIX)); buffOffset += sizeof(CUID2_PERFIX); memcpy(md5Buffer + buffOffset, androidID, TBC_ANDROID_ID_SIZE); unsigned char md5[TBC_MD5_HASH_SIZE]; mbedtls_md5(md5Buffer, sizeof(md5Buffer), md5); // step 2: assign md5 hex to dst // dst will be [md5 hex, ...] size_t dstOffset = 0; for (size_t imd5 = 0; imd5 < TBC_MD5_HASH_SIZE; imd5++) { dst[dstOffset] = HEX_UPPERCASE_TABLE[md5[imd5] >> 4]; dstOffset++; dst[dstOffset] = HEX_UPPERCASE_TABLE[md5[imd5] & 0x0F]; dstOffset++; } // step 3: add joining char // dst will be [md5 hex, '|V', ...] dst[dstOffset] = '|'; dstOffset++; dst[dstOffset] = 'V'; dstOffset++; // step 4: build dst buffer and compute helios hash unsigned char heHash[TBC_HELIOS_HASH_SIZE]; tbc_heliosHash(dst, TBC_MD5_STR_SIZE, heHash); // step 5: assign helios base32 to dst // dst will be [md5 hex, '|V', heliosHash base32] tbc_base32_encode(heHash, TBC_HELIOS_HASH_SIZE, (dst + dstOffset)); } void tbc_c3_aid(const unsigned char* androidID, const unsigned char* uuid, unsigned char* dst) { // step 1: set prefix // dst will be ['A00-', ...] dst[0] = 'A'; dst[1] = '0'; dst[2] = '0'; dst[3] = '-'; size_t dstOffset = 4; // step 2: build src buffer and compute sha1 unsigned char sha1Buffer[sizeof(CUID3_PERFIX) + TBC_ANDROID_ID_SIZE + TBC_UUID_SIZE]; size_t sha1BuffOffset = 0; memcpy(sha1Buffer, CUID3_PERFIX, sizeof(CUID3_PERFIX)); sha1BuffOffset += sizeof(CUID3_PERFIX); memcpy(sha1Buffer + sha1BuffOffset, androidID, TBC_ANDROID_ID_SIZE); sha1BuffOffset += TBC_ANDROID_ID_SIZE; memcpy(sha1Buffer + sha1BuffOffset, uuid, TBC_UUID_SIZE); unsigned char sha1[TBC_SHA1_HASH_SIZE]; mbedtls_sha1(sha1Buffer, sizeof(sha1Buffer), sha1); // step 3: compute sha1 base32 and assign // dst will be ['A00-', sha1 base32, ...] tbc_base32_encode(sha1, TBC_SHA1_HASH_SIZE, (dst + dstOffset)); dstOffset += TBC_SHA1_BASE32_SIZE; // step 4: add joining char // dst will be ['A00-', sha1 base32, '-', ...] dst[dstOffset] = '-'; dstOffset++; // step 5: build dst buffer and compute helios hash unsigned char heHash[TBC_HELIOS_HASH_SIZE]; tbc_heliosHash(dst, dstOffset, heHash); // step 6: assign helios base32 to dst // dst will be ['A00-', sha1 base32, '-', heliosHash base32] tbc_base32_encode(heHash, TBC_HELIOS_HASH_SIZE, (dst + dstOffset)); } ================================================ FILE: src/aiotieba/helper/crypto/src/tbcrypto/lib.c ================================================ #include "tbcrypto/pywrap.h" #include "tbcrypto/bb64.h" #include "tbcrypto/const.h" #include "tbcrypto/cuid.h" #include "tbcrypto/rc442.h" #include "tbcrypto/sign.h" PyObject* cuid_galaxy2(PyObject* Py_UNUSED(self), PyObject* args) { unsigned char dst[TBC_CUID_GALAXY2_SIZE]; const unsigned char* androidID; Py_ssize_t androidIDSize; if (!PyArg_ParseTuple(args, "s#", &androidID, &androidIDSize)) { PyErr_SetString(PyExc_TypeError, "Failed to parse args"); return NULL; } if (androidIDSize != 16) { PyErr_Format(PyExc_ValueError, "Invalid size of android_id. Expect 16, got %zu", androidIDSize); return NULL; } tbc_cuid_galaxy2(androidID, dst); return PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, dst, TBC_CUID_GALAXY2_SIZE); } PyObject* c3_aid(PyObject* Py_UNUSED(self), PyObject* args) { unsigned char dst[TBC_C3_AID_SIZE]; const unsigned char* androidID; Py_ssize_t androidIDSize; const unsigned char* uuid; Py_ssize_t uuidSize; if (!PyArg_ParseTuple(args, "s#s#", &androidID, &androidIDSize, &uuid, &uuidSize)) { PyErr_SetString(PyExc_TypeError, "Failed to parse args"); return NULL; } if (androidIDSize != 16) { PyErr_Format(PyExc_ValueError, "Invalid size of android_id. Expect 16, got %zu", androidIDSize); return NULL; } if (uuidSize != 36) { PyErr_Format(PyExc_ValueError, "Invalid size of uuid. Expect 36, got %zu", androidIDSize); return NULL; } tbc_c3_aid(androidID, uuid, dst); return PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, dst, TBC_C3_AID_SIZE); } PyObject* rc4_42(PyObject* Py_UNUSED(self), PyObject* args) { unsigned char dst[TBC_RC4_SIZE]; const unsigned char* xyusMd5Str; Py_ssize_t xyusMd5Size; const unsigned char* cbcSecKey; Py_ssize_t cbcSecKeySize; if (!PyArg_ParseTuple(args, "s#y#", &xyusMd5Str, &xyusMd5Size, &cbcSecKey, &cbcSecKeySize)) { PyErr_SetString(PyExc_TypeError, "Failed to parse args"); return NULL; } if (xyusMd5Size != 32) { PyErr_Format(PyExc_ValueError, "Invalid size of xyus_md5. Expect 32, got %zu", xyusMd5Size); return NULL; } if (cbcSecKeySize != 16) { PyErr_Format(PyExc_ValueError, "Invalid size of cbc_sec_key. Expect 16, got %zu", cbcSecKeySize); return NULL; } tbc_rc4_42(xyusMd5Str, cbcSecKey, dst); return PyBytes_FromStringAndSize((char*)dst, TBC_RC4_SIZE); } PyObject* enuid(PyObject* Py_UNUSED(self), PyObject* args) { unsigned char dst[TBC_ENUID_SIZE + 1]; // str ends with '\0' const unsigned char* cuid2; Py_ssize_t cuid2Size; if (!PyArg_ParseTuple(args, "s#", &cuid2, &cuid2Size)) { PyErr_SetString(PyExc_TypeError, "Failed to parse args"); return NULL; } if (cuid2Size != TBC_CUID_GALAXY2_SIZE) { PyErr_Format(PyExc_ValueError, "Invalid size of cuid_galaxy2. Expect %zu, got %zu", TBC_CUID_GALAXY2_SIZE, cuid2Size); return NULL; } tbc_BB64Encode(cuid2, cuid2Size, 0, dst); return PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, dst, TBC_ENUID_SIZE); } static PyMethodDef crypto_methods[] = { {"cuid_galaxy2", (PyCFunction)cuid_galaxy2, METH_VARARGS, NULL}, {"c3_aid", (PyCFunction)c3_aid, METH_VARARGS, NULL}, {"rc4_42", (PyCFunction)rc4_42, METH_VARARGS, NULL}, {"sign", (PyCFunction)sign, METH_VARARGS, NULL}, {"enuid", (PyCFunction)enuid, METH_VARARGS, NULL}, {NULL, NULL, 0, NULL}, }; static PyModuleDef crypto_module = {PyModuleDef_HEAD_INIT, "crypto", NULL, -1, crypto_methods}; PyMODINIT_FUNC PyInit_crypto(void) { PyObject* mod = PyModule_Create(&crypto_module); if (mod == NULL) { return NULL; } #ifdef Py_GIL_DISABLED PyUnstable_Module_SetGIL(mod, Py_MOD_GIL_NOT_USED); #endif return mod; } ================================================ FILE: src/aiotieba/helper/crypto/src/tbcrypto/rc442.c ================================================ #include #include "tbcrypto/const.h" #include "tbcrypto/rc442.h" typedef struct rc4_42_context { int x; int y; unsigned char m[256]; } rc4_42_context; static void __tbc_rc442Setup(rc4_42_context* ctx, const unsigned char* key, unsigned int keyLen) { int i, j, a; unsigned int k; unsigned char* m; ctx->x = 0; ctx->y = 0; m = ctx->m; for (i = 0; i < 256; i++) m[i] = (unsigned char)i; j = k = 0; for (i = 0; i < 256; i++, k++) { if (k >= keyLen) k = 0; a = m[i]; j = (j + a + key[k]) & 0xFF; m[i] = m[j]; m[j] = (unsigned char)a; } } static void __tbc_rc442Crypt(rc4_42_context* ctx, const unsigned char* src, size_t srcLen, unsigned char* dst) { int x, y, a, b; size_t i; unsigned char* m; x = ctx->x; y = ctx->y; m = ctx->m; for (i = 0; i < srcLen; i++) { x = (x + 1) & 0xFF; a = m[x]; y = (y + a) & 0xFF; b = m[y]; m[x] = (unsigned char)b; m[y] = (unsigned char)a; dst[i] = (unsigned char)(src[i] ^ m[(unsigned char)(a + b)]); dst[i] = dst[i] ^ 42; // different from general RC4 } ctx->x = x; ctx->y = y; } void tbc_rc4_42(const unsigned char* xyusMd5Str, const unsigned char* cbcSecKey, unsigned char* dst) { rc4_42_context rc442Ctx; __tbc_rc442Setup(&rc442Ctx, xyusMd5Str, TBC_MD5_STR_SIZE); __tbc_rc442Crypt(&rc442Ctx, cbcSecKey, TBC_CBC_SECKEY_SIZE, dst); } ================================================ FILE: src/aiotieba/helper/crypto/src/tbcrypto/sign.c ================================================ #include #include #include "mbedtls/md5.h" #include "rapidjson/itoa.h" #include "tbcrypto/const.h" #include "tbcrypto/sign.h" static const unsigned char SIGN_SUFFIX[] = {'t', 'i', 'e', 'b', 'a', 'c', 'l', 'i', 'e', 'n', 't', '!', '!', '!'}; static void __tbc_pyStr2UTF8(const char** dst, size_t* dstSize, PyObject* pyoStr) { if (PyUnicode_1BYTE_KIND == PyUnicode_KIND(pyoStr)) { (*dst) = PyUnicode_DATA(pyoStr); (*dstSize) = PyUnicode_GET_LENGTH(pyoStr); } else { (*dst) = PyUnicode_AsUTF8(pyoStr); (*dstSize) = strlen(*dst); } } PyObject* sign(PyObject* Py_UNUSED(self), PyObject* args) { PyObject* items; if (!PyArg_ParseTuple(args, "O", &items)) { PyErr_SetString(PyExc_TypeError, "Failed to parse args"); return NULL; } if (!PyList_Check(items)) { PyErr_SetString(PyExc_TypeError, "Input should be List[Tuple[str, str | int]]]"); return NULL; } Py_ssize_t listSize = PyList_GET_SIZE(items); mbedtls_md5_context md5Ctx; mbedtls_md5_init(&md5Ctx); mbedtls_md5_starts(&md5Ctx); char itoaBuffer[20]; for (Py_ssize_t iList = 0; iList < listSize; iList++) { PyObject* item = PyList_GET_ITEM(items, iList); if (!PyTuple_Check(item)) { PyErr_SetString(PyExc_TypeError, "List item should be Tuple[str, str | int]"); return NULL; } PyObject* pyoKey = PyTuple_GetItem(item, 0); if (!pyoKey) { return NULL; // IndexError } char* key; size_t keySize; __tbc_pyStr2UTF8((const char**)&key, &keySize, pyoKey); // Warn: The last NULL is replaced by '=', DO NOT use `strlen` or similar method over `key` afterwards! key[keySize] = '='; keySize++; mbedtls_md5_update(&md5Ctx, (unsigned char*)key, keySize); PyObject* pyoVal = PyTuple_GetItem(item, 1); if (!pyoVal) { return NULL; // IndexError } if (PyUnicode_Check(pyoVal)) { const char* val; size_t valSize; __tbc_pyStr2UTF8(&val, &valSize, pyoVal); mbedtls_md5_update(&md5Ctx, (unsigned char*)val, valSize); } else if (PyLong_Check(pyoVal)) { int64_t ival = PyLong_AsLongLong(pyoVal); char* val = itoaBuffer; char* valEnd = i64toa(ival, val); size_t valSize = valEnd - val; mbedtls_md5_update(&md5Ctx, (unsigned char*)val, valSize); } else { PyErr_SetString(PyExc_TypeError, "item[1] should be str or int"); return NULL; } } mbedtls_md5_update(&md5Ctx, SIGN_SUFFIX, sizeof(SIGN_SUFFIX)); unsigned char md5[TBC_MD5_HASH_SIZE]; mbedtls_md5_finish(&md5Ctx, md5); unsigned char dst[TBC_MD5_STR_SIZE]; size_t dstOffset = 0; for (size_t imd5 = 0; imd5 < TBC_MD5_HASH_SIZE; imd5++) { dst[dstOffset] = HEX_LOWERCASE_TABLE[md5[imd5] >> 4]; dstOffset++; dst[dstOffset] = HEX_LOWERCASE_TABLE[md5[imd5] & 0x0F]; dstOffset++; } return PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, dst, TBC_MD5_STR_SIZE); } ================================================ FILE: src/aiotieba/helper/utils.py ================================================ from __future__ import annotations import asyncio import functools import logging import sys from datetime import datetime from typing import TYPE_CHECKING, Any from ..logging import get_logger if TYPE_CHECKING: from collections.abc import Callable if sys.version_info >= (3, 11): async_timeout = asyncio else: import async_timeout try: import orjson as jsonlib def pack_json(obj: Any) -> str: bjson: bytes = jsonlib.dumps(obj) return bjson.decode("utf-8") except ImportError: import json as jsonlib pack_json = functools.partial(jsonlib.dumps, separators=(",", ":")) parse_json = jsonlib.loads def is_portrait(portrait: Any) -> bool: """ 简单判断输入是否符合portrait格式 """ return isinstance(portrait, str) and portrait.startswith("tb.") def is_user_name(user_name: Any) -> bool: """ 简单判断输入是否符合user_name格式 """ return isinstance(user_name, str) and not user_name.startswith("tb.") def default_datetime() -> datetime: return datetime(1970, 1, 1) def timeout(delay: float, loop: asyncio.AbstractEventLoop) -> async_timeout.Timeout: now = loop.time() when = round(now) + delay return async_timeout.timeout_at(when) if sys.version_info >= (3, 13): from warnings import deprecated else: import warnings def deprecated(reason): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): warnings.warn(f"{func.__name__} is deprecated: {reason}", DeprecationWarning, stacklevel=2) return func(*args, **kwargs) return wrapper return decorator def handle_exception( null_factory: Callable[[], Any], ok_log_level: int = logging.NOTSET, err_log_level: int = logging.WARNING, ): """ 处理request抛出的异常 只能用于装饰类成员函数 Args: null_factory (Callable[[], Any]): 空构造工厂 用于返回一个默认值 ok_log_level (int, optional): 正常日志等级. Defaults to logging.NOTSET. err_log_level (int, optional): 异常日志等级. Defaults to logging.WARNING. """ def wrapper(func): @functools.wraps(func) async def awrapper(self, *args, **kwargs): def _log(log_level: int, err: Exception | None = None) -> None: logger = get_logger() if logger.isEnabledFor(err_log_level): if err is None: err = "Succeeded" log_str = f"{err}. args={args} kwargs={kwargs}" record = logger.makeRecord(logger.name, log_level, None, 0, log_str, None, None, func.__name__) logger.handle(record) try: ret = await func(self, *args, **kwargs) if ok_log_level: _log(ok_log_level) except Exception as err: _log(err_log_level, err) ret = null_factory() ret.err = err return ret else: return ret return awrapper return wrapper ================================================ FILE: src/aiotieba/logging.py ================================================ import logging import logging.handlers import sys from pathlib import Path logging.addLevelName(logging.FATAL, "FATAL") logging.addLevelName(logging.WARNING, "WARN") logging.raiseExceptions = False _FORMATTER = logging.Formatter("<{asctime}> [{levelname}] [{funcName}] {message}", "%Y-%m-%d %H:%M:%S", style="{") class TiebaLogger(logging.Logger): """ 日志记录器 Args: name (str): 日志文件名(不含扩展名) 留空则自动设置为`sys.argv[0]`的文件名. Defaults to ''. stream_log_level (int): 标准输出日志级别. Defaults to `logging.DEBUG`. """ def __init__(self, name: str = "", stream_log_level: int = logging.DEBUG) -> None: if name == "": name = Path(sys.argv[0]).stem super().__init__(name) stream_hd = logging.StreamHandler(sys.stdout) stream_hd.setLevel(stream_log_level) stream_hd.setFormatter(_FORMATTER) self.addHandler(stream_hd) LOGGER = None def get_logger() -> TiebaLogger: """ 获取日志记录器 Returns: TiebaLogger """ global LOGGER if LOGGER is None: LOGGER = TiebaLogger() return LOGGER def set_logger(new_logger: logging.Logger) -> None: """ 更换aiotieba的日志记录器 Args: new_logger (logging.Logger): 新日志记录器 """ global LOGGER LOGGER = new_logger def set_formatter(formatter: logging.Formatter) -> None: """ 更换aiotieba的日志格式 Args: formatter (logging.Formatter): 新格式 """ global _FORMATTER _FORMATTER = formatter if LOGGER is not None: for hd in LOGGER.handlers: hd.setFormatter(formatter) _FILELOG_ENABLED = False def enable_filelog(log_level: int = logging.INFO, log_dir: Path = Path("log"), backup_count: int = 5) -> None: """ 启用文件日志 Args: log_level (int): 文件日志级别. Defaults to `logging.INFO`. log_dir (Path): 用于存放日志文件的文件夹. Defaults to Path('log'). backup_count (int): 时间轮转文件日志的保留文件数. Defaults to 5. """ global _FILELOG_ENABLED if _FILELOG_ENABLED: return log_dir = Path(log_dir) log_dir.mkdir(0o755, parents=True, exist_ok=True) logger = get_logger() file_hd = logging.handlers.TimedRotatingFileHandler( log_dir / f"{logger.name}.log", when="MIDNIGHT", backupCount=backup_count, encoding="utf-8" ) file_hd.setLevel(log_level) file_hd.setFormatter(_FORMATTER) logger.addHandler(file_hd) _FILELOG_ENABLED = True ================================================ FILE: src/aiotieba/typing.py ================================================ from aiotieba.api._classdef import UserInfo from .api._classdef import contents from .api.get_comments import Comment, Comments from .api.get_posts import Post, Posts from .api.get_threads import Thread, Threads TypeUserInfo = UserInfo ================================================ FILE: tests/conftest.py ================================================ import os import pytest_asyncio import aiotieba as tb @pytest_asyncio.fixture(loop_scope="session") async def client(): async with tb.Client(os.getenv("TB_BDUSS"), os.getenv("TB_STOKEN", ""), try_ws=True, proxy=True) as client: yield client ================================================ FILE: tests/test_crypto.py ================================================ import pytest import aiotieba as tb from aiotieba.helper.crypto import _sign, rc4_42 @pytest.mark.asyncio(loop_scope="session") async def test_clib(client: tb.Client): # await client._Client__init_z_id() # assert client.account.z_id != '' account = client.account account.android_id = "6723280942424242" account.uuid = "67232809-3407-3442-4207-672346917aaa" assert account.cuid_galaxy2 == "06C7F37D41256F25FABA97B885DB6EFB|VAPUDW7TA" assert account.c3_aid == "A00-OGBA33NRAQASXI6FDZ4YAJFTK75EF4Y5-YVOG764X" data = [ ("reverse", 1999), ("hello_cosmic", "你好42"), ] assert _sign(data) == "248dab3e1fe46aea603cdc45c08f80eb" query_key = rc4_42("d0337b3b3d597c5f87a1c0c37139d87b", b"6723280942424242") assert query_key == b"\x9f\xabU\x14\xa7\x0e\xb6k\xc4wV\xf2HN+." ================================================ FILE: tests/test_get_ats.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Ats(client: tb.Client): ats = await client.get_ats() ##### At ##### at = ats[0] # UserInfo_at user = at.user assert user.user_id > 0 assert user.portrait != "" assert user.nick_name_new != "" assert user.nick_name == user.nick_name_new assert user.show_name == user.nick_name_new assert user.priv_like != 0 assert user.priv_reply != 0 # At assert at.text != "" assert at.fname != "" assert at.tid > 0 assert at.pid > 0 assert at.author_id == user.user_id assert at.create_time > 0 ================================================ FILE: tests/test_get_blocks.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Blocks(client: tb.Client): blocks = await client.get_blocks(21841105) ##### Block ##### block = blocks[0] assert block.user_id > 0 assert block.day > 0 ================================================ FILE: tests/test_get_comments.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Comments(client: tb.Client): comments = await client.get_comments(8211419000, 146544112004) comment = comments[0] ##### Forum_c ##### forum = comments.forum assert forum.fid == 37574 assert forum.fname == "starry" assert forum.category != "" assert forum.subcategory != "" ##### Thread_c ##### thread = comments.thread # UserInfo_ct user = thread.user assert user.user_id > 0 assert user.portrait != "" assert user.user_name != "" assert user.nick_name_new != "" assert user.nick_name == user.nick_name_new assert user.show_name == user.nick_name_new assert user.level > 0 # Thread_c assert thread.title != "" assert thread.fid > 0 assert thread.fname != "" assert thread.tid == 8211419000 assert thread.author_id == user.user_id assert thread.reply_num > 0 ##### Post_c ##### post = comments.post # UserInfo_cp user = post.user assert user.user_id > 0 assert user.portrait != "" assert user.user_name != "" assert user.nick_name_new != "" assert user.nick_name == user.nick_name_new assert user.show_name == user.nick_name_new assert user.level > 0 assert user.gender > 0 assert user.priv_like != 0 assert user.priv_reply != 0 # Post_c assert post.text != "" assert post.fid > 0 assert post.fname != "" assert post.tid > 0 assert post.pid > 0 assert post.author_id == user.user_id assert post.floor > 0 assert post.create_time > 0 # FragText frag = post.contents.texts[0] assert frag.text != "" # FragAt frag = post.contents.ats[0] assert frag.text != "" assert frag.user_id > 0 # FragVoice frag = post.contents.voice assert frag.md5 != "" assert frag.duration > 0 # FragImage frag = post.contents.imgs[0] assert frag.src != "" assert frag.big_src != "" assert frag.origin_src != "" assert len(frag.hash) == 40 assert frag.show_width > 0 assert frag.show_height > 0 # FragEmoji frag = post.contents.emojis[0] assert frag.desc != "" assert frag.id == "image_emoticon3" ##### Comment ##### comment = comments[0] # UserInfo_c user = comment.user assert user.user_id > 0 assert user.portrait != "" assert user.user_name != "" assert user.nick_name_new != "" assert user.nick_name == user.nick_name_new assert user.show_name == user.nick_name_new assert user.level > 0 assert user.gender > 0 assert user.priv_like != 0 assert user.priv_reply != 0 # Comment assert comment.text != "" assert comment.fid > 0 assert comment.fname != "" assert comment.tid > 0 assert comment.ppid > 0 assert comment.pid > 0 assert comment.author_id == user.user_id assert comment.floor > 0 assert comment.create_time > 0 assert comment.is_thread_author == (comment.author_id == thread.author_id) # FragText frag = comment.contents.texts[0] assert frag.text != "" # FragAt frag = comment.contents.ats[0] assert frag.text != "" assert frag.user_id > 0 # FragVoice frag = comment.contents.voice assert frag.md5 != "" assert frag.duration > 0 # FragEmoji frag = comment.contents.emojis[0] assert frag.desc != "" # FragTiebaplus frag = comment.contents.tiebapluses[0] assert frag.text != "" assert frag.url != "" frag = comment.contents.tiebapluses[1] assert frag.text != "" assert frag.url != "" comment = comments[1] assert comment.reply_to_id != 0 @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_FragLink(client: tb.Client): comments = await client.get_comments(8211419000, 146546137439) ##### Post_c ##### post = comments.post # FragLink frag = post.contents.links[0] assert frag.title != "" assert frag.url.host == "tieba.baidu.com" assert frag.is_external is False frag = post.contents.links[1] assert frag.url.host == "stackoverflow.com" assert frag.is_external is True ##### Comment ##### comment = comments[0] # FragLink frag = comment.contents.links[0] assert frag.title != "" assert frag.url.host == "tieba.baidu.com" assert frag.is_external is False frag = comment.contents.links[1] assert frag.url.host == "stackoverflow.com" assert frag.is_external is True ================================================ FILE: tests/test_get_fans.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Fans(client: tb.Client): fans = await client.get_fans(957339815) ##### Fan ##### fan = fans[0] assert fan.user_id > 0 assert fan.portrait != "" ================================================ FILE: tests/test_get_follow_forums.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_FollowForums(client: tb.Client): forums = await client.get_follow_forums(4954297652) ##### FollowForum ##### forum = forums[0] assert forum.fid > 0 assert forum.fname != "" assert forum.level > 0 assert forum.exp > 0 ================================================ FILE: tests/test_get_follows.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Follows(client: tb.Client): follows = await client.get_follows(957339815) ##### Follow ##### follow = follows[0] assert follow.user_id > 0 assert follow.portrait != "" ================================================ FILE: tests/test_get_forum_detail.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Forum_detail(client: tb.Client): forum = await client.get_forum_detail(21841105) ##### Forum_detail ##### assert forum.fid > 0 assert forum.fname != "" assert forum.small_avatar != "" assert forum.origin_avatar != "" assert forum.slogan != "" assert forum.member_num > 0 assert forum.post_num > 0 assert forum.has_bawu is True ================================================ FILE: tests/test_get_homepage.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Homepage(client: tb.Client): homepage = await client.get_homepage(957339815) ##### Thread_pf ##### thread = homepage[0] assert thread.fid > 0 assert thread.fname != "" assert thread.tid > 0 assert thread.pid > 0 assert thread.author_id == thread.user.user_id assert thread.view_num > 0 assert thread.create_time > 0 ##### UserInfo_pf ##### user = homepage.user assert user.user_id == thread.user.user_id assert user.user_id > 0 assert user.portrait != "" assert user.user_name != "" assert user.nick_name_new != "" assert user.tieba_uid > 0 assert user.glevel > 0 assert user.gender != tb.Gender.UNKNOWN assert user.age > 0.0 assert user.post_num > 0 assert user.agree_num > 0 assert user.fan_num > 0 assert user.follow_num > 0 assert user.forum_num > 0 assert user.ip != "" ================================================ FILE: tests/test_get_posts.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Posts(client: tb.Client): posts = await client.get_posts(8211419000) ##### Forum_p ##### forum = posts.forum assert forum.fid == 37574 assert forum.fname == "starry" assert forum.category != "" assert forum.subcategory != "" assert forum.member_num > 0 assert forum.post_num > 0 ##### Thread_p ##### thread = posts.thread # UserInfo_pt user = thread.user assert user.user_id > 0 assert user.portrait != "" assert user.user_name != "" assert user.nick_name_new != "" assert user.nick_name == user.nick_name_new assert user.show_name == user.nick_name_new assert user.level > 0 assert user.glevel > 0 assert user.ip != "" assert user.priv_like != 0 assert user.priv_reply != 0 # VoteInfo vote_info = thread.vote_info assert vote_info.title != "" assert vote_info.total_vote > 0 assert vote_info.total_user > 0 option = vote_info.options[0] assert option.vote_num > 0 assert option.text != "" # Thread_p assert thread.text != "" assert thread.title != "" assert thread.fid > 0 assert thread.fname != "" assert thread.tid > 0 assert thread.pid == posts[0].pid assert thread.author_id == posts[0].user.user_id assert thread.view_num > 0 assert thread.reply_num > 0 assert thread.share_num > 0 assert thread.create_time > 0 ##### Post ##### assert len(posts) >= 2 post = posts[1] # UserInfo_p user = post.user assert user.user_id > 0 assert user.portrait != "" assert user.user_name != "" assert user.nick_name_new != "" assert user.show_name == user.nick_name_new assert user.level > 0 assert user.glevel > 0 assert user.ip != "" assert user.priv_like != 0 assert user.priv_reply != 0 # Post assert post.text != "" assert post.fid > 0 assert post.fname != "" assert post.tid > 0 assert post.pid > 0 assert post.author_id == user.user_id assert post.floor > 0 assert post.reply_num > 0 assert post.create_time > 0 assert post.is_thread_author == (post.author_id == thread.author_id) # FragText frag = post.contents.texts[0] assert frag.text != "" # FragAt frag = post.contents.ats[0] assert frag.text != "" assert frag.user_id > 0 # FragVoice frag = post.contents.voice assert frag.md5 != "" # FragImage frag = post.contents.imgs[0] assert frag.src != "" assert frag.big_src != "" assert frag.origin_src != "" assert len(frag.hash) == 40 assert frag.show_width > 0 assert frag.show_height > 0 # FragEmoji frag = post.contents.emojis[0] assert frag.id == "image_emoticon3" assert frag.desc != "" # FragTiebaplus frag = post.contents.tiebapluses[0] assert frag.text != "" assert frag.url != "" frag = post.contents.tiebapluses[1] assert frag.text != "" assert frag.url != "" # FragLink post = posts[2] frag = post.contents.links[0] assert frag.title != "" assert frag.url.host == "tieba.baidu.com" assert frag.is_external is False frag = post.contents.links[1] assert frag.url.host == "stackoverflow.com" assert frag.is_external is True # Posts with video posts = await client.get_posts(6205407601) # FragVideo frag = posts.thread.contents.video assert frag.src != "" assert frag.cover_src != "" assert frag.duration > 0 assert frag.width > 0 assert frag.height > 0 assert frag.view_num > 0 @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_ShareThread_pt(client: tb.Client): posts = await client.get_posts(8213449397) ##### ShareThread_pt ##### sthread = posts.thread.share_origin assert sthread.text != "" assert sthread.title != "" assert sthread.author_id > 0 assert sthread.fid == 37574 assert sthread.fname == "starry" assert sthread.tid > 0 # VoteInfo vote_info = sthread.vote_info assert vote_info.title != "" assert vote_info.total_vote > 0 assert vote_info.total_user > 0 option = vote_info.options[0] assert option.vote_num > 0 assert option.text != "" # FragText frag = sthread.contents.texts[1] assert frag.text != "" # FragAt frag = sthread.contents.ats[0] assert frag.text != "" assert frag.user_id > 0 # FragVoice frag = sthread.contents.voice assert frag.md5 != "" assert frag.duration > 0 # FragImage frag = sthread.contents.imgs[0] assert frag.src != "" assert frag.big_src != "" assert frag.origin_src != "" assert len(frag.hash) == 40 assert frag.show_width > 0 assert frag.show_height > 0 # FragLink frag = sthread.contents.links[0] assert frag.title != "" assert frag.url.host == "tieba.baidu.com" assert frag.is_external is False ================================================ FILE: tests/test_get_recovers.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Recovers(client: tb.Client): recovers = await client.get_recovers(21841105) ##### Recover ##### recover = recovers[0] assert recover.tid > 0 assert recover.op_show_name != "" assert recover.op_time != 0 ================================================ FILE: tests/test_get_threads.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_Threads(client: tb.Client): fname = "starry" threads = await client.get_threads(fname) ##### Forum_t ##### forum = threads.forum assert forum.fid == 37574 assert forum.fname == fname assert forum.category != "" assert forum.subcategory != "" assert forum.member_num > 0 assert forum.post_num > 0 assert forum.thread_num > 0 assert forum.has_bawu is True assert forum.has_rule is False ##### Thread ##### assert len(threads) >= 2 for thread in threads: # Normal Thread if thread.tid == 8211419000: # UserInfo_t user = thread.user assert user.user_id > 0 assert user.portrait != "" assert user.user_name != "" assert user.nick_name_new != "" assert user.nick_name == user.nick_name_new assert user.show_name == user.nick_name_new assert user.level > 0 assert user.glevel > 0 assert user.priv_like != 0 assert user.priv_reply != 0 # VoteInfo vote_info = thread.vote_info assert vote_info.title != "" option = vote_info.options[0] assert option.vote_num > 0 assert option.text != "" # Thread assert thread.text != "" assert thread.title != "" assert thread.fid > 0 assert thread.fname != "" assert thread.pid > 0 assert thread.author_id == user.user_id assert thread.view_num > 0 assert thread.reply_num > 0 assert thread.create_time > 0 assert thread.last_time > 0 # FragText frag = thread.contents.texts[0] assert frag.text != "" # FragAt frag = thread.contents.ats[0] assert frag.text != "" assert frag.user_id > 0 # FragVoice frag = thread.contents.voice assert frag.md5 != "" assert frag.duration > 0 # FragImage frag = thread.contents.imgs[0] assert frag.src != "" assert frag.big_src != "" assert frag.origin_src != "" assert len(frag.hash) == 40 assert frag.show_width > 0 assert frag.show_height > 0 # FragEmoji frag = thread.contents.emojis[0] assert frag.id == "image_emoticon2" assert frag.desc != "" # FragTiebaplus frag = thread.contents.tiebapluses[0] assert frag.text != "" assert frag.url != "" frag = thread.contents.tiebapluses[1] assert frag.text != "" assert frag.url != "" # FragLink frag = thread.contents.links[0] assert frag.title != "" assert frag.url.host == "tieba.baidu.com" assert frag.is_external is False frag = thread.contents.links[1] assert frag.url.host == "stackoverflow.com" assert frag.is_external is True # Share Thread elif thread.tid == 8213449397: sthread = thread.share_origin # FragText frag = sthread.contents.texts[0] assert frag.text != "" # FragAt frag = sthread.contents.ats[0] assert frag.text != "" assert frag.user_id != 0 # FragLink frag = sthread.contents.links[0] assert frag.title != "" assert frag.url.host == "tieba.baidu.com" assert frag.is_external is False # FragVoice frag = sthread.contents.voice assert frag.md5 != "" assert frag.duration > 0 # FragImage frag = sthread.contents.imgs[0] assert frag.src != "" assert frag.big_src != "" assert frag.origin_src != "" assert len(frag.hash) == 40 assert frag.show_width > 0 assert frag.show_height > 0 # VoteInfo vote_info = sthread.vote_info assert vote_info.title != "" option = vote_info.options[0] assert option.vote_num > 0 assert option.text != "" # Share Video elif thread.tid == 8553772146: sthread = thread.share_origin # FragVideo frag = sthread.contents.video assert frag.src != "" assert frag.cover_src != "" assert frag.duration > 0 assert frag.width > 0 assert frag.height > 0 assert frag.view_num > 0 ================================================ FILE: tests/test_get_user_info.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_get_user_info(client: tb.Client): self_info = await client.get_self_info(tb.ReqUInfo.BASIC) assert self_info.user_id > 0 assert self_info.portrait != "" assert self_info.user_name != "" self_info = await client.get_self_info() assert self_info.user_id > 0 assert self_info.portrait != "" assert self_info.user_name != "" assert self_info.nick_name_new != "" assert self_info.tieba_uid > 0 assert self_info.glevel > 0 assert self_info.age > 0 assert self_info.ip != "" assert self_info.post_num > 0 assert self_info.priv_like != 0 assert self_info.priv_reply != 0 homepage = await client.get_homepage(self_info.user_id) user = homepage.user assert user.user_id == self_info.user_id assert user.portrait == self_info.portrait assert user.user_name == self_info.user_name user = await client.get_user_info(self_info.portrait, tb.enums.ReqUInfo.BASIC) assert user.user_id == self_info.user_id assert user.portrait == self_info.portrait assert user.user_name == self_info.user_name user = await client.get_user_info(user.user_id, tb.enums.ReqUInfo.BASIC) assert user.user_id == self_info.user_id assert user.portrait == self_info.portrait assert user.user_name == self_info.user_name user = await client.get_user_info(user.user_name, tb.enums.ReqUInfo.BASIC) assert user.user_id == self_info.user_id assert user.portrait == self_info.portrait assert user.user_name == self_info.user_name user = await client.get_user_info(user.portrait) assert user.user_id == self_info.user_id assert user.portrait == self_info.portrait assert user.user_name == self_info.user_name assert user.tieba_uid > 0 assert user.glevel > 0 assert user.age > 0 assert user.ip != "" assert user.post_num > 0 assert user.priv_like != 0 assert user.priv_reply != 0 user = await client.get_user_info(user.user_id) assert user.user_id == self_info.user_id assert user.portrait == self_info.portrait assert user.user_name == self_info.user_name assert user.tieba_uid > 0 assert user.glevel > 0 assert user.age > 0 assert user.ip != "" assert user.post_num > 0 assert user.priv_like != 0 assert user.priv_reply != 0 user = await client.tieba_uid2user_info(3356245857) assert user.user_id == self_info.user_id assert user.portrait == self_info.portrait assert user.user_name == self_info.user_name assert user.tieba_uid > 0 assert user.age > 0 ================================================ FILE: tests/test_get_user_posts.py ================================================ import pytest import aiotieba as tb @pytest.mark.flaky(reruns=2, reruns_delay=5.0) @pytest.mark.asyncio(loop_scope="session") async def test_get_user_posts(client: tb.Client): user_id = 4954297652 postss = await client.get_user_posts(user_id) ##### UserPosts ##### posts = postss[0] assert posts.fid > 0 assert posts.tid > 0 ##### UserPost ##### post = posts[0] assert len(post.contents) > 0 assert post.fid > 0 assert post.tid > 0 assert post.pid > 0 assert post.create_time > 0 ##### UserInfo_u ##### user = post.user assert user.user_id == post.author_id assert user.portrait != "" assert user.user_name != "" assert user.nick_name_new != ""