package voicechat import ( "bytes" "context" "encoding/base64" "encoding/json" "fmt" "net/http" "sync/atomic" "time" "unsafe" "repositories.action2quare.com/ayo/gocommon/logger" ) type eosauth struct { AccessToken string `json:"access_token"` ExpiresAt time.Time `json:"expires_at"` ExpiresIn int64 `json:"expires_in"` DeploymentId string `json:"deployment_id"` ProductId string `json:"product_id"` SandboxId string `json:"sandbox_id"` TokenType string `json:"token_type"` } func eosTokenRefresh(target *unsafe.Pointer, ctx context.Context) { defer func() { r := recover() if r != nil { logger.Error(r) } }() endpoint := "https://api.epicgames.dev/auth/v1/oauth/token" auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", config.EosClientId, config.EosClientSecret))) for { req, _ := http.NewRequest("POST", endpoint, bytes.NewBufferString("grant_type=client_credentials&deployment_id="+config.EosDeploymentId)) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Basic %s", auth)) resp, err := http.DefaultClient.Do(req) if err != nil { logger.Println("eosTokenRefresh failed. eos token reqeust err :", err) time.Sleep(time.Minute) continue } var neweos eosauth err = json.NewDecoder(resp.Body).Decode(&neweos) resp.Body.Close() if err != nil { logger.Println("eosTokenRefresh failed. decode err :", err) return } logger.Printf("eos access_token retreived : %s...", neweos.AccessToken[:20]) atomic.StorePointer(target, unsafe.Pointer(&neweos)) select { case <-ctx.Done(): return case <-time.After(time.Duration(neweos.ExpiresIn-60) * time.Second): } } } type eosRoomParticipantRequests struct { Puid string `json:"puid"` ClientIP string `json:"clientIP"` HardMuted bool `json:"hardMuted"` } type eosRoomParticipants struct { Participants []eosRoomParticipantRequests `json:"participants"` } func (gv *eosauth) joinVoiceChat(data JoinVoiceChatRequst) map[string]any { // https://dev.epicgames.com/docs/web-api-ref/voice-web-api accessToken := gv.AccessToken if len(accessToken) == 0 { logger.Println("eos voice chat is not ready. access_token is empty") return nil } voiceendpoint := fmt.Sprintf("https://api.epicgames.dev/rtc/v1/%s/room/%s", config.EosDeploymentId, data.Gid) participants := eosRoomParticipants{ Participants: []eosRoomParticipantRequests{ {Puid: data.Alias}, }, } body, _ := json.Marshal(participants) req, _ := http.NewRequest("POST", voiceendpoint, bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) resp, err := http.DefaultClient.Do(req) if err != nil { logger.Println("join voice room failed. api.epicgames.dev return err :", err) return nil } var result map[string]any json.NewDecoder(resp.Body).Decode(&result) resp.Body.Close() result["client_id"] = config.EosClientId result["client_secret"] = config.EosClientSecret par := result["participants"].([]any)[0] participant := par.(map[string]any) channelCredentials := map[string]any{ "override_userid": data.Alias, "client_base_url": result["clientBaseUrl"], "participant_token": participant["token"], } marshaled, _ := json.Marshal(channelCredentials) result["channel_credentials"] = base64.StdEncoding.EncodeToString(marshaled) return result } func (gv *eosauth) leaveVoiceChat(data LeaveVoiceChatRequst) { voiceendpoint := fmt.Sprintf("https://api.epicgames.dev/rtc/v1/%s/room/%s/participants/%s", config.EosDeploymentId, data.Gid, data.Alias) accessToken := gv.AccessToken req, _ := http.NewRequest("DELETE", voiceendpoint, nil) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) resp, err := http.DefaultClient.Do(req) if err != nil { logger.Println("LeaveVoiceChat failed. err :", err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { // 204가 정상 응답 logger.Println("LeaveVoiceChat failed. status code :", resp.StatusCode) } }