class SmugmugAPI

Constants

ACCESS_TOKEN_URL
API_ENDPOINT
AUTHORIZE_URL
OAUTH_ORIGIN
REQUEST_TOKEN_URL
UPLOAD_ENDPOINT

Attributes

http[RW]
uploader[RW]

Public Class Methods

new(ejson_file = '~/.photo_helper.ejson') click to toggle source
# File lib/helpers/smugmug_api.rb, line 19
def initialize(ejson_file = '~/.photo_helper.ejson')
  ejson_file = File.expand_path(ejson_file)
  @secrets = Secrets.new(ejson_file, %i[api_key api_secret])
  request_access_token if !@secrets['access_token'] || !@secrets['access_secret']

  @http = get_access_token
  @uploader = get_access_token(UPLOAD_ENDPOINT)
  user_resp = user
  @user = user_resp['NickName']
  @root_node = File.basename(user_resp['Uris']['Node']['Uri'])
end

Public Instance Methods

albums() click to toggle source
# File lib/helpers/smugmug_api.rb, line 31
def albums
  albums_list = []
  start = 1
  count = 100
  loop do
    resp = get("/api/v2/user/#{@user}!albums", start: start, count: count)

    resp['Album'].each do |album|
      albums_list.push(album_parser(album))
    end
    break if (start + count) > resp['Pages']['Total'].to_i
    start += count
  end
  albums_list
end
albums_long(path = '', node_id = @root_node) click to toggle source
# File lib/helpers/smugmug_api.rb, line 47
def albums_long(path = '', node_id = @root_node)
  album_list = []
  node_children(node_id)['Node'].each do |node|
    node_path = path.empty? ? node['Name'] : File.join(path, node['Name'])
    case node['Type']
    when 'Folder'
      album_list.concat(albums_long(node_path, node['NodeID']))
    when 'Album'
      album_list.push(path: node_path,
                      name: node['Name'],
                      web_uri: node['WebUri'],
                      node_uri: node['Uri'],
                      id: File.basename(node['Uris']['Album']['Uri']),
                      type: 'node')
    end
  end
  album_list
end
collect_images(images, album_id) click to toggle source
# File lib/helpers/smugmug_api.rb, line 232
def collect_images(images, album_id)
  return if images.empty?
  images = images.join(',') if images.is_a? Array
  post("/api/v2/album/#{album_id}!collectimages", 'CollectUris' => images)
end
delete_images(images) click to toggle source
# File lib/helpers/smugmug_api.rb, line 163
def delete_images(images)
  images = [images] unless images.is_a? Array
  images.each do |image|
    http(:delete, image[:uri])
  end
end
folders() click to toggle source
# File lib/helpers/smugmug_api.rb, line 74
def folders
  folder_list = []
  resp = get('/api/v2/folder/user/bcaldwell!folderlist')
  resp['FolderList'].each do |folder|
    folder_list.push(folder_parser(folder))
  end
  folder_list
end
get(url, params = nil, headers = {}) click to toggle source
# File lib/helpers/smugmug_api.rb, line 181
def get(url, params = nil, headers = {})
  url = url.tr(' ', '-')
  uri = URI.parse(url)
  uri.query = URI.encode_www_form(params) if params
  http(:get, uri.to_s, headers)
end
get_or_create_album(path, album_url: nil) click to toggle source

path is the full path with spaces

# File lib/helpers/smugmug_api.rb, line 84
def get_or_create_album(path, album_url: nil)
  folder_path = File.dirname(path).split('/').map(&:capitalize).join('/')
  album_name = File.basename(path).split(' ').map(&:capitalize).join(' ')
  album = nil

  folder = get_or_create_folder(folder_path)
  resp = get(folder[:albums_url])
  albums = get(folder[:albums_url])['Album'] if resp.key? 'Album'
  albums ||= []
  albums.each do |album_raw|
    next unless album_raw['Name'] == album_name
    album = album_parser(album_raw)
  end

  if album.nil?
    url = "/api/v2/folder/user/#{@user}"
    url += "/#{folder_path}" unless folder_path.empty?
    url += '!albums'
    album_url = album_name if album_url.nil?
    resp = post(url, Name: album_name,
                     UrlName: album_url.tr(' ', '-').capitalize,
                     Privacy: 'Unlisted',
                     SmugSearchable: 'No',
                     SortMethod: 'Date Taken',
                     LargestSize: 'X4Large',
                     SortDirection: 'Ascending',
                     WorldSearchable: false,
                     EXIF: false,
                     Printable: false,
                     Filenames: true)
    album_raw = resp['Album']
    album = album_parser(album_raw)
  end
  album
end
get_or_create_folder(path) click to toggle source
# File lib/helpers/smugmug_api.rb, line 120
def get_or_create_folder(path)
  parts = path.split('/')
  current_path = ''
  folder = nil

  parts.each do |part|
    part = part.capitalize
    new_path = current_path.empty? ? part : File.join(current_path, part)
    resp = http_raw(:get, "/api/v2/folder/user/#{@user}/#{new_path}")
    if resp.is_a? Net::HTTPSuccess
      folder_raw = JSON.parse(resp.body)['Response']['Folder']
      folder = folder_parser(folder_raw)
    else
      url = "/api/v2/folder/user/#{@user}"
      url += "/#{current_path}" unless current_path.empty?
      url += '!folders'
      resp = post(url, Name: part.capitalize,
                       UrlName: part.tr(' ', '-').capitalize,
                       Privacy: 'Unlisted')
      folder = folder_parser(resp['Folder'])
    end
    current_path = new_path
  end

  folder
end
image_list(album_id) click to toggle source
# File lib/helpers/smugmug_api.rb, line 170
def image_list(album_id)
  @images = images(album_id)
  @images.map { |i| i[:filename] }
end
images(album_id) click to toggle source
# File lib/helpers/smugmug_api.rb, line 147
def images(album_id)
  images = []
  start = 1
  count = 100
  loop do
    images_raw = get("/api/v2/album/#{album_id}!images", start: start, count: count)
    return [] unless images_raw.key? 'AlbumImage'
    images_raw['AlbumImage'].each do |image|
      images.push(imager_parser(image))
    end
    break if (start + count) > images_raw['Pages']['Total'].to_i
    start += count
  end
  images
end
move_images(images, album_id) click to toggle source
# File lib/helpers/smugmug_api.rb, line 238
def move_images(images, album_id)
  return if images.empty?
  images = images.join(',') if images.is_a? Array
  post("/api/v2/album/#{album_id}!moveimages", 'MoveUris' => images)
end
node_children(id) click to toggle source
# File lib/helpers/smugmug_api.rb, line 66
def node_children(id)
  get("/api/v2/node/#{id}!children", count: 100)
end
post(url, body = {}, headers = {}) click to toggle source
# File lib/helpers/smugmug_api.rb, line 188
def post(url, body = {}, headers = {})
  url = url.tr(' ', '-')
  headers['Accept'] = 'application/json'
  headers['Content-Type'] = 'application/json'
  headers['Connection'] = 'keep-alive'
  response = @http.post(url, body, headers)
  raise "Request failed\n#{URI.unescape(response.body)}" unless response.is_a? Net::HTTPSuccess
  JSON.parse(response.body)['Response']
end
request_access_token() click to toggle source
# File lib/helpers/smugmug_api.rb, line 252
def request_access_token
  @consumer = OAuth::Consumer.new(@secrets.api_key, @secrets.api_secret,
                                  site: OAUTH_ORIGIN,
                                  name: 'photo-helper',
                                  request_token_path: REQUEST_TOKEN_URL,
                                  authorize_path: AUTHORIZE_URL,
                                  access_token_path: ACCESS_TOKEN_URL)

  # Generate request token
  @request_token = @consumer.get_request_token

  # Get authorize URL
  @request_token.authorize_url

  url = add_auth_params(@request_token.authorize_url, 'Access' => 'Full', 'Permissions' => 'Modify')

  puts "Go to #{url} and enter shown 6 digit number:"
  verifier = STDIN.gets.strip

  # Now go to that url and when you're done authorization you can run the
  # following command where you put in the value for oauth_verifier that you got
  # from completely the above URL request:
  access_token = @request_token.get_access_token(oauth_verifier: verifier)

  puts "Add the following to your ejson file #{@secrets.ejson_config_file}:"
  puts "\"access_token\": \"#{access_token.token}\","
  puts "\"access_secret\": \"#{access_token.secret}\""
  exit 0
end
update_images(images, album_id, headers = {}, workers: 4, filename_as_title: false) click to toggle source
# File lib/helpers/smugmug_api.rb, line 223
def update_images(images, album_id, headers = {}, workers: 4, filename_as_title: false)
  Parallel.each(images, in_processes: workers, progress: 'Updating images') do |image|
    # replace not working, delete then upload
    http(:delete, image[:uri])
    upload(image[:file], album_id, headers, filename_as_title: filename_as_title)
    puts "Done #{image[:file]}\n"
  end
end
update_keywords(image, keywords, overwrite = false) click to toggle source
# File lib/helpers/smugmug_api.rb, line 244
def update_keywords(image, keywords, overwrite = false)
  return if image.nil?
  keywords = (image[:keywords] + keywords).uniq unless overwrite

  # inspect need outwise it isnt encoded right
  post("#{image[:image_uri]}?_method=PATCH", KeywordArray: keywords.inspect)
end
upload(image_path, album_id, headers = {}, filename_as_title: false) click to toggle source
# File lib/helpers/smugmug_api.rb, line 198
def upload(image_path, album_id, headers = {}, filename_as_title: false)
  image = File.open(image_path)

  headers.merge!('Content-Type' => MimeMagic.by_path(image_path).type,
                 'X-Smug-AlbumUri' => "/api/v2/album/#{album_id}",
                 'X-Smug-ResponseType' => 'JSON',
                 'X-Smug-Version' => 'v2',
                 'charset' => 'UTF-8',
                 'Accept' => 'JSON',
                 'X-Smug-FileName' => File.basename(image_path),
                 'Content-MD5' => Digest::MD5.file(image_path).hexdigest)

  headers['X-Smug-Title'] = File.basename(image_path, '.*') if filename_as_title

  resp = @uploader.post('/', image, headers)
  resp.body
end
upload_images(images, album_id, headers = {}, workers: 4, filename_as_title: false) click to toggle source
# File lib/helpers/smugmug_api.rb, line 216
def upload_images(images, album_id, headers = {}, workers: 4, filename_as_title: false)
  Parallel.each(images, in_processes: workers, progress: 'Uploading images') do |image|
    upload(image, album_id, headers, filename_as_title: filename_as_title)
    puts "Done #{image}\n"
  end
end
user() click to toggle source
# File lib/helpers/smugmug_api.rb, line 70
def user
  get('/api/v2!authuser')['User']
end

Private Instance Methods

add_auth_params(url, params) click to toggle source
# File lib/helpers/smugmug_api.rb, line 334
def add_auth_params(url, params)
  uri = URI.parse(url)
  new_query_ar = URI.decode_www_form(uri.query || '')
  params.to_a.each { |el| new_query_ar << el }
  uri.query = URI.encode_www_form(new_query_ar)
  uri.to_s
end
album_parser(album) click to toggle source
# File lib/helpers/smugmug_api.rb, line 311
def album_parser(album)
  { name: album['Name'],
    id: album['AlbumKey'],
    web_uri: album['WebUri'],
    images_uri: album['Uris']['AlbumImages']['Uri'],
    type: 'album' }
end
folder_parser(folder) click to toggle source
# File lib/helpers/smugmug_api.rb, line 300
def folder_parser(folder)
  {
    name: folder['Name'],
    url_name: folder['UrlName'],
    web_uri: folder['UrlPath'],
    uri: folder['Uri'],
    albums_url: folder['Uris']['FolderAlbums']['Uri'],
    type: 'folder'
  }
end
get_access_token(endpoint = API_ENDPOINT) click to toggle source
# File lib/helpers/smugmug_api.rb, line 290
def get_access_token(endpoint = API_ENDPOINT)
  @consumer = OAuth::Consumer.new(
    @secrets.api_key,
    @secrets.api_secret,
    site: endpoint
  )
  # # Create the access_token for all traffic
  OAuth::AccessToken.new(@consumer, @secrets.access_token, @secrets.access_secret)
end
http_raw(method, url, headers = {}, _body = nil) click to toggle source
# File lib/helpers/smugmug_api.rb, line 284
def http_raw(method, url, headers = {}, _body = nil)
  url = url.tr(' ', '-')
  headers['Accept'] = 'application/json'
  @http.request(method, url, headers)
end
imager_parser(image) click to toggle source
# File lib/helpers/smugmug_api.rb, line 319
def imager_parser(image)
  {
    title: image['Title'],
    filename: image['FileName'],
    caption: image['Caption'],
    keywords: image['KeywordArray'],
    id: image['ImageKey'],
    md5: image['ArchivedMD5'],
    uri: image['Uri'],
    image_uri: image['Uris']['Image']['Uri'],
    web_uri: image['WebUri'],
    type: 'image'
  }
end