├── .gitignore ├── Rakefile ├── Gemfile ├── lib ├── app_store_screenshots.rb └── app_store_screenshots │ ├── version.rb │ ├── get.rb │ └── cli.rb ├── bin ├── setup ├── app_store_screenshots └── console ├── circle.yml ├── app_store_screenshots.gemspec ├── LICENSE ├── spec ├── get_spec.rb └── cli_spec.rb └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.jpeg 3 | *.json 4 | *.gem 5 | *.error 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | task :default => :spec 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | gem 'rspec_junit_formatter', '0.2.2' # test meta data / ci 5 | gem 'rspec', '~> 3.4.0' # tests' 6 | -------------------------------------------------------------------------------- /lib/app_store_screenshots.rb: -------------------------------------------------------------------------------- 1 | require "app_store_screenshots/cli" 2 | 3 | # Grab screenshots from the App Store. 4 | module AppStoreScreenshots 5 | end 6 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 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 9 | -------------------------------------------------------------------------------- /bin/app_store_screenshots: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $LOAD_PATH.push File.expand_path('../../lib', __FILE__) 3 | 4 | require 'app_store_screenshots' 5 | 6 | AppStoreScreenshots.cli 7 | -------------------------------------------------------------------------------- /lib/app_store_screenshots/version.rb: -------------------------------------------------------------------------------- 1 | module AppStoreScreenshots 2 | APP = 'app_store_screenshots' 3 | SUMMARY = 'Grab screenshots from the App Store.' 4 | VERSION = '0.2.0' 5 | end 6 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | ruby: 3 | version: 2.2.0 4 | test: 5 | override: 6 | - bundle exec rspec -fd --color -r rspec_junit_formatter --format RspecJunitFormatter -o $CIRCLE_TEST_REPORTS/rspec/junit.xml 7 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "app_store_screenshots" 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 15 | -------------------------------------------------------------------------------- /app_store_screenshots.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'app_store_screenshots/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = AppStoreScreenshots::APP 8 | spec.version = AppStoreScreenshots::VERSION 9 | spec.authors = ['Daniel Khamsing'] 10 | spec.email = ['dkhamsing8@gmail.com'] 11 | 12 | spec.summary = AppStoreScreenshots::SUMMARY 13 | spec.description = spec.summary 14 | spec.homepage = 'https://github.com/dkhamsing/app_store_screenshots' 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 | end 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/get_spec.rb: -------------------------------------------------------------------------------- 1 | require 'app_store_screenshots' 2 | 3 | describe AppStoreScreenshots do 4 | describe "get" do 5 | 6 | context "given the instagram itunes id" do 7 | id = '389801252' 8 | value = AppStoreScreenshots::Get.new(id).screenshots.count 9 | expected = 5 10 | it "finds the screenshots" do 11 | expect(value).to eql(expected) 12 | end 13 | end 14 | 15 | context "given the instagram itunes id" do 16 | id = '389801252' 17 | value = AppStoreScreenshots::Get.new(id).error 18 | expected = false 19 | it "no error" do 20 | expect(value).to eql(expected) 21 | end 22 | end 23 | 24 | context "given invalid id" do 25 | id = '123' 26 | value = AppStoreScreenshots::Get.new(id).screenshots.count 27 | expected = 0 28 | it "finds no screenshots" do 29 | expect(value).to eql(expected) 30 | end 31 | end 32 | 33 | context "given invalid id" do 34 | id = '123' 35 | value = AppStoreScreenshots::Get.new(id).error 36 | expected = true 37 | it "has error" do 38 | expect(value).to eql(expected) 39 | end 40 | end 41 | 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/cli_spec.rb: -------------------------------------------------------------------------------- 1 | require 'app_store_screenshots' 2 | 3 | describe AppStoreScreenshots do 4 | describe "cli id" do 5 | 6 | context "given an itunes url" do 7 | input = 'https://itunes.apple.com/us/app/instagram/id389801252?mt=8' 8 | value = AppStoreScreenshots.cli_id input 9 | expected = '389801252' 10 | it "finds the id" do 11 | expect(value).to eql(expected) 12 | end 13 | end 14 | 15 | context "given part of an itunes url" do 16 | input = 'id389801252?mt=8' 17 | value = AppStoreScreenshots.cli_id input 18 | expected = '389801252' 19 | it "finds the id" do 20 | expect(value).to eql(expected) 21 | end 22 | end 23 | 24 | context "given an id" do 25 | input = '389801252' 26 | value = AppStoreScreenshots.cli_id input 27 | expected = '389801252' 28 | it "validates" do 29 | expect(value).to eql(expected) 30 | end 31 | end 32 | 33 | context "given a string with id" do 34 | input = 'id389801252' 35 | value = AppStoreScreenshots.cli_id input 36 | expected = '389801252' 37 | it "finds the id" do 38 | expect(value).to eql(expected) 39 | end 40 | end 41 | 42 | context "given a string without id" do 43 | input = 'kjlda' 44 | value = AppStoreScreenshots.cli_id input 45 | expected = 'Error' 46 | it "finds an error" do 47 | expect(value).to include(expected) 48 | end 49 | end 50 | 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # app_store_screenshots 2 | 3 | Grab screenshots from the App Store. 4 | 5 | [![CircleCI](https://img.shields.io/circleci/project/github/dkhamsing/app_store_screenshots.svg)](https://circleci.com/gh/dkhamsing/app_store_screenshots) 6 | 7 | ## Installation 8 | 9 | $ git clone https://github.com/dkhamsing/app_store_screenshots.git 10 | $ cd app_store_screenshots 11 | $ rake install 12 | 13 | ## Usage 14 | 15 | ```shell 16 | app_store_screenshots [--open] [--save] 17 | # --open open screenshots in browser 18 | # --save save screenshots 19 | ``` 20 | 21 | ``` 22 | $ app_store_screenshots https://itunes.apple.com/us/app/instagram/id389801252?mt=8 23 | > app_store_screenshots 0.1.0 24 | > Getting screenshots for 389801252... 25 | > Found 5 screenshot(s): 26 | [ 27 | "http://a5.mzstatic.com/us/r30/Purple60/v4/4e/94/ef/4e94efe2-5ad7-bc50-1d6e-8d135f363d84/screen696x696.jpeg", 28 | "http://a3.mzstatic.com/us/r30/Purple60/v4/b6/fe/4e/b6fe4e9b-9cdc-4ad3-489e-f369e0f89918/screen696x696.jpeg", 29 | "http://a1.mzstatic.com/us/r30/Purple60/v4/ca/24/61/ca246154-134c-ccc7-7dc0-375522d25662/screen696x696.jpeg", 30 | "http://a2.mzstatic.com/us/r30/Purple18/v4/da/5f/fb/da5ffb51-349f-5d3b-aa23-1544b4f5d24a/screen696x696.jpeg", 31 | "http://a3.mzstatic.com/us/r30/Purple18/v4/51/71/e0/5171e09e-81ea-0014-17c5-73c410d20ded/screen696x696.jpeg" 32 | ] 33 | ``` 34 | 35 | This was pretty handy for [`osia`](https://github.com/dkhamsing/open-source-ios-apps/issues/431). 36 | 37 | ## Credits 38 | 39 | - [itunes_store_bot](https://github.com/stefano-bortolotti/itunes_store_bot) 40 | 41 | ## Contact 42 | 43 | - [github.com/dkhamsing](https://github.com/dkhamsing) 44 | - [twitter.com/dkhamsing](https://twitter.com/dkhamsing) 45 | 46 | ## License 47 | 48 | This project is available under the MIT license. See the [LICENSE](LICENSE) file for more info. 49 | -------------------------------------------------------------------------------- /lib/app_store_screenshots/get.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'net/http' 3 | require 'openssl' 4 | 5 | # Get app store screenshots. 6 | module AppStoreScreenshots 7 | class Get 8 | def initialize(app_id, app_country='us') 9 | @app_id = app_id 10 | @app_country = app_country 11 | 12 | @itunes_domain_url = 'itunes.apple.com' 13 | @itunes_http_options = {'User-Agent' => 'iTunes/11.1.5 (Macintosh; OS X 10.9.2) AppleWebKit/537.74.9'} 14 | 15 | @app_data = {} 16 | get_app_info 17 | end 18 | 19 | def error 20 | @app_data['screenshots'].count==0 21 | end 22 | 23 | def response 24 | @app_data['response'] 25 | end 26 | 27 | def screenshots 28 | @app_data['screenshots'] 29 | end 30 | 31 | def urls 32 | @app_data['urls'] 33 | end 34 | 35 | private 36 | 37 | def get_app_info 38 | http = Net::HTTP.new(@itunes_domain_url, Net::HTTP.https_default_port()) 39 | http.use_ssl = true 40 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE # disable ssl certificate check 41 | 42 | path = "/#{@app_country}/app/id#{@app_id}" 43 | 44 | response = http.request( Net::HTTP::Get.new( path + "&appVersion=current" , @itunes_http_options) ) 45 | 46 | if response.code != '200' 47 | puts "App Store store website communication (status-code: #{response.code})\n#{response.body}" 48 | else 49 | @app_data['response'] = response.body 50 | 51 | data = extract_app_data_from_raw_json( response.body ) 52 | @app_data['screenshots'] = data 53 | end 54 | 55 | data 56 | end 57 | 58 | def extract_app_data_from_raw_json(data) 59 | list = data.split 'url' 60 | 61 | shots = [] 62 | 63 | # look for screenshots with large size 64 | list.each do |x| 65 | if shots.count<5 66 | shots.push x.match('http.*jpeg')[0] if x.include?('mzstatic') && x.include?('696x') 67 | end 68 | end 69 | 70 | # look for any screenshots 71 | if shots.count==0 72 | list.each do |x| 73 | if shots.count<5 74 | shots.push x.match('http.*jpeg')[0] if x.include?('mzstatic') 75 | end 76 | end 77 | end 78 | 79 | @app_data['urls'] = list if shots.count == 0 80 | 81 | shots 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/app_store_screenshots/cli.rb: -------------------------------------------------------------------------------- 1 | require "app_store_screenshots/version" 2 | require "app_store_screenshots/get" 3 | 4 | FILEBASE = 'screenshots' 5 | OPT_OPEN = 'open' 6 | OPT_SAVE = 'save' 7 | 8 | # Command line interface. 9 | module AppStoreScreenshots 10 | class << self 11 | def cli() 12 | cli_put "#{APP} #{VERSION}" 13 | 14 | if ARGV.count == 0 15 | cli_put "Usage: #{APP} [--#{OPT_OPEN}] [--#{OPT_SAVE}]" 16 | exit 17 | end 18 | 19 | id = cli_id(ARGV[0]) 20 | if id.include? 'Error' 21 | cli_put id 22 | exit 23 | end 24 | 25 | cli_put "Getting screenshots for #{id}..." 26 | 27 | g = Get.new(id) 28 | if g.error 29 | r = g.response 30 | 31 | fn = "#{FILEBASE}-#{id}.error" 32 | File.open(fn, 'w') {|f| f.write r } 33 | 34 | cli_put "Error: no screenshots found, wrote #{fn}" 35 | exit 36 | end 37 | 38 | screenshots = g.screenshots 39 | cli_put "Found #{screenshots.count} screenshot(s):" 40 | 41 | list = [] 42 | screenshots.each do |x| 43 | list.push " \"#{x}\"" 44 | end 45 | 46 | s = "[ \n" 47 | s << list.join(", \n") 48 | s << "\n]" 49 | puts s 50 | 51 | if ARGV.join(' ').include? OPT_OPEN 52 | cli_put 'Opening screenshots in browser...' 53 | screenshots.each do |x| 54 | `open #{x}` 55 | end 56 | end 57 | 58 | if ARGV.join(' ').include? OPT_SAVE 59 | cli_put 'Saving screenshots...' 60 | 61 | curl_string = cli_curl_string screenshots, id 62 | `#{curl_string}` 63 | end 64 | 65 | fn = "#{FILEBASE}-#{id}.json" 66 | File.open(fn, 'w') {|f| f.write screenshots.to_json } 67 | # cli_put "Wrote: #{fn}" 68 | 69 | fn 70 | end 71 | 72 | def cli_put(o) 73 | puts "> #{o}" 74 | end 75 | 76 | def cli_curl_string(list, id) 77 | curl_string = 'curl' 78 | list.each_with_index do |x, i| 79 | begin 80 | ext = x.match(/[^\.]+$/)[0] 81 | rescue 82 | ext = 'jpeg' 83 | end 84 | 85 | curl_string << ' -o' 86 | curl_string << " #{id}-#{i}.#{ext} #{x}" 87 | end 88 | 89 | curl_string 90 | end 91 | 92 | def cli_id(input) 93 | id = input.sub('id', '') 94 | if id.include?('http') || id.include?('?') 95 | match = id.match /([0-9]*)\?/ 96 | if match.nil? 97 | return "Error: could not find id in #{id}" 98 | end 99 | id = match[0].sub('?', '') 100 | end 101 | 102 | valid = id.to_i 103 | unless valid>0 104 | return "Error: #{id} is not a valid id" 105 | end 106 | 107 | id 108 | end 109 | end 110 | end 111 | --------------------------------------------------------------------------------