This repository has been archived by the owner on Apr 16, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add instagram gem library to project lib.
- Loading branch information
Showing
7 changed files
with
262 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
require 'addressable/uri' | ||
require 'addressable/template' | ||
require 'instagram/models' | ||
|
||
module Instagram | ||
|
||
extend self | ||
|
||
Popular = Addressable::URI.parse 'http://instagr.am/api/v1/feed/popular/' | ||
UserFeed = Addressable::Template.new 'http://instagr.am/api/v1/feed/user/{user_id}/' | ||
UserInfo = Addressable::Template.new 'http://instagr.am/api/v1/users/{user_id}/info/' | ||
|
||
def popular(params = {}, options = {}) | ||
parse_response(Popular.dup, params, options.fetch(:parse_with, Timeline)) | ||
end | ||
|
||
def by_user(user_id, params = {}, options = {}) | ||
url = UserFeed.expand :user_id => user_id | ||
parse_response(url, params, options.fetch(:parse_with, Timeline)) | ||
end | ||
|
||
def user_info(user_id, params = {}, options = {}) | ||
url = UserInfo.expand :user_id => user_id | ||
parse_response(url, params, options.fetch(:parse_with, UserWrap)) | ||
end | ||
|
||
private | ||
|
||
def parse_response(url, params, parser = nil) | ||
url.query_values = params | ||
body = get_url url | ||
parser ? parser.parse(body) : body | ||
end | ||
|
||
def get_url(url) | ||
response = Net::HTTP.start(url.host, url.port) { |http| | ||
http.get url.request_uri | ||
} | ||
|
||
if Net::HTTPSuccess === response | ||
response.body | ||
else | ||
response.error! | ||
end | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
require 'instagram/failsafe_store' | ||
|
||
module Instagram | ||
module Cached | ||
extend Instagram | ||
|
||
class << self | ||
attr_accessor :cache | ||
|
||
def setup(cache_dir, options = {}) | ||
self.cache = FailsafeStore.new(cache_dir, { | ||
namespace: 'instagram', | ||
exceptions: [Net::HTTPServerException, JSON::ParserError] | ||
}.update(options)) | ||
end | ||
|
||
private | ||
def get_url(url) | ||
cache.fetch(url.to_s) { super } | ||
end | ||
end | ||
|
||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
require 'active_support/cache' | ||
|
||
module Instagram | ||
class FailsafeStore < ActiveSupport::Cache::FileStore | ||
# Reuses the stale cache if a known exception occurs while yielding to the block. | ||
# The list of exception classes is read from the ":exceptions" array. | ||
def fetch(name, options = nil) | ||
options = merged_options(options) | ||
key = namespaced_key(name, options) | ||
entry = unless options[:force] | ||
instrument(:read, name, options) do |payload| | ||
payload[:super_operation] = :fetch if payload | ||
read_entry(key, options) | ||
end | ||
end | ||
|
||
if entry and not entry.expired? | ||
instrument(:fetch_hit, name, options) { |payload| } | ||
entry.value | ||
else | ||
reusing_stale = false | ||
|
||
result = begin | ||
instrument(:generate, name, options) do |payload| | ||
yield | ||
end | ||
rescue | ||
if entry and ignore_exception?($!) | ||
reusing_stale = true | ||
instrument(:reuse_stale, name, options) do |payload| | ||
payload[:exception] = $! if payload | ||
entry.value | ||
end | ||
else | ||
# TODO: figure out if deleting entries is ever necessary | ||
# delete_entry(key, options) if entry | ||
raise | ||
end | ||
end | ||
|
||
write(name, result, options) unless reusing_stale | ||
result | ||
end | ||
end | ||
|
||
private | ||
|
||
def ignore_exception?(ex) | ||
options[:exceptions] && options[:exceptions].any? { |klass| ex.is_a? klass } | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
require 'nibbler/json' | ||
|
||
module Instagram | ||
|
||
class Base < NibblerJSON | ||
# `pk` is such a dumb property name | ||
element 'pk' => :id | ||
end | ||
|
||
class User < Base | ||
element :username | ||
element :full_name | ||
element 'profile_pic_url' => :avatar_url | ||
alias avatar avatar_url # `avatar` is deprecated | ||
|
||
# extended info | ||
element :media_count | ||
element :following_count | ||
alias following following_count # `following` will return an array of users in future! | ||
element :follower_count | ||
alias followers follower_count # `followers` will return an array of users in future! | ||
|
||
def ==(other) | ||
User === other and other.id == self.id | ||
end | ||
end | ||
|
||
class UserWrap < NibblerJSON | ||
element :user, :with => User | ||
# return user instead of self when done | ||
def parse() super.user end | ||
end | ||
|
||
class Media < Base | ||
# short string used for permalink | ||
element :code | ||
# type is always 1 (other values possibly reserved for video in the future?) | ||
element :media_type | ||
# filter code; use `filter_name` to get human name of the filter used | ||
element :filter_type | ||
# I don't know what "device timestamp" is and how it relates to `taken_at`? | ||
element :device_timestamp | ||
# timestamp of when the picture was taken | ||
element :taken_at, :with => lambda { |sec| Time.at(sec) } | ||
# user who uploaded the media | ||
element :user, :with => User | ||
|
||
# array of people who liked this media | ||
elements :likers, :with => User | ||
|
||
elements :comments, :with => NibblerJSON do | ||
element :created_at, :with => lambda { |sec| Time.at(sec) } | ||
# content type is always "comment" | ||
element :content_type | ||
# `type` is always 1 (other values possibly reserved for comments in form of media?) | ||
element :type | ||
# the `pk` of parent media | ||
element :media_id | ||
# comment body | ||
element :text | ||
# comment author | ||
element :user, :with => User | ||
end | ||
|
||
elements 'image_versions' => :images, :with => NibblerJSON do | ||
element :url | ||
# `type` is 5 for 150px, 6 for 306px and 7 for 612px | ||
element :type | ||
element :width | ||
element :height | ||
|
||
alias to_s url | ||
end | ||
|
||
# image location | ||
element :lat | ||
element :lng | ||
|
||
def geolocated? | ||
self.lat and self.lng | ||
end | ||
|
||
element :location, :with => Base do | ||
# ID on a 3rd-party service | ||
element :external_id | ||
# name of 3rd-party service, like "foursquare" | ||
element :external_source | ||
# name of location | ||
element :name | ||
# address in the external service's database | ||
element :address | ||
element :lat | ||
element :lng | ||
end | ||
|
||
# author's caption for the image; can be nil | ||
def caption | ||
# caption is implemented as a first comment made by the owner | ||
if comments.first and self.user == comments.first.user | ||
comments.first.text | ||
end | ||
end | ||
|
||
# typical sizes: 150px / 306px / 612px square | ||
def image_url(size = 150) | ||
self.images.find { |img| img.width == size }.to_s | ||
end | ||
|
||
FILTERS = { | ||
1 => 'X-Pro II', | ||
2 => 'Lomo-fi', | ||
3 => 'Earlybird', | ||
17 => 'Lily', | ||
5 => 'Poprocket', | ||
10 => 'Inkwell', | ||
4 => 'Apollo', | ||
15 => 'Nashville', | ||
13 => 'Gotham', | ||
14 => '1977', | ||
16 => 'Lord Kelvin' | ||
} | ||
|
||
def filter_name | ||
FILTERS[filter_type.to_i] | ||
end | ||
end | ||
|
||
class Timeline < NibblerJSON | ||
elements :items, :with => Media | ||
# return items instead of self when done | ||
def parse() super.items end | ||
end | ||
|
||
end |