Class: YouTube::ApiClient

Inherits:
Object
  • Object
show all
Defined in:
app/services/youtube/api_client.rb

Overview

HTTP client for YouTube Data API v3.

Features:

  • Auto-refreshes expired access tokens
  • Retries on transient failures (500, 502, 503)
  • Wraps the Google API client gem for a simpler interface

Defined Under Namespace

Classes: ApiError, NotFoundError, QuotaExceededError

Constant Summary collapse

MAX_RETRIES =

Maximum retries.

3

Instance Method Summary collapse

Constructor Details

#initialize(account: nil, oauth_service: nil) ⇒ ApiClient

Returns a new instance of ApiClient.

Parameters:

  • account (Account, nil) (defaults to: nil)

    the CRM account whose YouTube token to use

  • oauth_service (YouTube::OauthService, nil) (defaults to: nil)

    override for testing



36
37
38
# File 'app/services/youtube/api_client.rb', line 36

def initialize(account: nil, oauth_service: nil)
  @oauth_service = oauth_service || YouTube::OauthService.new(account: )
end

Instance Method Details

#delete_caption(caption_id) ⇒ Object



163
164
165
# File 'app/services/youtube/api_client.rb', line 163

def delete_caption(caption_id)
  with_service { |yt| yt.delete_caption(caption_id) }
end

#get_video(video_id, parts: 'snippet,contentDetails,status,statistics') ⇒ Object



62
63
64
65
66
67
# File 'app/services/youtube/api_client.rb', line 62

def get_video(video_id, parts: 'snippet,contentDetails,status,statistics')
  with_service do |yt|
    response = yt.list_videos(parts, id: video_id)
    response.items&.first
  end
end

#get_videos(video_ids, parts: 'snippet,contentDetails,status,statistics') ⇒ Object



69
70
71
72
73
74
75
76
# File 'app/services/youtube/api_client.rb', line 69

def get_videos(video_ids, parts: 'snippet,contentDetails,status,statistics')
  return [] if video_ids.blank?

  with_service do |yt|
    response = yt.list_videos(parts, id: video_ids.join(','))
    response.items || []
  end
end

#insert_caption(video_id, language:, name:, file_path:, content_type: 'text/vtt') ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'app/services/youtube/api_client.rb', line 136

def insert_caption(video_id, language:, name:, file_path:, content_type: 'text/vtt')
  caption = Google::Apis::YoutubeV3::Caption.new(
    snippet: Google::Apis::YoutubeV3::CaptionSnippet.new(
      video_id: video_id,
      language: language,
      name: name,
      is_draft: false
    )
  )

  with_service do |yt|
    yt.insert_caption('snippet', caption,
                      upload_source: file_path,
                      content_type: content_type)
  end
end

#insert_video(metadata, file_path, content_type: 'video/mp4') ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'app/services/youtube/api_client.rb', line 78

def insert_video(, file_path, content_type: 'video/mp4')
  video = Google::Apis::YoutubeV3::Video.new(
    snippet: Google::Apis::YoutubeV3::VideoSnippet.new(
      title: [:title],
      description: [:description],
      tags: [:tags],
      category_id: [:category_id] || '22',
      default_language: [:default_language] || 'en'
    ),
    status: Google::Apis::YoutubeV3::VideoStatus.new(
      privacy_status: [:privacy_status] || 'private',
      self_declared_made_for_kids: false,
      contains_synthetic_media: [:contains_synthetic_media]
    )
  )

  with_service do |yt|
    yt.insert_video('snippet,status', video,
                    upload_source: file_path,
                    content_type: content_type)
  end
end

#list_captions(video_id) ⇒ Object

--- Captions ---



129
130
131
132
133
134
# File 'app/services/youtube/api_client.rb', line 129

def list_captions(video_id)
  with_service do |yt|
    response = yt.list_captions('snippet', video_id)
    response.items || []
  end
end

#list_channel_videos(channel_id:, max_results: 50, page_token: nil) ⇒ Object

--- Videos ---



51
52
53
54
55
56
57
58
59
60
# File 'app/services/youtube/api_client.rb', line 51

def list_channel_videos(channel_id:, max_results: 50, page_token: nil)
  with_service do |yt|
    yt.list_searches('snippet',
                     channel_id: channel_id,
                     type: 'video',
                     max_results: [max_results, 50].min,
                     order: 'date',
                     page_token: page_token)
  end
end

#my_channelObject

--- Channel ---



42
43
44
45
46
47
# File 'app/services/youtube/api_client.rb', line 42

def my_channel
  with_service do |yt|
    response = yt.list_channels('snippet,contentDetails,statistics', mine: true)
    response.items&.first
  end
end

#set_thumbnail(video_id, file_path, content_type: 'image/jpeg') ⇒ Object

--- Thumbnails ---



169
170
171
172
173
174
175
# File 'app/services/youtube/api_client.rb', line 169

def set_thumbnail(video_id, file_path, content_type: 'image/jpeg')
  with_service do |yt|
    yt.set_thumbnail(video_id,
                     upload_source: file_path,
                     content_type: content_type)
  end
end

#update_caption(caption_id, file_path:, content_type: 'text/vtt') ⇒ Object



153
154
155
156
157
158
159
160
161
# File 'app/services/youtube/api_client.rb', line 153

def update_caption(caption_id, file_path:, content_type: 'text/vtt')
  caption = Google::Apis::YoutubeV3::Caption.new(id: caption_id)

  with_service do |yt|
    yt.update_caption('snippet', caption,
                      upload_source: file_path,
                      content_type: content_type)
  end
end

#update_video(video_id, snippet_attrs: {}, status_attrs: {}) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'app/services/youtube/api_client.rb', line 101

def update_video(video_id, snippet_attrs: {}, status_attrs: {})
  with_service do |yt|
    fetch_parts = []
    fetch_parts << 'snippet' if snippet_attrs.present?
    fetch_parts << 'status' if status_attrs.present?
    raise ArgumentError, 'snippet_attrs or status_attrs required' if fetch_parts.empty?

    # Fetch only the parts we will write back. If we load snippet+status but call
    # update with part=snippet, the client serializes both into the JSON body and
    # YouTube returns: unexpectedPart: 'status' (body must not include parts omitted
    # from the request's `part` parameter).
    existing = yt.list_videos(fetch_parts.join(','), id: video_id).items&.first
    raise NotFoundError.new("Video #{video_id} not found on YouTube", status: 404) unless existing

    if snippet_attrs.present?
      snippet_attrs.each { |k, v| existing.snippet.send(:"#{k}=", v) if existing.snippet.respond_to?(:"#{k}=") }
    end

    if status_attrs.present?
      status_attrs.each { |k, v| existing.status.send(:"#{k}=", v) if existing.status.respond_to?(:"#{k}=") }
    end

    yt.update_video(fetch_parts.join(','), existing)
  end
end