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



31
32
33
# File 'app/services/youtube/api_client.rb', line 31

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

Instance Method Details

#delete_caption(caption_id) ⇒ Object



157
158
159
# File 'app/services/youtube/api_client.rb', line 157

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

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



57
58
59
60
61
62
# File 'app/services/youtube/api_client.rb', line 57

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



64
65
66
67
68
69
70
71
# File 'app/services/youtube/api_client.rb', line 64

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



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'app/services/youtube/api_client.rb', line 130

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



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

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

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



123
124
125
126
127
128
# File 'app/services/youtube/api_client.rb', line 123

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



46
47
48
49
50
51
52
53
54
55
# File 'app/services/youtube/api_client.rb', line 46

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



37
38
39
40
41
42
# File 'app/services/youtube/api_client.rb', line 37

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



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

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



147
148
149
150
151
152
153
154
155
# File 'app/services/youtube/api_client.rb', line 147

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



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

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