Files
trufflehog/pkg/analyzer/analyzers/elevenlabs/requests.go
Kashif KhanandEng Zer Jun f7ff9980e3
Lint / golangci-lint (push) Waiting to run
Lint / semgrep (push) Waiting to run
Release / Release (push) Waiting to run
Scan for secrets / test (push) Waiting to run
Test / test (push) Waiting to run
Test / test-community (push) Waiting to run
elevenlabs analyzer (#3850)
* added analyzer initial working structure

* restructured the resources

* added history read api

* added history delete api

* fixed http method issue

* added delete dubbing api and refactored the code

* added dubbing read api

* optimized requests and added projects api

* added pronunciation dictionaries apis

* fixed linter

* added models,audionative,workspace apis

* added some last apis

* Deleted \

* fixed linter

* few more enhancements and test cases added

* added go generate command

* optimized the code and resolved the comments

* fixed linter

* fixed indentation

* resolved comments

* kicked imposter print statement

* formatted cli.go

* Update pkg/analyzer/analyzers/elevenlabs/elevenlabs.go

Co-authored-by: Eng Zer Jun <[email protected]>

* resolved comments

* added secret info methods which safely append and read permission and resources

---------

Co-authored-by: Eng Zer Jun <[email protected]>
2025-02-25 07:59:56 -08:00

783 lines
27 KiB
Go

package elevenlabs
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"slices"
"strings"
)
// permissionToAPIMap contain the API endpoints for each scope/permission
// api docs: https://elevenlabs.io/docs/api-reference/introduction
var permissionToAPIMap = map[Permission]string{
TextToSpeech: "https://api.elevenlabs.io/v1/text-to-speech/%s", // require voice id
SpeechToSpeech: "https://api.elevenlabs.io/v1/speech-to-speech/%s", // require voice id
AudioIsolation: "https://api.elevenlabs.io/v1/audio-isolation",
DubbingRead: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id
DubbingWrite: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id
ProjectsRead: "https://api.elevenlabs.io/v1/projects",
ProjectsWrite: "https://api.elevenlabs.io/v1/projects/%s", // require project id
AudioNativeWrite: "https://api.elevenlabs.io/v1/audio-native/%s/content", // require project id
PronunciationDictionariesRead: "https://api.elevenlabs.io/v1/pronunciation-dictionaries",
PronunciationDictionariesWrite: "https://api.elevenlabs.io/v1/pronunciation-dictionaries/%s/remove-rules", // require pronunciation dictionary id
VoicesRead: "https://api.elevenlabs.io/v1/voices",
VoicesWrite: "https://api.elevenlabs.io/v1/voices/%s", // require voice id
ModelsRead: "https://api.elevenlabs.io/v1/models",
SpeechHistoryRead: "https://api.elevenlabs.io/v1/history",
SpeechHistoryWrite: "https://api.elevenlabs.io/v1/history/%s", // require history item id
UserRead: "https://api.elevenlabs.io/v1/user",
WorkspaceWrite: "https://api.elevenlabs.io/v1/workspace/invites",
}
var (
// not exist key
fakeID = "_thou_shalt_not_exist_"
// error statuses
NotVerifiable = "api_key_not_verifiable"
InvalidAPIKey = "invalid_api_key"
MissingPermissions = "missing_permissions"
DubbingNotFound = "dubbing_not_found"
ProjectNotFound = "project_not_found"
VoiceDoesNotExist = "voice_does_not_exist"
InvalidSubscription = "invalid_subscription"
PronunciationDictionaryNotFound = "pronunciation_dictionary_not_found"
InternalServerError = "internal_server_error"
InvalidProjectID = "invalid_project_id"
ModelNotFound = "model_not_found"
VoiceNotFound = "voice_not_found"
InvalidContent = "invalid_content"
)
// ErrorResponse is the error response for all APIs
type ErrorResponse struct {
Detail struct {
Status string `json:"status"`
} `json:"detail"`
}
// UserResponse is the /user API response
type UserResponse struct {
UserID string `json:"user_id"`
FirstName string `json:"first_name"`
Subscription struct {
Tier string `json:"tier"`
Status string `json:"status"`
} `json:"subscription"`
}
// HistoryResponse is the /history API response
type HistoryResponse struct {
History []struct {
ID string `json:"history_item_id"`
ModelID string `json:"model_id"`
VoiceID string `json:"voice_id"`
} `json:"history"`
}
// VoiceResponse is the /voices API response
type VoicesResponse struct {
Voices []struct {
ID string `json:"voice_id"`
Name string `json:"name"`
Category string `json:"category"`
} `json:"voices"`
}
// ProjectsResponse is the /projects API response
type ProjectsResponse struct {
Projects []struct {
ID string `json:"project_id"`
Name string `json:"name"`
State string `json:"state"`
AccessLevel string `json:"access_level"`
} `json:"projects"`
}
// PronunciationDictionaries is the /pronunciation-dictionaries API response
type PronunciationDictionariesResponse struct {
PronunciationDictionaries []struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"pronunciation_dictionaries"`
}
// Models is the /models API response
type ModelsResponse struct {
ID string `json:"model_id"`
Name string `json:"name"`
}
// AgentsResponse is the /agents API response
type AgentsResponse struct {
Agents []struct {
ID string `json:"agent_id"`
Name string `json:"name"`
AccessLevel string `json:"access_level"`
} `json:"agents"`
}
// ConversationResponse is the /conversation API response
type ConversationResponse struct {
Conversations []struct {
AgentID string `json:"agent_id"`
ID string `json:"conversation_id"`
Status string `json:"status"`
}
}
// getAPIUrl return the API Url mapped to the permission
func getAPIUrl(permission Permission) string {
apiUrl := permissionToAPIMap[permission]
if strings.Contains(apiUrl, "%s") {
return fmt.Sprintf(apiUrl, fakeID)
}
return apiUrl
}
// makeElevenLabsRequest send the API request to passed url with passed key as API Key and return response body and status code
func makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte, int, error) {
// create request
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
return nil, 0, err
}
// add key in the header
req.Header.Add("xi-api-key", key)
resp, err := client.Do(req)
if err != nil {
return nil, 0, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
/*
the reason to translate body to byte and does not directly return http.Response
is if we return http.Response we cannot close the body in defer. If we do we will get an error
when reading body outside this function
*/
responseBodyByte, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 0, err
}
return responseBodyByte, resp.StatusCode, nil
}
// makeElevenLabsRequestWithPayload sends a POST/PATCH API request to the passed URL with the given key as the API Key
// and an optional payload. It returns the response body and status code.
func makeElevenLabsRequestWithPayload(client *http.Client, url, method, contentType, key string, payload []byte) ([]byte, int, error) {
// Create request with payload
req, err := http.NewRequest(method, url, bytes.NewBuffer(payload))
if err != nil {
return nil, 0, err
}
// Add headers
req.Header.Add("xi-api-key", key)
req.Header.Add("Content-Type", contentType)
// Send the request
resp, err := client.Do(req)
if err != nil {
return nil, 0, err
}
// ensure the response body is properly closed
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
// read the response body
responseBodyByte, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 0, err
}
return responseBodyByte, resp.StatusCode, nil
}
// getHistory get history item using the key passed and add them to secret info
func getHistory(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var history HistoryResponse
if err := json.Unmarshal(response, &history); err != nil {
return err
}
// add history read scope to secret info
secretInfo.AppendPermission(PermissionStrings[SpeechHistoryRead])
// map resource to secret info
for _, historyItem := range history.History {
secretInfo.AppendResource(ElevenLabsResource{
ID: historyItem.ID,
Name: "", // no name
Type: "History",
Permission: PermissionStrings[SpeechHistoryRead],
})
}
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking history read scope", statusCode)
}
}
// deleteHistory try to delete a history item. The item must not exist.
func deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryWrite), http.MethodDelete, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusInternalServerError:
// for some reason if we send fake id and token has the permission, the history api return 500 error instead of 404
// issue opened in elevenlabs-docs: https://github.com/elevenlabs/elevenlabs-docs/issues/649
return handleErrorStatus(response, PermissionStrings[SpeechHistoryWrite], secretInfo, InternalServerError)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking history write scope", statusCode)
}
}
// deleteDubbing try to delete a dubbing. The item must not exist.
func deleteDubbing(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingWrite), http.MethodDelete, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusNotFound:
// as we send fake id, if permission is assigned to token we must get 404 dubbing not found
if err := handleErrorStatus(response, PermissionStrings[DubbingWrite], secretInfo, DubbingNotFound); err != nil {
return err
}
// add read scope of dubbing to avoid get dubbing api call
secretInfo.AppendPermission(PermissionStrings[DubbingRead])
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking dubbing write scope", statusCode)
}
}
// getDebugging try to get a dubbing. The item must not exist.
func getDebugging(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusNotFound:
// as we send fake id, if permission is assigned to token we must get 404 dubbing not found
return handleErrorStatus(response, PermissionStrings[DubbingRead], secretInfo, DubbingNotFound)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking dubbing read scope", statusCode)
}
}
// getVoices get list of voices using the key passed and add them to secret info
func getVoices(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var voices VoicesResponse
if err := json.Unmarshal(response, &voices); err != nil {
return err
}
// add voices read scope to secret info
secretInfo.AppendPermission(PermissionStrings[VoicesRead])
// map resource to secret info
for _, voice := range voices.Voices {
secretInfo.AppendResource(ElevenLabsResource{
ID: voice.ID,
Name: voice.Name,
Type: "Voice",
Permission: PermissionStrings[VoicesRead],
Metadata: map[string]string{
"category": voice.Category,
},
})
}
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking voice read scope", statusCode)
}
}
// deleteVoice try to delete a voice. The item must not exist.
func deleteVoice(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesWrite), http.MethodDelete, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
// if permission was assigned to scope we should get 400 error with voice not found status
return handleErrorStatus(response, PermissionStrings[VoicesWrite], secretInfo, VoiceDoesNotExist)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking voice write scope", statusCode)
}
}
// getProjects get list of projects using the key passed and add them to secret info
func getProjects(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var projects ProjectsResponse
if err := json.Unmarshal(response, &projects); err != nil {
return err
}
// add project read scope to secret info
secretInfo.AppendPermission(PermissionStrings[ProjectsRead])
// map resource to secret info
for _, project := range projects.Projects {
secretInfo.AppendResource(ElevenLabsResource{
ID: project.ID,
Name: project.Name,
Type: "Project",
Permission: PermissionStrings[ProjectsRead],
Metadata: map[string]string{
"state": project.State,
"access level": project.AccessLevel, // access level of project
},
})
}
return nil
case http.StatusForbidden:
// if token has the permission but trail is free, projects are not accessible
return handleErrorStatus(response, PermissionStrings[ProjectsRead], secretInfo, InvalidSubscription)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking projects read scope", statusCode)
}
}
// deleteProject try to delete a project. The item must not exist.
func deleteProject(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsWrite), http.MethodDelete, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
// if permission was assigned to token we should get 400 error with project not found status
return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, ProjectNotFound)
case http.StatusForbidden:
// if token has the permission but trail is free, projects are not accessible
return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, InvalidSubscription)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking project write scope", statusCode)
}
}
// getPronunciationDictionaries get list of pronunciation dictionaries using the key passed and add them to secret info
func getPronunciationDictionaries(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(PronunciationDictionariesRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var PDs PronunciationDictionariesResponse
if err := json.Unmarshal(response, &PDs); err != nil {
return err
}
// add voices read scope to secret info
secretInfo.AppendPermission(PermissionStrings[PronunciationDictionariesRead])
// map resource to secret info
for _, pd := range PDs.PronunciationDictionaries {
secretInfo.AppendResource(ElevenLabsResource{
ID: pd.ID,
Name: pd.Name,
Type: "Pronunciation Dictionary",
Permission: PermissionStrings[PronunciationDictionariesRead],
})
}
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking pronunciation dictionaries read scope", statusCode)
}
}
// removePronunciationDictionariesRule try to remove a rule from pronunciation dictionaries. The item must not exist.
func removePronunciationDictionariesRule(client *http.Client, key string, secretInfo *SecretInfo) error {
// send empty list of rule strings
payload := map[string]interface{}{
"rule_strings": []string{""},
}
payloadBytes, _ := json.Marshal(payload)
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), http.MethodPost,
"application/json", key, payloadBytes)
if err != nil {
return err
}
switch statusCode {
case http.StatusNotFound:
// if permission was assigned to token we should get 404 error with pronunciation_dictionary_not_found status
return handleErrorStatus(response, PermissionStrings[PronunciationDictionariesWrite], secretInfo, PronunciationDictionaryNotFound)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking pronunciation dictionary write scope", statusCode)
}
}
// getModels list models using the key passed and add them to secret info
func getModels(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ModelsRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var models []ModelsResponse
if err := json.Unmarshal(response, &models); err != nil {
return err
}
// add models read scope to secret info
secretInfo.AppendPermission(PermissionStrings[ModelsRead])
// map resource to secret info
for _, model := range models {
secretInfo.AppendResource(ElevenLabsResource{
ID: model.ID,
Name: model.Name,
Type: "Model",
Permission: PermissionStrings[ModelsRead],
})
}
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode)
}
}
// updateAudioNativeProject try to update a project content. The item must not exist.
func updateAudioNativeProject(client *http.Client, key string, secretInfo *SecretInfo) error {
// create a buffer to hold the multipart form data
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// add required fields to multipart form body
_ = writer.WriteField("auto_convert", "false")
_ = writer.WriteField("auto_publish", "false")
// close the writer
_ = writer.Close()
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioNativeWrite), http.MethodPost,
writer.FormDataContentType(), key, body.Bytes())
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
// if the permission is assigned to token, the api should return 400 with invalid project id
if err := handleErrorStatus(response, PermissionStrings[AudioNativeWrite], secretInfo, InvalidProjectID); err != nil {
return err
}
// add read permission as no separate API exist to check read audio native permission
secretInfo.AppendPermission(PermissionStrings[AudioNativeRead])
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking audio native write scope", statusCode)
}
}
// deleteInviteFromWorkspace try to remove a invite from workspace. The item must not exist.
func deleteInviteFromWorkspace(client *http.Client, key string, secretInfo *SecretInfo) error {
// send fake email in payload
payload := map[string]interface{}{
"email": fakeID + "@example.com",
}
payloadBytes, _ := json.Marshal(payload)
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(WorkspaceWrite), http.MethodDelete,
"application/json", key, payloadBytes)
if err != nil {
return err
}
switch statusCode {
case http.StatusInternalServerError:
// for some reason if we send fake email and token has the permission, the workspace invite api return 500 error instead of 404
if err := handleErrorStatus(response, PermissionStrings[WorkspaceWrite], secretInfo, InternalServerError); err != nil {
return err
}
// add read permission as no separate API exist to check workspace read permission
secretInfo.AppendPermission(PermissionStrings[WorkspaceRead])
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking workspace write scope", statusCode)
}
}
// textToSpeech try to convert text to speech. The model id and voice id is fake so it actually never happens.
func textToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error {
// send fake model id in payload
payload := map[string]interface{}{
"text": "This is trufflehog trying to check text to speech permission of the token",
"model_id": fakeID,
}
payloadBytes, _ := json.Marshal(payload)
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(TextToSpeech), http.MethodPost,
"application/json", key, payloadBytes)
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
// if permission is assigned to token, error status will be either model not found or voice not found as we sent both fake ;)
return handleErrorStatus(response, PermissionStrings[TextToSpeech], secretInfo, ModelNotFound, VoiceNotFound)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking text to speech scope", statusCode)
}
}
// speechToSpeech try to change a voice in speech. The model id and voice id is fake so it actually never happens.
func speechToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error {
// create a buffer to hold the multipart form data
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// add required fields to multipart form body
_ = writer.WriteField("model_id", fakeID)
_ = writer.WriteField("seed", "1")
_ = writer.WriteField("remove_background_noise", "false")
audio, _ := writer.CreateFormFile("audio", "")
_, _ = audio.Write([]byte("This is example fake audio for api call"))
// close the writer
_ = writer.Close()
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(SpeechToSpeech), http.MethodPost,
writer.FormDataContentType(), key, body.Bytes())
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
return handleErrorStatus(response, PermissionStrings[SpeechToSpeech], secretInfo, InvalidContent)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking speech to speech scope", statusCode)
}
}
// audioIsolation try to remove background noise from a voice. The file will be corrupted so it should return an error.
func audioIsolation(client *http.Client, key string, secretInfo *SecretInfo) error {
// create a buffer to hold the multipart form data
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
audio, _ := writer.CreateFormFile("audio", "")
_, _ = audio.Write([]byte("This is example fake audio for api call"))
// close the writer
_ = writer.Close()
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioIsolation), http.MethodPost,
writer.FormDataContentType(), key, body.Bytes())
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
return handleErrorStatus(response, PermissionStrings[AudioIsolation], secretInfo, InvalidContent)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking audio isolation speech scope", statusCode)
}
}
/*
getAgents get all user agents which are not bound with any permission
call APIs in pattern: agents->conversation
*/
func getAgents(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, "https://api.elevenlabs.io/v1/convai/agents", http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var agents AgentsResponse
if err := json.Unmarshal(response, &agents); err != nil {
return err
}
// map resource to secret info
for _, agent := range agents.Agents {
resource := ElevenLabsResource{
ID: agent.ID,
Name: agent.Name,
Type: "Agent",
Permission: "", // not binded with any permission
Metadata: map[string]string{
"access level": agent.AccessLevel,
},
}
secretInfo.AppendResource(resource)
// get agent conversations
if err := getConversation(client, key, agent.ID, secretInfo); err != nil {
return err
}
}
return nil
default:
return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode)
}
}
// getConversation list all agent conversations using the key and agentID passed and add them to secret info
func getConversation(client *http.Client, key, agentID string, secretInfo *SecretInfo) error {
apiUrl := fmt.Sprintf("https://api.elevenlabs.io/v1/convai/conversations?agent_id=%s", agentID)
response, statusCode, err := makeElevenLabsRequest(client, apiUrl, http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var conversations ConversationResponse
if err := json.Unmarshal(response, &conversations); err != nil {
return err
}
// map resource to secret info
for _, conversation := range conversations.Conversations {
secretInfo.AppendResource(ElevenLabsResource{
ID: conversation.ID,
Name: "", // no name
Type: "Conversation",
Permission: "", // not binded with any permission
Metadata: map[string]string{
"status": conversation.Status,
},
})
}
return nil
default:
return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode)
}
}
// handleErrorStatus handle error response, check if expected error status is in the response and add require permission to secret info
// this is used in case where we expect error response with specific status mostly in write calls
func handleErrorStatus(response []byte, permissionToAdd string, secretInfo *SecretInfo, expectedErrStatuses ...string) error {
// check if status in response is what is expected to be
ok, err := checkErrorStatus(response, expectedErrStatuses...)
if err != nil {
return err
}
// if permission to add was passed and it was expected error status add the permission
if permissionToAdd != "" && ok {
secretInfo.AppendPermission(permissionToAdd)
} else if permissionToAdd != "" && !ok {
// if permission to add was passed and it was unexpected error status - return error
return errors.New("unexpected error response")
}
return nil
}
// checkErrorStatus check if any of expected error status exist in actual API error response
func checkErrorStatus(response []byte, expectedStatuses ...string) (bool, error) {
var errorResp ErrorResponse
if err := json.Unmarshal(response, &errorResp); err != nil {
return false, err
}
if slices.Contains(expectedStatuses, errorResp.Detail.Status) {
return true, nil
}
return false, nil
}