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



172
173
174
# File 'app/services/youtube/api_client.rb', line 172

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

#delete_video(video_id) ⇒ Object

Permanently delete a video from YouTube. Irreversible: the videoId, public
URL, view count and comments are gone. YouTube has no media-replace, so a
swapped source must be deleted here and re-uploaded as a NEW video. A 404
(already deleted on YouTube) is treated as success so the local link can
still be cleared.



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

def delete_video(video_id)
  with_service { |yt| yt.delete_video(video_id) }
  true
rescue NotFoundError
  true
end

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



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

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



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

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



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'app/services/youtube/api_client.rb', line 145

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



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

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



138
139
140
141
142
143
# File 'app/services/youtube/api_client.rb', line 138

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



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

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



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

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



177
178
179
180
181
182
183
# File 'app/services/youtube/api_client.rb', line 177

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



162
163
164
165
166
167
168
169
170
# File 'app/services/youtube/api_client.rb', line 162

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



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

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