package http import ( "strconv" "hyapp/pkg/tencentrtc" roomv1 "hyapp.local/api/proto/room/v1" ) const joinRoomContributionRankLimit = 10 type roomListData struct { Rooms []roomListItemData `json:"rooms"` NextCursor string `json:"next_cursor,omitempty"` } type myRoomData struct { HasRoom bool `json:"has_room"` Room *roomListItemData `json:"room,omitempty"` ServerTimeMS int64 `json:"server_time_ms"` } type roomListItemData struct { RoomID string `json:"room_id"` IMGroupID string `json:"im_group_id"` OwnerUserID string `json:"owner_user_id,omitempty"` HostUserID string `json:"host_user_id,omitempty"` Title string `json:"title,omitempty"` CoverURL string `json:"cover_url,omitempty"` Mode string `json:"mode,omitempty"` Status string `json:"status,omitempty"` Heat int64 `json:"heat"` OnlineCount int32 `json:"online_count"` SeatCount int32 `json:"seat_count"` OccupiedSeatCount int32 `json:"occupied_seat_count"` VisibleRegionID int64 `json:"visible_region_id,omitempty"` AppCode string `json:"app_code,omitempty"` RoomShortID string `json:"room_short_id,omitempty"` } type joinRoomData struct { Result roomCommandResultData `json:"result"` Room roomInitialData `json:"room"` Viewer roomViewerData `json:"viewer"` Seats []roomSeatData `json:"seats"` ContributionRank []roomRankItemData `json:"contribution_rank"` Profiles []roomUserProfileData `json:"profiles"` IM roomIMData `json:"im"` RTC roomRTCData `json:"rtc"` ServerTimeMS int64 `json:"server_time_ms"` } type roomDetailData struct { Room roomInitialData `json:"room"` Viewer roomViewerData `json:"viewer"` Seats []roomSeatData `json:"seats"` RankTop []roomRankItemData `json:"rank_top"` OnlineCount int32 `json:"online_count"` Permissions roomPermissionsData `json:"permissions"` Profiles []roomDisplayProfileData `json:"profiles,omitempty"` IM roomIMData `json:"im"` RTC roomRTCData `json:"rtc"` ServerTimeMS int64 `json:"server_time_ms"` } type roomOnlineUsersData struct { Items []roomOnlineUserData `json:"items"` Total int64 `json:"total"` Page int32 `json:"page"` PageSize int32 `json:"page_size"` ServerTimeMS int64 `json:"server_time_ms"` } type roomOnlineUserData struct { UserID string `json:"user_id"` Role string `json:"role,omitempty"` LastSeenAtMS int64 `json:"last_seen_at_ms,omitempty"` Profile *roomDisplayProfileData `json:"profile,omitempty"` } type roomPermissionsData struct { IsOwner bool `json:"is_owner"` IsHost bool `json:"is_host"` IsAdmin bool `json:"is_admin"` IsMuted bool `json:"is_muted"` CanSpeak bool `json:"can_speak"` CanSendGift bool `json:"can_send_gift"` CanMicUp bool `json:"can_mic_up"` CanManageRoom bool `json:"can_manage_room"` CanLeaveRoom bool `json:"can_leave_room"` CanCloseRoom bool `json:"can_close_room"` CanMuteMic bool `json:"can_mute_mic"` CanOpenGiftBox bool `json:"can_open_gift_box"` } type roomDisplayProfileData struct { UserID string `json:"user_id"` Username string `json:"username"` Avatar string `json:"avatar"` DisplayUserID string `json:"display_user_id"` VIP map[string]any `json:"vip"` Level map[string]any `json:"level"` Badges []map[string]any `json:"badges"` AvatarFrame map[string]any `json:"avatar_frame"` Vehicle map[string]any `json:"vehicle"` Charm int64 `json:"charm"` } type roomGiftPanelData struct { CoinBalance int64 `json:"coin_balance"` Recipients []roomGiftRecipientData `json:"recipients"` Tabs []roomGiftTabData `json:"tabs"` Gifts []giftConfigData `json:"gifts"` QuantityPresets []int32 `json:"quantity_presets"` } type roomGiftRecipientData struct { TargetType string `json:"target_type"` UserID string `json:"user_id,omitempty"` SeatNo int32 `json:"seat_no,omitempty"` Label string `json:"label,omitempty"` Profile *roomDisplayProfileData `json:"profile,omitempty"` } type roomGiftTabData struct { Key string `json:"key"` Order int32 `json:"order"` } type updateRoomProfileData struct { Result roomCommandResultData `json:"result"` Room roomInitialData `json:"room"` Seats []roomSeatData `json:"seats"` ServerTimeMS int64 `json:"server_time_ms"` } type roomSnapshotData struct { Room roomInitialData `json:"room"` Seats []roomSeatData `json:"seats"` ContributionRank []roomRankItemData `json:"contribution_rank"` ServerTimeMS int64 `json:"server_time_ms"` } type roomCommandResultData struct { Applied bool `json:"applied"` RoomVersion int64 `json:"room_version"` ServerTimeMS int64 `json:"server_time_ms"` } type roomInitialData struct { RoomID string `json:"room_id"` IMGroupID string `json:"im_group_id"` RoomShortID string `json:"room_short_id,omitempty"` Title string `json:"title,omitempty"` CoverURL string `json:"cover_url,omitempty"` Description string `json:"description,omitempty"` OwnerUserID string `json:"owner_user_id,omitempty"` HostUserID string `json:"host_user_id,omitempty"` Mode string `json:"mode,omitempty"` Status string `json:"status,omitempty"` ChatEnabled bool `json:"chat_enabled"` Heat int64 `json:"heat"` OnlineCount int32 `json:"online_count"` SeatCount int32 `json:"seat_count"` OccupiedSeatCount int32 `json:"occupied_seat_count"` VisibleRegionID int64 `json:"visible_region_id,omitempty"` Version int64 `json:"version"` } type roomViewerData struct { UserID string `json:"user_id"` Role string `json:"role,omitempty"` JoinedAtMS int64 `json:"joined_at_ms,omitempty"` LastSeenAtMS int64 `json:"last_seen_at_ms,omitempty"` } type roomSeatData struct { SeatNo int32 `json:"seat_no"` UserID string `json:"user_id,omitempty"` Locked bool `json:"locked"` PublishState string `json:"publish_state,omitempty"` MicSessionID string `json:"mic_session_id,omitempty"` PublishDeadlineMS int64 `json:"publish_deadline_ms,omitempty"` MicMuted bool `json:"mic_muted"` } type roomRankItemData struct { UserID string `json:"user_id"` Score int64 `json:"score"` GiftValue int64 `json:"gift_value"` UpdatedAtMS int64 `json:"updated_at_ms,omitempty"` } type roomUserProfileData struct { UserID string `json:"user_id"` DisplayUserID string `json:"display_user_id,omitempty"` Username string `json:"username,omitempty"` Avatar string `json:"avatar,omitempty"` } type roomIMData struct { GroupID string `json:"group_id"` NeedJoinGroup bool `json:"need_join_group"` } type roomRTCData struct { NeedToken bool `json:"need_token"` Available bool `json:"available"` Token *tencentrtc.TokenResult `json:"token,omitempty"` Reason string `json:"reason,omitempty"` } func roomListDataFromProto(resp *roomv1.ListRoomsResponse) roomListData { if resp == nil { return roomListData{} } items := make([]roomListItemData, 0, len(resp.GetRooms())) for _, room := range resp.GetRooms() { items = append(items, roomListItemDataFromProto(room)) } return roomListData{Rooms: items, NextCursor: resp.GetNextCursor()} } func myRoomDataFromProto(resp *roomv1.GetMyRoomResponse) myRoomData { if resp == nil { return myRoomData{} } data := myRoomData{HasRoom: resp.GetHasRoom(), ServerTimeMS: resp.GetServerTimeMs()} if resp.GetHasRoom() && resp.GetRoom() != nil { room := roomListItemDataFromProto(resp.GetRoom()) data.Room = &room } return data } func roomListItemDataFromProto(room *roomv1.RoomListItem) roomListItemData { if room == nil { return roomListItemData{} } roomID := room.GetRoomId() return roomListItemData{ RoomID: roomID, IMGroupID: roomIMGroupID(roomID), OwnerUserID: formatOptionalUserID(room.GetOwnerUserId()), HostUserID: formatOptionalUserID(room.GetHostUserId()), Title: room.GetTitle(), CoverURL: room.GetCoverUrl(), Mode: room.GetMode(), Status: room.GetStatus(), Heat: room.GetHeat(), OnlineCount: room.GetOnlineCount(), SeatCount: room.GetSeatCount(), OccupiedSeatCount: room.GetOccupiedSeatCount(), VisibleRegionID: room.GetVisibleRegionId(), AppCode: room.GetAppCode(), RoomShortID: room.GetRoomShortId(), } } func commandResultDataFromProto(result *roomv1.CommandResult) roomCommandResultData { if result == nil { return roomCommandResultData{} } return roomCommandResultData{ Applied: result.GetApplied(), RoomVersion: result.GetRoomVersion(), ServerTimeMS: result.GetServerTimeMs(), } } func updateRoomProfileDataFromProto(resp *roomv1.UpdateRoomProfileResponse) updateRoomProfileData { if resp == nil { return updateRoomProfileData{} } snapshot := resp.GetRoom() return updateRoomProfileData{ Result: commandResultDataFromProto(resp.GetResult()), Room: roomInitialRoomDataFromSnapshot(snapshot, snapshot.GetRoomId()), Seats: roomSeatDataFromSnapshot(snapshot), ServerTimeMS: resp.GetResult().GetServerTimeMs(), } } func roomSnapshotDataFromProto(resp *roomv1.GetRoomSnapshotResponse) roomSnapshotData { if resp == nil { return roomSnapshotData{} } snapshot := resp.GetRoom() return roomSnapshotData{ Room: roomInitialRoomDataFromSnapshot(snapshot, snapshot.GetRoomId()), Seats: roomSeatDataFromSnapshot(snapshot), ContributionRank: roomRankDataFromSnapshot(snapshot, 0), ServerTimeMS: resp.GetServerTimeMs(), } } func roomInitialRoomDataFromSnapshot(snapshot *roomv1.RoomSnapshot, roomID string) roomInitialData { if snapshot == nil { return roomInitialData{RoomID: roomID, IMGroupID: roomIMGroupID(roomID)} } ext := snapshot.GetRoomExt() if roomID == "" { roomID = snapshot.GetRoomId() } roomShortID := snapshot.GetRoomShortId() if roomShortID == "" { roomShortID = ext["room_short_id"] } seatCount, occupiedSeatCount := roomSeatCounts(snapshot) return roomInitialData{ RoomID: roomID, IMGroupID: roomIMGroupID(roomID), RoomShortID: roomShortID, Title: ext["title"], CoverURL: ext["cover_url"], Description: ext["description"], OwnerUserID: formatOptionalUserID(snapshot.GetOwnerUserId()), HostUserID: formatOptionalUserID(snapshot.GetHostUserId()), Mode: snapshot.GetMode(), Status: snapshot.GetStatus(), ChatEnabled: snapshot.GetChatEnabled(), Heat: snapshot.GetHeat(), OnlineCount: int32(len(snapshot.GetOnlineUsers())), SeatCount: seatCount, OccupiedSeatCount: occupiedSeatCount, VisibleRegionID: snapshot.GetVisibleRegionId(), Version: snapshot.GetVersion(), } } func roomViewerDataFromProto(user *roomv1.RoomUser, fallbackUserID int64) roomViewerData { userID := fallbackUserID if user.GetUserId() > 0 { userID = user.GetUserId() } return roomViewerData{ UserID: formatOptionalUserID(userID), Role: user.GetRole(), JoinedAtMS: user.GetJoinedAtMs(), LastSeenAtMS: user.GetLastSeenAtMs(), } } func roomSeatDataFromSnapshot(snapshot *roomv1.RoomSnapshot) []roomSeatData { seats := snapshot.GetMicSeats() items := make([]roomSeatData, 0, len(seats)) for _, seat := range seats { items = append(items, roomSeatData{ SeatNo: seat.GetSeatNo(), UserID: formatOptionalUserID(seat.GetUserId()), Locked: seat.GetLocked(), PublishState: seat.GetPublishState(), MicSessionID: seat.GetMicSessionId(), PublishDeadlineMS: seat.GetPublishDeadlineMs(), MicMuted: seat.GetMicMuted(), }) } return items } func roomDetailDataFromSnapshot(resp *roomv1.GetRoomSnapshotResponse, viewerUserID int64, rtc roomRTCData, profiles []roomDisplayProfileData) roomDetailData { if resp == nil { return roomDetailData{} } snapshot := resp.GetRoom() roomID := snapshot.GetRoomId() viewer := protoRoomUserByID(snapshot, viewerUserID) return roomDetailData{ Room: roomInitialRoomDataFromSnapshot(snapshot, roomID), Viewer: roomViewerDataFromProto(viewer, viewerUserID), Seats: roomSeatDataFromSnapshot(snapshot), RankTop: roomRankDataFromSnapshot(snapshot, joinRoomContributionRankLimit), OnlineCount: int32(len(snapshot.GetOnlineUsers())), Permissions: roomPermissionsFromSnapshot(snapshot, viewerUserID), Profiles: profiles, IM: roomIMData{GroupID: roomIMGroupID(roomID), NeedJoinGroup: true}, RTC: rtc, ServerTimeMS: resp.GetServerTimeMs(), } } func roomPermissionsFromSnapshot(snapshot *roomv1.RoomSnapshot, viewerUserID int64) roomPermissionsData { if snapshot == nil || viewerUserID <= 0 { return roomPermissionsData{} } isOwner := snapshot.GetOwnerUserId() == viewerUserID isHost := snapshot.GetHostUserId() == viewerUserID isAdmin := containsInt64(snapshot.GetAdminUserIds(), viewerUserID) isMuted := containsInt64(snapshot.GetMuteUserIds(), viewerUserID) inRoom := protoRoomUserByID(snapshot, viewerUserID) != nil return roomPermissionsData{ IsOwner: isOwner, IsHost: isHost, IsAdmin: isAdmin, IsMuted: isMuted, CanSpeak: inRoom && snapshot.GetChatEnabled() && !isMuted, CanSendGift: inRoom && snapshot.GetStatus() == "active", CanMicUp: inRoom && snapshot.GetStatus() == "active" && protoSeatByUserID(snapshot, viewerUserID) == nil, CanManageRoom: inRoom && (isOwner || isHost || isAdmin), CanLeaveRoom: inRoom, CanCloseRoom: inRoom, CanMuteMic: inRoom && protoSeatByUserID(snapshot, viewerUserID) != nil, CanOpenGiftBox: inRoom && snapshot.GetStatus() == "active", } } func roomRankDataFromSnapshot(snapshot *roomv1.RoomSnapshot, limit int) []roomRankItemData { rank := snapshot.GetGiftRank() if limit > 0 && len(rank) > limit { rank = rank[:limit] } items := make([]roomRankItemData, 0, len(rank)) for _, item := range rank { items = append(items, roomRankItemData{ UserID: formatOptionalUserID(item.GetUserId()), Score: item.GetScore(), GiftValue: item.GetGiftValue(), UpdatedAtMS: item.GetUpdatedAtMs(), }) } return items } func roomInitialProfileUserIDs(snapshot *roomv1.RoomSnapshot, viewerUserID int64, rankLimit int) []int64 { seen := map[int64]bool{} userIDs := make([]int64, 0, 16) add := func(userID int64) { if userID <= 0 || seen[userID] { return } seen[userID] = true userIDs = append(userIDs, userID) } add(snapshot.GetOwnerUserId()) add(snapshot.GetHostUserId()) add(viewerUserID) for _, seat := range snapshot.GetMicSeats() { add(seat.GetUserId()) } rank := snapshot.GetGiftRank() if rankLimit > 0 && len(rank) > rankLimit { rank = rank[:rankLimit] } for _, item := range rank { add(item.GetUserId()) } return userIDs } func roomOnlineUserDataFromProto(resp *roomv1.ListRoomOnlineUsersResponse, profiles map[int64]roomDisplayProfileData) roomOnlineUsersData { if resp == nil { return roomOnlineUsersData{} } items := make([]roomOnlineUserData, 0, len(resp.GetUsers())) for _, user := range resp.GetUsers() { item := roomOnlineUserData{ UserID: formatOptionalUserID(user.GetUserId()), Role: user.GetRole(), LastSeenAtMS: user.GetLastSeenAtMs(), } if profile, ok := profiles[user.GetUserId()]; ok { copied := profile item.Profile = &copied } items = append(items, item) } return roomOnlineUsersData{ Items: items, Total: resp.GetTotal(), Page: resp.GetPage(), PageSize: resp.GetPageSize(), ServerTimeMS: resp.GetServerTimeMs(), } } func protoRoomUserByID(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.RoomUser { if snapshot == nil || userID <= 0 { return nil } for _, user := range snapshot.GetOnlineUsers() { if user.GetUserId() == userID { return user } } return nil } func protoSeatByUserID(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.SeatState { if snapshot == nil || userID <= 0 { return nil } for _, seat := range snapshot.GetMicSeats() { if seat.GetUserId() == userID { return seat } } return nil } func containsInt64(values []int64, target int64) bool { for _, value := range values { if value == target { return true } } return false } func roomSeatCounts(snapshot *roomv1.RoomSnapshot) (int32, int32) { seatCount := int32(len(snapshot.GetMicSeats())) occupiedSeatCount := int32(0) for _, seat := range snapshot.GetMicSeats() { if seat.GetUserId() > 0 { occupiedSeatCount++ } } return seatCount, occupiedSeatCount } func roomIMGroupID(roomID string) string { // 当前房间群规则固定为腾讯云 IM GroupID 等于内部 room_id;显式下发给客户端,避免端侧硬编码规则。 return roomID } func formatOptionalUserID(userID int64) string { if userID <= 0 { return "" } return strconv.FormatInt(userID, 10) }