Class: ScrapeCreators::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/scrape_creators/client.rb

Constant Summary collapse

TIKTOK =
'tiktok'
INSTAGRAM =
'instagram'
YOUTUBE =
'youtube'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = nil, api_key: nil) ⇒ Client

Returns a new instance of Client.



17
18
19
20
# File 'lib/scrape_creators/client.rb', line 17

def initialize(config = nil, api_key: nil)
  @config = config || ScrapeCreators.configuration
  @api_key = api_key || @config.api_key
end

Instance Attribute Details

#api_key ⇒ Object (readonly)

Returns the value of attribute api_key.



15
16
17
# File 'lib/scrape_creators/client.rb', line 15

def api_key
  @api_key
end

#config ⇒ Object (readonly)

Returns the value of attribute config.



15
16
17
# File 'lib/scrape_creators/client.rb', line 15

def config
  @config
end

Instance Method Details

#comments(url_or_code, options = {}) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/scrape_creators/client.rb', line 139

def comments(url_or_code, options = {})
  pages = options[:pages] || options[:max_pages] || 1
  cursor = options[:cursor]
  seen_cursors = {}
  all_comments = []

  pages.to_i.times do
    break if seen_cursors[cursor.to_s]

    seen_cursors[cursor.to_s] = true
    page = comments_page(url_or_code, options.merge(cursor: cursor))
    all_comments.concat(page[:comments])
    break unless page[:has_more]

    cursor = page[:cursor]
  end

  all_comments
end

#comments_page(url_or_code, options = {}) ⇒ Object

Raises:

  • (ArgumentError)


159
160
161
162
163
164
165
166
167
# File 'lib/scrape_creators/client.rb', line 159

def comments_page(url_or_code, options = {})
  source = (options[:source] || INSTAGRAM).to_s
  raise ArgumentError, "Unsupported comments source: #{source}" unless [INSTAGRAM, TIKTOK, YOUTUBE].include?(source)

  cursor = options[:cursor] || options['cursor']
  params = comments_params(url_or_code, source, cursor, options)
  response = get(comments_url_for(source: source), params)
  comments_result(response, source, cursor)
end

#comments_url_for(source:) ⇒ Object



40
41
42
43
44
45
46
47
# File 'lib/scrape_creators/client.rb', line 40

def comments_url_for(source:)
  case source.to_s
  when TIKTOK then '/v1/tiktok/video/comments'
  when INSTAGRAM then '/v2/instagram/post/comments'
  when YOUTUBE then '/v1/youtube/video/comments'
  else '/404'
  end
end

#get(endpoint, params = {}, options = {}) ⇒ Object



169
170
171
# File 'lib/scrape_creators/client.rb', line 169

def get(endpoint, params = {}, options = {})
  request(endpoint, method: :get, params: params, options: options)
end

#post(url_or_code, options = {}) ⇒ Object



131
132
133
134
135
136
137
# File 'lib/scrape_creators/client.rb', line 131

def post(url_or_code, options = {})
  url = normalize_url(url_or_code)
  source = options.dig(:source) || 'instagram'
  post_url = post_url_for(source:)
  params = { url: url }.merge(options)
  get(post_url, params)
end

#post_url_for(source:) ⇒ Object



31
32
33
34
35
36
37
38
# File 'lib/scrape_creators/client.rb', line 31

def post_url_for(source:)
  case source.to_s
  when TIKTOK then '/v2/tiktok/video'
  when INSTAGRAM then '/v1/instagram/post'
  when YOUTUBE then '/v1/youtube/video'
  else '/404'
  end
end

#posts(handle, options = {}) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/scrape_creators/client.rb', line 90

def posts(handle, options = {})
  source = (options[:source] || 'instagram').to_s
  pages = options[:pages] || options[:max_pages] || 1
  all_items = []
  cursor = nil
  has_more = true

  pages.to_i.times do
    break unless has_more

    page = posts_page(handle, options.merge(cursor: cursor))
    items = Array(page[:items])
    all_items.concat(items)
    cursor = page[:cursor]
    has_more = page[:has_more] && !cursor.to_s.empty? && !items.empty?
  end

  all_items
end

#posts_key_for(source:) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/scrape_creators/client.rb', line 49

def posts_key_for(source:)
  case source.to_s
  when TIKTOK then 'aweme_list'
  when INSTAGRAM then 'items'
  when YOUTUBE then 'shorts'
  else 'items'
  end
end

#posts_page(handle, options = {}) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/scrape_creators/client.rb', line 110

def posts_page(handle, options = {})
  source = (options[:source] || 'instagram').to_s
  params = { handle: handle }
  cursor = options[:cursor] || options['cursor']
  if cursor && !cursor.to_s.empty?
    params[posts_cursor_param_for(source:)] = cursor
  end
  extra = options.reject { |k, _| %w[source cursor max_pages pages].include?(k.to_s) }
  params.merge!(extra)

  res = get(posts_url_for(source:), params)
  key = posts_key_for(source:)
  items = res.is_a?(Hash) ? (res[key] || []) : []
  next_cursor = next_posts_cursor(res, source)
  {
    items: items,
    cursor: next_cursor,
    has_more: posts_has_more?(res, source, next_cursor)
  }
end

#posts_url_for(source:) ⇒ Object



22
23
24
25
26
27
28
29
# File 'lib/scrape_creators/client.rb', line 22

def posts_url_for(source:)
  case source.to_s
  when TIKTOK then '/v3/tiktok/profile/videos'
  when INSTAGRAM then '/v2/instagram/user/posts'
  when YOUTUBE then '/v1/youtube/channel/shorts'
  else '/404'
  end
end

#request(endpoint, method: :get, params: {}, options: {}) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/scrape_creators/client.rb', line 173

def request(endpoint, method: :get, params: {}, options: {})
  current_key = options[:api_key] || api_key
  if current_key.nil? || current_key.to_s.strip.empty?
    raise ConfigurationError, "Scrape Creators API key is missing. Set ENV['SCRAPE_CREATORS_API_KEY'] or configure via ScrapeCreators.configure { |c| c.api_key = '...' }"
  end

  clean_endpoint = endpoint.to_s.start_with?('/') ? endpoint.to_s : "/#{endpoint}"
  base_url = options[:api_base_url] || config.api_base_url
  url_str = "#{base_url}#{clean_endpoint}"

  uri = URI.parse(url_str)

  if method == :get
    uri.query = URI.encode_www_form(params) if params && !params.empty?
    req = Net::HTTP::Get.new(uri.request_uri)
  else
    req = Net::HTTP::Post.new(uri.request_uri)
    req['Content-Type'] = 'application/json'
    req.body = JSON.generate(params) if params && !params.empty?
  end

  req['x-api-key'] = current_key
  req['Accept'] = 'application/json'
  req['Accept-Encoding'] = 'gzip, deflate'

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = (uri.scheme == 'https')
  http.open_timeout = options[:open_timeout] || config.open_timeout
  http.read_timeout = options[:read_timeout] || config.read_timeout

  res = http.request(req)
  parse_response(res)
end

#search_users(query, source: INSTAGRAM) ⇒ Object

Raises:

  • (ArgumentError)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/scrape_creators/client.rb', line 58

def search_users(query, source: INSTAGRAM)
  query = query.to_s.strip
  raise ArgumentError, 'Search query is required' if query.empty?

  users = case source.to_s
          when INSTAGRAM
            response = get('/v1/instagram/search', query: query)
            search_results(response).dig('data', 'users').to_a.map do |user|
              { handle: user['username'], name: user['full_name'], avatar_url: user['profile_pic_url'] }
            end
          when TIKTOK
            response = get('/v1/tiktok/search/users', query: query, trim: true)
            search_results(response).fetch('users', []).map do |user|
              { handle: user['unique_id'], name: user['nickname'], avatar_url: user.dig('avatar_medium', 'url_list', 0) }
            end
          when YOUTUBE
            response = get('/v1/youtube/search', query: query, type: 'channels')
            search_results(response).fetch('channels', []).map do |channel|
              { handle: channel['handle'], name: channel['title'], avatar_url: channel['thumbnail'] }
            end
          else
            raise ArgumentError, "Unsupported search source: #{source}"
          end

  users.filter_map do |user|
    handle = user[:handle].to_s.strip.delete_prefix('@')
    next if handle.empty? || handle.match?(%r{[[:space:]/]})

    user.merge(handle: handle)
  end.uniq { |user| user[:handle].downcase }
end