Вопрос или проблема
Я следую этому API: https://developer.dribbble.com/v2/shots/#create-a-shot и пытаюсь опубликовать новый шот. Токен доступа был создан с использованием scope=upload. Я смог вызвать /user/shots, используя тот же токен, так что токен действителен. API возвращает HTTP 403 неавторизовано. Тело сообщения ответа пустое.
Вот метод, который я использую:
type DribbbleApi struct {
Token string
}
// https://developer.dribbble.com/v2/shots/#create-a-shot
// Нулевое значение не будет отправлено (например, игнорируется)
type CreateShotArgs struct {
Image io.Reader
Filename string
Title string
Description string
LowProfile bool
ReboundResourceId int
ScheduledFor *time.Time
Tags []string
TeamId int
Timeout time.Duration
}
// CreateShot создает новый шот с использованием https://developer.dribbble.com/v2/shots/#create-a-shot
func (d *DribbbleApi) CreateShot(args CreateShotArgs) error {
rd, wr := io.Pipe()
w := multipart.NewWriter(wr)
var err error = nil
go func() {
defer wr.Close()
defer w.Close()
err = w.WriteField("title", args.Title)
if err != nil {
return
}
if args.Description != "" {
err = w.WriteField("description", args.Description)
if err != nil {
return
}
}
if args.LowProfile {
err = w.WriteField("low_profile", "true")
if err != nil {
return
}
}
if args.ReboundResourceId > 0 {
err = w.WriteField("rebound_resource_id", fmt.Sprintf("%d", args.ReboundResourceId))
if err != nil {
return
}
}
if args.ScheduledFor != nil {
err = w.WriteField("scheduled_for", args.ScheduledFor.Format(time.RFC3339))
if err != nil {
return
}
}
if len(args.Tags) > 0 {
tags := strings.Join(args.Tags, ",")
err = w.WriteField("tags", tags)
if err != nil {
return
}
}
if args.TeamId > 0 {
err = w.WriteField("team_id", fmt.Sprint(args.TeamId))
if err != nil {
return
}
}
var fp io.Writer
fp, err = w.CreateFormFile("image", args.Filename)
if err != nil {
return
}
_, err = io.Copy(fp, args.Image)
return
}()
req, err2 := http.NewRequest("POST", "https://api.dribbble.com/v2/shots", rd)
if err2 != nil {
return err2
}
if err != nil {
return err
}
req.Header.Set("Content-Type", w.FormDataContentType())
req.Header.Set("Authorization", "Bearer "+d.Token)
client := &http.Client{
Timeout: args.Timeout,
}
res, err2 := client.Do(req)
if err2 != nil {
return err2
}
defer res.Body.Close()
if err != nil {
return err
}
if res.StatusCode != http.StatusAccepted {
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
return err
}
fmt.Println(string(bodyBytes)) // выводит пустую строку
return fmt.Errorf("плохой статус: %s %s", res.Status, string(bodyBytes))
}
return nil
}
Документация API Dribbble не очень ясна. Она не объясняет типы полей, например, поле low_profile имеет тип “boolean”, но что это значит? Если это POST-запрос с многокомпонентной формой, то такого понятия как “boolean” не существует, и неясно, как его следует представлять.
В ответе нет сообщения об ошибке, и у меня нет понятия, что не так.
Что я делаю не так?
Ответ или решение
Для решения проблемы с получением ошибки 403 Unauthorized при попытке создать новый шот (shot) с помощью API Dribbble, необходимо обратить внимание на несколько аспектов вашего кода и конфигурации.
-
Проверка доступа и прав токена: Вы упомянули, что токен был создан с использованием
scope=upload
и что вы можете вызывать эндпоинт/user/shots
с тем же токеном. Это означает, что у вас есть права на публикацию шотов. Однако обязательно убедитесь, что токен не истек и с ним не связаны ограничения. -
Параметры запроса: API требует определенные поля в запросе. По вашему коду видно, что вы пытаетесь превратить
low_profile
в строку "true", когда он установлен вtrue
. Однако, поскольку это поле является булевым, его следует отправлять как "true" или "false" (возможны строки). Убедитесь, что вы передаете его корректно:if args.LowProfile { err = w.WriteField("low_profile", "true") if err != nil { return } }
Вместо этого, вы можете полностью исключить поле, если оно равно
false
. -
Отправка файла изображения: Убедитесь, что файл изображения корректно загружается и передается. Если он не может быть прочитан или я неправильно переданы, это может вызывать ошибку 403. Убедитесь, что
args.Image
является корректнымio.Reader
и что вы передаете файл в нужном формате. -
Формат даты для
scheduled_for
: Убедитесь, что передаваемая датаscheduled_for
соответствует форматуtime.RFC3339
. Вы правильно это делаете, но если передаетеnil
, это должно быть обработано, чтобы избежать попыток писать это поле при отсутствии значения. -
Обработка HTTP-ответа: В вашем коде логика обработки ответа выглядит корректно. К тому же, выдвигайте дополнительные попытки логирования, чтобы понять, в каком именно месте возникает сбой.
-
Изменение URL API: Убедитесь, что используете правильный URL для API. Текущая версия API может измениться, и не следует полагаться на старую информацию.
-
Тестирование через Postman или аналогичный инструмент: Если проблема сохраняется, попробуйте выполнить запрос через Postman или аналогичный инструмент, чтобы вручную проверить работу API с тем же токеном и параметрами запросов. Это может помочь выявить проблемы на уровне данных или аутентификации.
Пример отправки запроса через Postman:
- Установите метод HTTP на
POST
. - Установите URL на
https://api.dribbble.com/v2/shots
. - Вкладка
Authorization
: выберитеBearer Token
и вставьте ваш токен. - Вкладка
Body
выберитеform-data
и добавьте поля, аналогично тем, что вы добавляете в коде, убедитесь, что файл изображения загружается корректно.
Если после проверки всех вышеперечисленных шагов проблема все еще не решена, я рекомендую обратиться к документации API Dribbble или службе поддержки для получения более детальной информации о наличии возможных ограничений или ошибок в API.