package roomapi 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"` Countries []roomListCountryData `json:"countries,omitempty"` } type roomListCountryData struct { CountryID int64 `json:"country_id,omitempty"` CountryCode string `json:"country_code"` CountryName string `json:"country_name,omitempty"` CountryDisplayName string `json:"country_display_name,omitempty"` CountryFlag string `json:"country_flag,omitempty"` SortOrder int32 `json:"sort_order,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"` Title string `json:"title,omitempty"` CoverURL string `json:"cover_url,omitempty"` Mode string `json:"mode,omitempty"` Status string `json:"status,omitempty"` Locked bool `json:"locked"` 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"` CountryCode string `json:"country_code,omitempty"` CountryFlag string `json:"country_flag,omitempty"` } type createRoomData struct { Result roomCommandResultData `json:"result"` Room roomInitialData `json:"room"` Seats []roomSeatData `json:"seats"` IM roomIMData `json:"im"` ServerTimeMS int64 `json:"server_time_ms"` } 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 []roomDisplayProfileData `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"` IsFollowed bool `json:"is_followed"` 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"` IsOwner bool `json:"is_owner"` RoomRole string `json:"room_role,omitempty"` GiftValue int64 `json:"gift_value"` JoinedAtMS int64 `json:"joined_at_ms,omitempty"` LastSeenAtMS int64 `json:"last_seen_at_ms,omitempty"` Profile *roomDisplayProfileData `json:"profile,omitempty"` } type roomBannedUsersData struct { RoomID string `json:"room_id"` Items []roomBannedUserData `json:"items"` Total int64 `json:"total"` Page int32 `json:"page"` PageSize int32 `json:"page_size"` ServerTimeMS int64 `json:"server_time_ms"` } type roomBannedUserData struct { UserID string `json:"user_id"` DisplayUserID string `json:"display_user_id"` Username string `json:"username"` Avatar string `json:"avatar"` Country string `json:"country"` CountryName string `json:"country_name"` CountryDisplayName string `json:"country_display_name"` CountryFlag string `json:"country_flag"` ActorUserID string `json:"actor_user_id"` CreatedAtMS int64 `json:"created_at_ms"` UnbanAtMS int64 `json:"unban_at_ms"` RemainingMS int64 `json:"remaining_ms"` Permanent bool `json:"permanent"` } type setRoomAdminData struct { Result roomCommandResultData `json:"result"` TargetUserID string `json:"target_user_id"` RoomRole string `json:"room_role"` ServerTimeMS int64 `json:"server_time_ms"` } type roomPermissionsData struct { IsOwner bool `json:"is_owner"` 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"` Gender string `json:"gender"` Age int32 `json:"age"` Country string `json:"country"` CountryName string `json:"country_name"` CountryDisplayName string `json:"country_display_name"` CountryFlag string `json:"country_flag"` VIP map[string]any `json:"vip"` Level map[string]any `json:"level"` LevelBadges map[string]any `json:"level_badges"` Badges []map[string]any `json:"badges"` AvatarFrame map[string]any `json:"avatar_frame"` ProfileCard map[string]any `json:"profile_card"` 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 roomRocketData struct { Enabled bool `json:"enabled"` Levels []roomRocketLevelData `json:"levels"` State roomRocketStateData `json:"state"` BroadcastScope string `json:"broadcast_scope"` LaunchDelayMS int64 `json:"launch_delay_ms"` BroadcastDelayMS int64 `json:"broadcast_delay_ms"` RewardStackPolicy string `json:"reward_stack_policy"` ServerTimeMS int64 `json:"server_time_ms"` } type roomRocketLevelData struct { Level int32 `json:"level"` FuelThreshold int64 `json:"fuel_threshold"` CoverURL string `json:"cover_url,omitempty"` AnimationURL string `json:"animation_url,omitempty"` LaunchAnimationURL string `json:"launch_animation_url,omitempty"` LaunchedImageURL string `json:"launched_image_url,omitempty"` InRoomRewards []roomRocketRewardData `json:"in_room_rewards"` Top1Rewards []roomRocketRewardData `json:"top1_rewards"` IgniterRewards []roomRocketRewardData `json:"igniter_rewards"` } type roomRocketRewardData struct { RewardItemID string `json:"reward_item_id"` ResourceGroupID int64 `json:"resource_group_id"` Weight int64 `json:"weight,omitempty"` DisplayName string `json:"display_name,omitempty"` IconURL string `json:"icon_url,omitempty"` } type roomRocketStateData struct { CurrentLevel int32 `json:"current_level"` CurrentFuel int64 `json:"current_fuel"` FuelThreshold int64 `json:"fuel_threshold"` Status string `json:"status"` IgnitedAtMS int64 `json:"ignited_at_ms,omitempty"` LaunchAtMS int64 `json:"launch_at_ms,omitempty"` LaunchedAtMS int64 `json:"launched_at_ms,omitempty"` ResetAtMS int64 `json:"reset_at_ms"` Top1UserID string `json:"top1_user_id,omitempty"` IgniterUserID string `json:"igniter_user_id,omitempty"` RocketID string `json:"rocket_id,omitempty"` ConfigVersion int64 `json:"config_version,omitempty"` LastRewards []roomRocketRewardGrantData `json:"last_rewards,omitempty"` PendingLaunches []roomRocketPendingLaunchData `json:"pending_launches,omitempty"` } type roomRocketPendingLaunchData struct { RocketID string `json:"rocket_id"` Level int32 `json:"level"` FuelThreshold int64 `json:"fuel_threshold"` IgnitedAtMS int64 `json:"ignited_at_ms"` LaunchAtMS int64 `json:"launch_at_ms"` ResetAtMS int64 `json:"reset_at_ms"` Top1UserID string `json:"top1_user_id,omitempty"` IgniterUserID string `json:"igniter_user_id,omitempty"` ConfigVersion int64 `json:"config_version,omitempty"` CoverURL string `json:"cover_url,omitempty"` } type roomRocketRewardGrantData struct { RewardRole string `json:"reward_role"` UserID string `json:"user_id"` RewardItemID string `json:"reward_item_id"` ResourceGroupID int64 `json:"resource_group_id"` DisplayName string `json:"display_name,omitempty"` IconURL string `json:"icon_url,omitempty"` GrantID string `json:"grant_id,omitempty"` Status string `json:"status,omitempty"` } 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"` GiftTypeCode string `json:"gift_type_code,omitempty"` Label string `json:"label,omitempty"` 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 roomBackgroundData struct { BackgroundID int64 `json:"background_id"` RoomID string `json:"room_id"` ImageURL string `json:"image_url"` CreatedByUserID string `json:"created_by_user_id,omitempty"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` } type saveRoomBackgroundData struct { Background roomBackgroundData `json:"background"` ServerTimeMS int64 `json:"server_time_ms"` } type listRoomBackgroundsData struct { Backgrounds []roomBackgroundData `json:"backgrounds"` SelectedBackgroundURL string `json:"selected_background_url,omitempty"` ServerTimeMS int64 `json:"server_time_ms"` } type setRoomBackgroundData struct { Result roomCommandResultData `json:"result"` Room roomInitialData `json:"room"` Background roomBackgroundData `json:"background"` ServerTimeMS int64 `json:"server_time_ms"` } type roomSnapshotData struct { Room roomInitialData `json:"room"` Seats []roomSeatData `json:"seats"` ContributionRank []roomRankItemData `json:"contribution_rank"` IsFollowed bool `json:"is_followed"` ServerTimeMS int64 `json:"server_time_ms"` } type roomFollowData struct { RoomID string `json:"room_id"` IsFollowed bool `json:"is_followed"` FollowedAtMS int64 `json:"followed_at_ms,omitempty"` 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"` BackgroundURL string `json:"background_url,omitempty"` Description string `json:"description,omitempty"` OwnerUserID string `json:"owner_user_id,omitempty"` Mode string `json:"mode,omitempty"` Status string `json:"status,omitempty"` ChatEnabled bool `json:"chat_enabled"` Locked bool `json:"locked"` 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"` SeatStatus string `json:"seat_status,omitempty"` MicHeartbeatAtMS int64 `json:"mic_heartbeat_at_ms,omitempty"` } 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 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 createRoomDataFromProto(resp *roomv1.CreateRoomResponse) createRoomData { if resp == nil { return createRoomData{} } snapshot := resp.GetRoom() roomID := snapshot.GetRoomId() return createRoomData{ Result: commandResultDataFromProto(resp.GetResult()), Room: roomInitialRoomDataFromSnapshot(snapshot, roomID), Seats: roomSeatDataFromSnapshot(snapshot), IM: roomIMData{GroupID: roomIMGroupID(roomID), NeedJoinGroup: true}, ServerTimeMS: resp.GetResult().GetServerTimeMs(), } } func roomRocketDataFromProto(resp *roomv1.GetRoomRocketResponse) roomRocketData { if resp == nil || resp.GetRocket() == nil { return roomRocketData{} } rocket := resp.GetRocket() return roomRocketData{ Enabled: rocket.GetEnabled(), Levels: roomRocketLevelsFromProto(rocket.GetLevels()), State: roomRocketStateFromProto(rocket.GetState()), BroadcastScope: rocket.GetBroadcastScope(), LaunchDelayMS: rocket.GetLaunchDelayMs(), BroadcastDelayMS: rocket.GetBroadcastDelayMs(), RewardStackPolicy: rocket.GetRewardStackPolicy(), ServerTimeMS: resp.GetServerTimeMs(), } } func roomRocketLevelsFromProto(levels []*roomv1.RoomRocketLevel) []roomRocketLevelData { out := make([]roomRocketLevelData, 0, len(levels)) for _, level := range levels { out = append(out, roomRocketLevelData{ Level: level.GetLevel(), FuelThreshold: level.GetFuelThreshold(), CoverURL: level.GetCoverUrl(), AnimationURL: level.GetAnimationUrl(), LaunchAnimationURL: level.GetLaunchAnimationUrl(), LaunchedImageURL: level.GetLaunchedImageUrl(), InRoomRewards: roomRocketRewardsFromProto(level.GetInRoomRewards()), Top1Rewards: roomRocketRewardsFromProto(level.GetTop1Rewards()), IgniterRewards: roomRocketRewardsFromProto(level.GetIgniterRewards()), }) } return out } func roomRocketRewardsFromProto(rewards []*roomv1.RoomRocketRewardItem) []roomRocketRewardData { out := make([]roomRocketRewardData, 0, len(rewards)) for _, reward := range rewards { out = append(out, roomRocketRewardData{ RewardItemID: reward.GetRewardItemId(), ResourceGroupID: reward.GetResourceGroupId(), Weight: reward.GetWeight(), DisplayName: reward.GetDisplayName(), IconURL: reward.GetIconUrl(), }) } return out } func roomRocketStateFromProto(state *roomv1.RoomRocketState) roomRocketStateData { if state == nil { return roomRocketStateData{} } return roomRocketStateData{ CurrentLevel: state.GetCurrentLevel(), CurrentFuel: state.GetCurrentFuel(), FuelThreshold: state.GetFuelThreshold(), Status: state.GetStatus(), IgnitedAtMS: state.GetIgnitedAtMs(), LaunchAtMS: state.GetLaunchAtMs(), LaunchedAtMS: state.GetLaunchedAtMs(), ResetAtMS: state.GetResetAtMs(), Top1UserID: formatOptionalUserID(state.GetTop1UserId()), IgniterUserID: formatOptionalUserID(state.GetIgniterUserId()), RocketID: state.GetRocketId(), ConfigVersion: state.GetConfigVersion(), LastRewards: roomRocketRewardGrantsFromProto(state.GetLastRewards()), PendingLaunches: roomRocketPendingLaunchesFromProto(state.GetPendingLaunches()), } } func roomRocketPendingLaunchesFromProto(launches []*roomv1.RoomRocketPendingLaunch) []roomRocketPendingLaunchData { out := make([]roomRocketPendingLaunchData, 0, len(launches)) for _, launch := range launches { out = append(out, roomRocketPendingLaunchData{ RocketID: launch.GetRocketId(), Level: launch.GetLevel(), FuelThreshold: launch.GetFuelThreshold(), IgnitedAtMS: launch.GetIgnitedAtMs(), LaunchAtMS: launch.GetLaunchAtMs(), ResetAtMS: launch.GetResetAtMs(), Top1UserID: formatOptionalUserID(launch.GetTop1UserId()), IgniterUserID: formatOptionalUserID(launch.GetIgniterUserId()), ConfigVersion: launch.GetConfigVersion(), CoverURL: launch.GetCoverUrl(), }) } return out } func roomRocketRewardGrantsFromProto(rewards []*roomv1.RoomRocketRewardGrant) []roomRocketRewardGrantData { out := make([]roomRocketRewardGrantData, 0, len(rewards)) for _, reward := range rewards { out = append(out, roomRocketRewardGrantData{ RewardRole: reward.GetRewardRole(), UserID: formatOptionalUserID(reward.GetUserId()), RewardItemID: reward.GetRewardItemId(), ResourceGroupID: reward.GetResourceGroupId(), DisplayName: reward.GetDisplayName(), IconURL: reward.GetIconUrl(), GrantID: reward.GetGrantId(), Status: reward.GetStatus(), }) } return out } 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()), Title: room.GetTitle(), CoverURL: room.GetCoverUrl(), Mode: room.GetMode(), Status: room.GetStatus(), Locked: room.GetLocked(), Heat: room.GetHeat(), OnlineCount: room.GetOnlineCount(), SeatCount: room.GetSeatCount(), OccupiedSeatCount: room.GetOccupiedSeatCount(), VisibleRegionID: room.GetVisibleRegionId(), AppCode: room.GetAppCode(), RoomShortID: room.GetRoomShortId(), CountryCode: room.GetCountryCode(), CountryFlag: roomProfileCountryFlag(room.GetCountryCode()), } } 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 saveRoomBackgroundDataFromProto(resp *roomv1.SaveRoomBackgroundResponse) saveRoomBackgroundData { if resp == nil { return saveRoomBackgroundData{} } return saveRoomBackgroundData{ Background: roomBackgroundDataFromProto(resp.GetBackground()), ServerTimeMS: resp.GetServerTimeMs(), } } func listRoomBackgroundsDataFromProto(resp *roomv1.ListRoomBackgroundsResponse) listRoomBackgroundsData { if resp == nil { return listRoomBackgroundsData{} } backgrounds := make([]roomBackgroundData, 0, len(resp.GetBackgrounds())) for _, background := range resp.GetBackgrounds() { backgrounds = append(backgrounds, roomBackgroundDataFromProto(background)) } return listRoomBackgroundsData{ Backgrounds: backgrounds, SelectedBackgroundURL: resp.GetSelectedBackgroundUrl(), ServerTimeMS: resp.GetServerTimeMs(), } } func setRoomBackgroundDataFromProto(resp *roomv1.SetRoomBackgroundResponse) setRoomBackgroundData { if resp == nil { return setRoomBackgroundData{} } snapshot := resp.GetRoom() return setRoomBackgroundData{ Result: commandResultDataFromProto(resp.GetResult()), Room: roomInitialRoomDataFromSnapshot(snapshot, snapshot.GetRoomId()), Background: roomBackgroundDataFromProto(resp.GetBackground()), ServerTimeMS: resp.GetResult().GetServerTimeMs(), } } func roomBackgroundDataFromProto(background *roomv1.RoomBackgroundImage) roomBackgroundData { if background == nil { return roomBackgroundData{} } return roomBackgroundData{ BackgroundID: background.GetBackgroundId(), RoomID: background.GetRoomId(), ImageURL: background.GetImageUrl(), CreatedByUserID: formatOptionalUserID(background.GetCreatedByUserId()), CreatedAtMS: background.GetCreatedAtMs(), UpdatedAtMS: background.GetUpdatedAtMs(), } } 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), IsFollowed: resp.GetIsFollowed(), 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"], BackgroundURL: ext["background_url"], Description: ext["description"], OwnerUserID: formatOptionalUserID(snapshot.GetOwnerUserId()), Mode: snapshot.GetMode(), Status: snapshot.GetStatus(), ChatEnabled: snapshot.GetChatEnabled(), Locked: snapshot.GetLocked(), 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(), SeatStatus: seat.GetSeatStatus(), MicHeartbeatAtMS: seat.GetMicHeartbeatAtMs(), }) } 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), IsFollowed: resp.GetIsFollowed(), 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 roomFollowDataFromProto(resp *roomv1.FollowRoomResponse) roomFollowData { if resp == nil { return roomFollowData{} } return roomFollowData{ RoomID: resp.GetRoomId(), IsFollowed: resp.GetFollowed(), FollowedAtMS: resp.GetFollowedAtMs(), ServerTimeMS: resp.GetServerTimeMs(), } } func roomUnfollowDataFromProto(resp *roomv1.UnfollowRoomResponse) roomFollowData { if resp == nil { return roomFollowData{} } return roomFollowData{ RoomID: resp.GetRoomId(), IsFollowed: resp.GetFollowed(), ServerTimeMS: resp.GetServerTimeMs(), } } func roomPermissionsFromSnapshot(snapshot *roomv1.RoomSnapshot, viewerUserID int64) roomPermissionsData { if snapshot == nil || viewerUserID <= 0 { return roomPermissionsData{} } isOwner := snapshot.GetOwnerUserId() == viewerUserID isAdmin := !isOwner && containsInt64(snapshot.GetAdminUserIds(), viewerUserID) isMuted := containsInt64(snapshot.GetMuteUserIds(), viewerUserID) inRoom := protoRoomUserByID(snapshot, viewerUserID) != nil return roomPermissionsData{ IsOwner: isOwner, 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 || 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(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{} } source := resp.GetItems() if len(source) == 0 { source = roomOnlineUsersFromLegacyUsers(resp.GetUsers()) } items := make([]roomOnlineUserData, 0, len(source)) for _, user := range source { item := roomOnlineUserData{ UserID: formatOptionalUserID(user.GetUserId()), Role: user.GetRole(), IsOwner: user.GetIsOwner(), RoomRole: user.GetRoomRole(), GiftValue: user.GetGiftValue(), JoinedAtMS: user.GetJoinedAtMs(), 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 roomBannedUsersDataFromProto(roomID string, resp *roomv1.ListRoomBannedUsersResponse, profiles map[int64]roomDisplayProfileData) roomBannedUsersData { if resp == nil { return roomBannedUsersData{RoomID: roomID} } items := make([]roomBannedUserData, 0, len(resp.GetItems())) for _, user := range resp.GetItems() { item := roomBannedUserData{ UserID: formatOptionalUserID(user.GetUserId()), ActorUserID: formatOptionalUserID(user.GetActorUserId()), CreatedAtMS: user.GetCreatedAtMs(), UnbanAtMS: user.GetUnbanAtMs(), RemainingMS: user.GetRemainingMs(), Permanent: user.GetPermanent(), } if profile, ok := profiles[user.GetUserId()]; ok { item.DisplayUserID = profile.DisplayUserID item.Username = profile.Username item.Avatar = profile.Avatar item.Country = profile.Country item.CountryName = profile.CountryName item.CountryDisplayName = profile.CountryDisplayName item.CountryFlag = profile.CountryFlag } items = append(items, item) } return roomBannedUsersData{ RoomID: roomID, Items: items, Total: resp.GetTotal(), Page: resp.GetPage(), PageSize: resp.GetPageSize(), ServerTimeMS: resp.GetServerTimeMs(), } } func roomOnlineUsersFromLegacyUsers(users []*roomv1.RoomUser) []*roomv1.RoomOnlineUser { items := make([]*roomv1.RoomOnlineUser, 0, len(users)) for _, user := range users { items = append(items, &roomv1.RoomOnlineUser{ UserId: user.GetUserId(), Role: user.GetRole(), JoinedAtMs: user.GetJoinedAtMs(), LastSeenAtMs: user.GetLastSeenAtMs(), RoomRole: "normal", }) } return items } 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) }