2026-05-12 19:34:47 +08:00

533 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}