2026-06-08 21:27:08 +08:00

1105 lines
40 KiB
Go

package roomapi
import (
"strconv"
"hyapp/pkg/imgroup"
"hyapp/pkg/tencentrtc"
roomv1 "hyapp.local/api/proto/room/v1"
walletv1 "hyapp.local/api/proto/wallet/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"`
ResourceGroup *roomRocketResourceGroupData `json:"resource_group,omitempty"`
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"`
ResourceGroup *roomRocketResourceGroupData `json:"resource_group,omitempty"`
DisplayName string `json:"display_name,omitempty"`
IconURL string `json:"icon_url,omitempty"`
GrantID string `json:"grant_id,omitempty"`
Status string `json:"status,omitempty"`
}
type roomRocketResourceData struct {
ResourceID int64 `json:"resource_id"`
ResourceCode string `json:"resource_code"`
ResourceType string `json:"resource_type"`
Name string `json:"name"`
Status string `json:"status"`
Grantable bool `json:"grantable"`
GrantStrategy string `json:"grant_strategy"`
WalletAssetType string `json:"wallet_asset_type,omitempty"`
WalletAssetAmount int64 `json:"wallet_asset_amount,omitempty"`
UsageScopes []string `json:"usage_scopes"`
AssetURL string `json:"asset_url"`
PreviewURL string `json:"preview_url"`
AnimationURL string `json:"animation_url"`
MetadataJSON string `json:"metadata_json"`
SortOrder int32 `json:"sort_order"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type roomRocketResourceGroupItemData struct {
GroupItemID int64 `json:"group_item_id"`
ItemType string `json:"item_type"`
ResourceID int64 `json:"resource_id"`
Resource *roomRocketResourceData `json:"resource,omitempty"`
WalletAssetType string `json:"wallet_asset_type,omitempty"`
WalletAssetAmount int64 `json:"wallet_asset_amount,omitempty"`
Quantity int64 `json:"quantity"`
DurationMS int64 `json:"duration_ms"`
SortOrder int32 `json:"sort_order"`
}
type roomRocketResourceGroupData struct {
GroupID int64 `json:"group_id"`
GroupCode string `json:"group_code"`
Name string `json:"name"`
Status string `json:"status"`
Description string `json:"description"`
SortOrder int32 `json:"sort_order"`
Items []roomRocketResourceGroupItemData `json:"items"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
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"`
GiftValue int64 `json:"gift_value"`
}
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, resourceGroups map[int64]*roomRocketResourceGroupData) roomRocketData {
if resp == nil || resp.GetRocket() == nil {
return roomRocketData{}
}
rocket := resp.GetRocket()
return roomRocketData{
Enabled: rocket.GetEnabled(),
Levels: roomRocketLevelsFromProto(rocket.GetLevels(), resourceGroups),
State: roomRocketStateFromProto(rocket.GetState(), resourceGroups),
BroadcastScope: rocket.GetBroadcastScope(),
LaunchDelayMS: rocket.GetLaunchDelayMs(),
BroadcastDelayMS: rocket.GetBroadcastDelayMs(),
RewardStackPolicy: rocket.GetRewardStackPolicy(),
ServerTimeMS: resp.GetServerTimeMs(),
}
}
func roomRocketLevelsFromProto(levels []*roomv1.RoomRocketLevel, resourceGroups map[int64]*roomRocketResourceGroupData) []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(), resourceGroups),
Top1Rewards: roomRocketRewardsFromProto(level.GetTop1Rewards(), resourceGroups),
IgniterRewards: roomRocketRewardsFromProto(level.GetIgniterRewards(), resourceGroups),
})
}
return out
}
func roomRocketRewardsFromProto(rewards []*roomv1.RoomRocketRewardItem, resourceGroups map[int64]*roomRocketResourceGroupData) []roomRocketRewardData {
out := make([]roomRocketRewardData, 0, len(rewards))
for _, reward := range rewards {
out = append(out, roomRocketRewardData{
RewardItemID: reward.GetRewardItemId(),
ResourceGroupID: reward.GetResourceGroupId(),
ResourceGroup: resourceGroups[reward.GetResourceGroupId()],
Weight: reward.GetWeight(),
DisplayName: reward.GetDisplayName(),
IconURL: reward.GetIconUrl(),
})
}
return out
}
func roomRocketStateFromProto(state *roomv1.RoomRocketState, resourceGroups map[int64]*roomRocketResourceGroupData) 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(), resourceGroups),
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, resourceGroups map[int64]*roomRocketResourceGroupData) []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(),
ResourceGroup: resourceGroups[reward.GetResourceGroupId()],
DisplayName: reward.GetDisplayName(),
IconURL: reward.GetIconUrl(),
GrantID: reward.GetGrantId(),
Status: reward.GetStatus(),
})
}
return out
}
func roomRocketResourceGroupIDs(resp *roomv1.GetRoomRocketResponse) []int64 {
if resp == nil || resp.GetRocket() == nil {
return nil
}
seen := map[int64]struct{}{}
ids := make([]int64, 0)
add := func(id int64) {
if id <= 0 {
return
}
if _, exists := seen[id]; exists {
return
}
seen[id] = struct{}{}
ids = append(ids, id)
}
for _, level := range resp.GetRocket().GetLevels() {
for _, reward := range level.GetInRoomRewards() {
add(reward.GetResourceGroupId())
}
for _, reward := range level.GetTop1Rewards() {
add(reward.GetResourceGroupId())
}
for _, reward := range level.GetIgniterRewards() {
add(reward.GetResourceGroupId())
}
}
for _, reward := range resp.GetRocket().GetState().GetLastRewards() {
add(reward.GetResourceGroupId())
}
return ids
}
func roomRocketResourceGroupFromProto(group *walletv1.ResourceGroup) *roomRocketResourceGroupData {
if group == nil {
return nil
}
items := make([]roomRocketResourceGroupItemData, 0, len(group.GetItems()))
for _, item := range group.GetItems() {
items = append(items, roomRocketResourceGroupItemData{
GroupItemID: item.GetGroupItemId(),
ItemType: item.GetItemType(),
ResourceID: item.GetResourceId(),
Resource: roomRocketResourceFromProto(item.GetResource()),
WalletAssetType: item.GetWalletAssetType(),
WalletAssetAmount: item.GetWalletAssetAmount(),
Quantity: item.GetQuantity(),
DurationMS: item.GetDurationMs(),
SortOrder: item.GetSortOrder(),
})
}
return &roomRocketResourceGroupData{
GroupID: group.GetGroupId(),
GroupCode: group.GetGroupCode(),
Name: group.GetName(),
Status: group.GetStatus(),
Description: group.GetDescription(),
SortOrder: group.GetSortOrder(),
Items: items,
CreatedAtMS: group.GetCreatedAtMs(),
UpdatedAtMS: group.GetUpdatedAtMs(),
}
}
func roomRocketResourceFromProto(item *walletv1.Resource) *roomRocketResourceData {
if item == nil {
return nil
}
return &roomRocketResourceData{
ResourceID: item.GetResourceId(),
ResourceCode: item.GetResourceCode(),
ResourceType: item.GetResourceType(),
Name: item.GetName(),
Status: item.GetStatus(),
Grantable: item.GetGrantable(),
GrantStrategy: item.GetGrantStrategy(),
WalletAssetType: item.GetWalletAssetType(),
WalletAssetAmount: item.GetWalletAssetAmount(),
UsageScopes: item.GetUsageScopes(),
AssetURL: item.GetAssetUrl(),
PreviewURL: item.GetPreviewUrl(),
AnimationURL: item.GetAnimationUrl(),
MetadataJSON: item.GetMetadataJson(),
SortOrder: item.GetSortOrder(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
}
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(),
GiftValue: seat.GetGiftValue(),
})
}
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 {
// 房间业务 ID 保持原始值;腾讯 IM GroupID 在协议边界转义,避免 UUID 连字符被腾讯 REST 拒绝。
groupID, err := imgroup.RoomGroupID(roomID)
if err != nil {
return roomID
}
return groupID
}
func formatOptionalUserID(userID int64) string {
if userID <= 0 {
return ""
}
return strconv.FormatInt(userID, 10)
}