instagram_public_api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c551fc1531ad6bcda7005e5ba65df0092ac3b1ed
4
+ data.tar.gz: 7c2de66f3efbdc23b10d48a1cc836caf56b1e0bd
5
+ SHA512:
6
+ metadata.gz: f4141716cdc95b4913556ca0e0b487b602a3d5acc029f78586054135defbf221709391a813152293aa353e36915e144195ec06e2185d7476643bc4450eabf7f9
7
+ data.tar.gz: 7349b071e43035433fc2d46365f52b98bd2322fdecae92ba8f45dace7b55cbb5629cca4dd0533563b0cf42d1a7e0b2e159b13cbf29236c47b09d1740b7c27116
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.2
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in instagram_public_api.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Instagram Public API client
2
+
3
+ A client/scraper/crawler for the public Instagram API. The client comes with handy methods such as passing a limit. The client will then proceed and crawl more data as required. Advanced configuration can be done using an http proxy.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'instagram_public_api'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install instagram_public_api
21
+
22
+ ## Usage
23
+
24
+ **Get media for a location:**
25
+
26
+ ```
27
+ InstagramPublicApi::Client.new.location_media("263928560", limit: 100, request_parameters: {})
28
+ ```
29
+
30
+ **Get media for a hashtag:**
31
+
32
+ ```
33
+ InstagramPublicApi::Client.new.hashtag_media("raccoon", limit: 100, request_parameters: {})
34
+ ```
35
+
36
+
37
+ ### Advanced configuration
38
+
39
+ You can configure an HTTP Proxy in case you would like the requests to be done via an external service.
40
+
41
+
42
+ ```
43
+ InstagramPublicApi.proxy_uri = "http://foo"
44
+ InstagramPublicApi.proxy_user = "ubuntu"
45
+ InstagramPublicApi.proxy_password = "mycrazypassword"
46
+
47
+ ```
48
+
49
+ This proxy config will be used as the default proxy configuration for all following requests. When creating a client, you can pass an optional `http_service` with different proxy options as needed.
50
+
51
+ ```
52
+ opts = {
53
+ uri: "http://customuri.com",
54
+ user: "customuser",
55
+ password: "custom-password",
56
+ }
57
+ service = InstagramPublicApi::HTTPService.new(proxy_config: opts)
58
+ client = InstagramPublicApi::Client.new(http_service: service)
59
+
60
+ ```
61
+
62
+
63
+ ## Development
64
+
65
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
66
+
67
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
68
+
69
+ ## Contributing
70
+
71
+ Bug reports and pull requests are welcome on GitHub at https://github.com/hendricius/instagram_public_api.
72
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "instagram_public_api"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'instagram_public_api/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "instagram_public_api"
8
+ spec.version = InstagramPublicApi::VERSION
9
+ spec.authors = ["Hendrik Kleinwaechter"]
10
+ spec.email = ["hendrik.kleinwaechter@gmail.com"]
11
+
12
+ spec.summary = %q{Client for the public Instagram API}
13
+ spec.description = %q{A client/scraper for the public Instagram API.}
14
+ spec.homepage = "https://github.com/hendricius/instagram_public_api"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.12"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest", "~> 5.0"
24
+ spec.add_development_dependency "webmock"
25
+ spec.add_development_dependency "pry"
26
+
27
+ spec.add_runtime_dependency(%q<faraday>, ["~> 0.8"])
28
+ spec.add_runtime_dependency "faraday_middleware"
29
+ spec.add_runtime_dependency "activesupport"
30
+ spec.add_runtime_dependency "virtus"
31
+ end
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+ require 'pry'
6
+ require 'pp'
7
+ require 'virtus'
8
+
9
+ require "instagram_public_api/version"
10
+ require "instagram_public_api/http_service"
11
+ require "instagram_public_api/client"
12
+ require "instagram_public_api/entities/location"
13
+ require "instagram_public_api/entities/medium_node"
14
+
15
+ module InstagramPublicApi
16
+ class << self
17
+ attr_accessor :proxy_uri, :proxy_password, :proxy_user
18
+ end
19
+ end
@@ -0,0 +1,96 @@
1
+ module InstagramPublicApi
2
+ # client that holds the method for communicating with the Instagram API
3
+ class Client
4
+ attr_reader :http_service
5
+
6
+ def initialize(http_service: HTTPService.new, default_parameters: {})
7
+ @http_service = http_service
8
+ @default_parameters = default_parameters.merge(build_default_parameters)
9
+ end
10
+
11
+ # returns a location with media attached for the given location id
12
+ #
13
+ # limit: the amount of media that should be fetched.
14
+ # request_parameters: optional parameters that will be passed to the request
15
+ def location_media(location_id, request_parameters: {limit: 1000}, limit: 10)
16
+ location = extract_location_media(location_id, request_parameters: request_parameters)
17
+ # check if we should get more data
18
+ paging_info = location.paging_info
19
+ # poll more data
20
+ while location.total_media_count < limit && paging_info[:has_next_page] do
21
+ request_opts = {}.merge(request_parameters)
22
+ if paging_info && paging_info[:end_cursor]
23
+ request_opts[:max_id] = paging_info[:end_cursor]
24
+ end
25
+ next_page_location = extract_location_media(location_id, request_parameters: request_opts)
26
+ location.add_media(next_page_location.media)
27
+ paging_info = next_page_location.paging_info
28
+ location
29
+ end
30
+ location
31
+ end
32
+
33
+ # returns a hashtag with media associated
34
+ #
35
+ # limit: the amount of media that should be fetched.
36
+ # request_parameters: optional parameters that will be passed to the request
37
+ def hashtag_media
38
+ raise ArgumentError, "not implemented yet"
39
+ end
40
+
41
+ private
42
+
43
+ # performs the actual request to the API.
44
+ #
45
+ # returns a Location object.
46
+ def extract_location_media(location_id, request_parameters: {})
47
+ uri = "explore/locations/#{location_id}/"
48
+ data = request(uri: uri, parameters: request_parameters)
49
+ body = data.body[:location]
50
+ location = Entities::Location.new
51
+ attrs = %i[name lat lng id]
52
+ attrs.each do |attribute|
53
+ location.send("#{attribute}=", body[attribute])
54
+ end
55
+ media = {}
56
+ body[:media].fetch(:nodes, []).each do |medium|
57
+ media[medium[:id]] = Entities::MediumNode.new(medium)
58
+ end
59
+ location.media = media.values
60
+ location.top_posts = body[:top_posts].fetch(:nodes, []).map {|d| Entities::MediumNode.new(d)}
61
+ location.paging_info = body[:media].fetch(:page_info, {})
62
+ location
63
+ end
64
+
65
+ # perform the actual request. receives a URI as argument. Optional parameters
66
+ # and request parameter options can be passed.
67
+ #
68
+ # returns the raw http response
69
+ def request(uri:, request_options: {}, parameters: {})
70
+ opts = {
71
+ uri: uri,
72
+ request_options: request_options,
73
+ parameters: @default_parameters.merge(parameters)
74
+ }
75
+ parse_response(http_service.perform_request(opts))
76
+ end
77
+
78
+ # Common Parameters required for every call
79
+ def build_default_parameters
80
+ return {
81
+ __a: 1
82
+ }
83
+ end
84
+
85
+ # parses the raw response by the remote. returns an object with a raw_response
86
+ # as method and the parsed body associated.
87
+ def parse_response(response)
88
+ OpenStruct.new(
89
+ raw_response: response,
90
+ body: JSON.parse(response.body, symbolize_names: true)
91
+ )
92
+ end
93
+
94
+
95
+ end
96
+ end
@@ -0,0 +1,4 @@
1
+ module InstagramPublicApi
2
+ module Entities
3
+ end
4
+ end
@@ -0,0 +1,30 @@
1
+ module InstagramPublicApi
2
+ module Entities
3
+ class Location
4
+ include Virtus.model
5
+ attribute :name, String
6
+ attribute :lat, Float
7
+ attribute :lng, Float
8
+ attribute :id, String
9
+ attribute :top_posts, Array, default: []
10
+ attribute :media, Array, default: []
11
+ attribute :paging_info, Object, default: {}
12
+
13
+ # returns all media including top posts and media
14
+ def all_media
15
+ [top_posts, media].flatten.uniq{|m| m.id }.sort_by {|m| m.likes_count }.reverse
16
+ end
17
+
18
+ # adds an array of media to the location
19
+ def add_media(array)
20
+ array.each {|m| self.media << m }
21
+ end
22
+
23
+ # returns how much media has been added to this location.
24
+ def total_media_count
25
+ all_media.length
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ module InstagramPublicApi
2
+ module Entities
3
+ class MediumNode
4
+ include Virtus.model
5
+ attribute :code, String
6
+ attribute :dimensions, Hash, default: {}
7
+ attribute :comments, Hash, default: {}
8
+ attribute :owner, Hash, default: {}
9
+ attribute :likes, Hash, default: {}
10
+ attribute :date, Numeric
11
+ attribute :thumbnail_src, String
12
+ attribute :is_video, Boolean
13
+ attribute :id, String
14
+ attribute :display_src, String
15
+
16
+ # returns the likes count of the medium node
17
+ def likes_count
18
+ likes[:count] || 0
19
+ end
20
+
21
+ # returns true if the dimensions are a square
22
+ def is_square?
23
+ dimensions[:width] && dimensions[:height] && dimensions[:width] == dimensions[:height]
24
+ end
25
+
26
+ # returns true if the image is landscape mode
27
+ def is_landscape?
28
+ dimensions[:width] && dimensions[:height] && dimensions[:width] > dimensions[:height]
29
+ end
30
+
31
+ # returns true if the image is in portrait mode
32
+ def is_portrait?
33
+ dimensions[:width] && dimensions[:height] && dimensions[:height] > dimensions[:width]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,51 @@
1
+ module InstagramPublicApi
2
+ # http service that performs the requests to the instagram API
3
+ class HTTPService
4
+ API_SERVER = 'https://www.instagram.com/'
5
+
6
+ def initialize(proxy_config: nil)
7
+ if proxy_config
8
+ @proxy_config = proxy_config
9
+ elsif InstagramPublicApi.proxy_uri && InstagramPublicApi.proxy_user && InstagramPublicApi.proxy_password
10
+ @proxy_config = {
11
+ uri: InstagramPublicApi.proxy_uri,
12
+ user: InstagramPublicApi.proxy_user,
13
+ password: InstagramPublicApi.proxy_password,
14
+ }
15
+ end
16
+ end
17
+
18
+
19
+ # The address of the appropriate Expedia server.
20
+ #
21
+ # return a complete server address with protocol
22
+ def server_url
23
+ API_SERVER
24
+ end
25
+
26
+ # returns a hash with options for faraday
27
+ def faraday_options
28
+ options = {
29
+ url: server_url
30
+ }
31
+ if @proxy_config
32
+ options[:proxy] = @proxy_config
33
+ end
34
+ options
35
+ end
36
+
37
+ # performs the actual http request
38
+ def perform_request(request_options: {}, parameters: {}, uri:)
39
+ args = parameters
40
+ request_options = request_options.merge(faraday_options)
41
+ # figure out our options for this request
42
+ # set up our Faraday connection
43
+ connection = Faraday.new(faraday_options) do |faraday|
44
+ faraday.adapter Faraday.default_adapter
45
+ end
46
+ connection.get(uri, args)
47
+ end
48
+
49
+
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module InstagramPublicApi
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,185 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: instagram_public_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hendrik Kleinwaechter
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-07-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: faraday
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.8'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: faraday_middleware
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activesupport
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: virtus
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description: A client/scraper for the public Instagram API.
140
+ email:
141
+ - hendrik.kleinwaechter@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".travis.yml"
148
+ - Gemfile
149
+ - README.md
150
+ - Rakefile
151
+ - bin/console
152
+ - bin/setup
153
+ - instagram_public_api.gemspec
154
+ - lib/instagram_public_api.rb
155
+ - lib/instagram_public_api/client.rb
156
+ - lib/instagram_public_api/entities.rb
157
+ - lib/instagram_public_api/entities/location.rb
158
+ - lib/instagram_public_api/entities/medium_node.rb
159
+ - lib/instagram_public_api/http_service.rb
160
+ - lib/instagram_public_api/version.rb
161
+ homepage: https://github.com/hendricius/instagram_public_api
162
+ licenses: []
163
+ metadata: {}
164
+ post_install_message:
165
+ rdoc_options: []
166
+ require_paths:
167
+ - lib
168
+ required_ruby_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ required_rubygems_version: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ requirements: []
179
+ rubyforge_project:
180
+ rubygems_version: 2.4.5
181
+ signing_key:
182
+ specification_version: 4
183
+ summary: Client for the public Instagram API
184
+ test_files: []
185
+ has_rdoc: