motion-steward 1.0.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: adbe9735dc7992626d91305de5fe4f802a3499cb
4
+ data.tar.gz: f340890903488d8d0234df95c232a8d62733e8df
5
+ SHA512:
6
+ metadata.gz: ad68238fb4e2d9a3ef4374cfa5f732fc81404a832654080237298fa3c9e7e3451c0bf0c6cdbec24c94953c20d2e3b2f01865094d50ab7248fa8c09cd4cfc747e
7
+ data.tar.gz: b9b10c6d7190fd8264f81b500ba988bb4073bbd4f513e544def24be9ddb1650090b57cf2016832e80fae8d3edbbd09642c712dcef2484f4ab32f9c060c25f2f8
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fastlane'
4
+ require 'httparty'
5
+ require 'motion-steward'
6
+
7
+ def options
8
+ [:research]
9
+ end
10
+
11
+ def list_apps
12
+ Spaceship.app.all.map do |a|
13
+ puts "#{a.name}: #{a.bundle_id}"
14
+ end
15
+ end
16
+
17
+ def go_to_apps
18
+ puts 'opening https://developer.apple.com/account/ios/identifier/bundle/'
19
+ system 'open https://developer.apple.com/account/ios/identifier/bundle/'
20
+ end
21
+
22
+ def get_udid_of_connected_devices
23
+ script = <<~SCRIPT
24
+ i=0
25
+ for line in $(system_profiler SPUSBDataType | sed -n -e '/iPad/,/Serial/p' -e '/iPhone/,/Serial/p' | grep "Serial Number:" | awk -F ": " '{print $2}'); do
26
+ UDID=${line}
27
+ echo $UDID
28
+ udid_array[i]=${line}
29
+ i=$(($i+1))
30
+ done
31
+
32
+ cnt=${#udid_array[@]}
33
+ for ((i=0;i<cnt;i++)); do
34
+ echo ${udid_array[i]}
35
+ done
36
+ SCRIPT
37
+
38
+ system script
39
+ end
40
+
41
+ def gets
42
+ STDIN.gets.chomp
43
+ end
44
+
45
+ def prompt_for_response message
46
+ message += ' ' if message[-1] != ' '
47
+ print message
48
+ gets
49
+ end
50
+
51
+ def research
52
+ term = prompt_for_response "Enter a search term or app name, and I'll return a return what people will see when the search for that term (along with some fancy data):"
53
+ MotionSteward::AppStoreResearch.analyze(term)
54
+ end
55
+
56
+ def help_with_certificates
57
+ production = Spaceship.certificate.all.find_all { |c| c.is_a? Spaceship::Portal::Certificate::Production }.first
58
+
59
+ development = Spaceship.certificate.all.find_all { |c| c.is_a? Spaceship::Portal::Certificate::Development }.first
60
+
61
+ if production.nil? or development.nil?
62
+ puts "You need to create a Production and Development certificate. This script doesn't support that stuff yet."
63
+ system "open https://developer.apple.com/account/ios/certificate/"
64
+ exit(0)
65
+ end
66
+
67
+ [development, production]
68
+ end
69
+
70
+ def create_app
71
+ puts <<~INSTRUCTIONS
72
+ I need the name of your app. Here are some tips:
73
+
74
+ - If you haven't done so, you should really run the research command before committing to a name for your app.
75
+ - Using spaces is fine.
76
+ - Using a hypen after the main app name (eg: "Metal Gear - Tactical Espionage") is fine.
77
+ - Make the app name memorable because this is what people will use to find your app in the App Store.
78
+ - Here are examples of BAD app names:
79
+ - "Threes!" Why? Because it has an exclamation point (punctuation) in the main app name. Apps that use punctuation are hard to find.
80
+ - "T.L.D.R. Reddit" Why? Because it has periods (punctuation) in the main app name. Apps that use punctuation are hard to find.
81
+ - "Heroes Fighting" Why? Heroes is a saturated app keyword (there are many others). It'll make your app incredibly difficult to find.
82
+
83
+ INSTRUCTIONS
84
+
85
+ continue = false
86
+ while !continue
87
+ print "Pressure's on, what will you name your app?: "
88
+ app_name = gets
89
+ puts ""
90
+ print 'Given the results above, do you want to change the name of your app? y/n: '
91
+ continue = true if gets == 'n'
92
+ end
93
+ puts 'I need an app website/company website to generate an app ID. Examples: amirrajan.net, rubymotion.com'
94
+ website = gets
95
+ recommended_app_id = website.split('.').reverse.join '.'
96
+ recommended_app_id += '.' + app_name.downcase.delete(' ')
97
+ app_id = recommended_app_id
98
+ puts "Alright, here is the app that I'll create for you: "
99
+ puts app_name
100
+ puts app_id
101
+ puts 'Everything look good? y/n: '
102
+ if gets == 'n'
103
+ print 'What would you like the App ID to be? '
104
+ app_id = gets
105
+ end
106
+
107
+ Spaceship.app.create!(bundle_id: app_id, name: app_name)
108
+
109
+ puts "If all went well, [#{app_name}] has now been created. Select list_apps to continue."
110
+ end
111
+
112
+ continue = true
113
+
114
+ while continue
115
+ puts ''
116
+ puts 'Options:'
117
+ puts ''
118
+ options.map { |o| puts "- #{o}" }
119
+ puts '- exit'
120
+ puts ''
121
+
122
+ print 'What would you like to do (choose from the list above): '
123
+
124
+ input = gets
125
+
126
+ if input == 'exit'
127
+ exit(0)
128
+ end
129
+
130
+ if options.include? input.to_sym
131
+ system 'clear'
132
+ send(input.to_sym)
133
+ end
134
+ end
@@ -0,0 +1,94 @@
1
+ require 'date'
2
+ require 'motion-steward/app_store_search'
3
+
4
+ class Integer
5
+ def commas
6
+ self #=> 12345678
7
+ .to_s #=> "12345678"
8
+ .reverse #=> "87654321"
9
+ .scan(/\d{1,3}/) #=> ["876","543","21"]
10
+ .join(",") #=> "876,543,21"
11
+ .reverse #=> "12,345,678"
12
+ end
13
+ end
14
+
15
+ class MotionSteward::AppStoreResearch
16
+ def self.analyze name
17
+ apps = MotionSteward::AppStoreSearch.search_for_app(name)
18
+
19
+ if apps.any? { |a| a[:track_name] == name }
20
+ puts "WARNING: You can't name your app \"#{name}\". Because there is already an app out there with that exact name."
21
+ end
22
+
23
+ apps.each do |a|
24
+ puts a[:track_name] + " (Category: #{a[:genres].join(', ')}, Price: #{(a[:price] || 0)})"
25
+
26
+ release_score = determine_release_score(a)
27
+ if release_score == :green
28
+ puts ' - App has had recent updates.'
29
+ elsif release_score == :yellow
30
+ puts " - It's been a while since this app has released an update, but stable apps usually don't release more than once a year."
31
+ else
32
+ puts " - It's been over #{months_between(a[:current_version_release_date], Date.today)} months since this app has released. Thats pretty bad, and may be an indicator of a dead app (number of ratings may say otherwise)."
33
+ end
34
+
35
+ user_rating_count = a[:user_rating_count] || 0
36
+
37
+ if user_rating_count < 99
38
+ puts ' - App has very few ratings, which usually means very few downloads.'
39
+ elsif user_rating_count < 300
40
+ puts ' - App has a moderate number of reviews. If the app has been recently updated, then this is probably a new app.'
41
+ elsif user_rating_count > 10_000
42
+ puts " - App has an astronomical number of reviews (#{user_rating_count.commas}). If your app is similar to this one, you probably shouldn't build yours because you have little to no chance of \"beating them\"."
43
+ elsif user_rating_count > 5_000
44
+ puts " - App has a very high number of reviews (#{user_rating_count.commas} with an average rating of #{a[:average_user_rating]}). If you're app is similar to this one, you've got some serious competition. Success is unlikely."
45
+ else
46
+ puts " - App has a solid number of reviews (#{user_rating_count.commas} with an average rating of #{a[:average_user_rating]}). If you're app is similar to this one, you've got some competition, but you may be able to \"beat them\" if they have a low rating."
47
+ end
48
+
49
+ life_time_of_app = months_between(a[:release_date], Date.today)
50
+
51
+ if life_time_of_app.zero?
52
+ puts " - This app has been released recently. I can't project any revenue numbers because of this (try again in a month)."
53
+ elsif user_rating_count.zero?
54
+ puts ' - This app has no ratings. It either has a very poor review conversion rate, or (more likely) has never been downloaded.'
55
+ else
56
+ if a[:price].zero?
57
+ money_per_download = (1.99 * 0.7)
58
+ life_time_revenue_top_end = (((user_rating_count * 0.05) * 100) * money_per_download).round.to_i
59
+ industry = (((user_rating_count * 0.02) * 50) * money_per_download).round.to_i
60
+ life_time_revenue_bottom_end = (((user_rating_count * 0.005) * 20) * money_per_download).round.to_i
61
+ else
62
+ money_per_download = a[:price] * 0.7
63
+ life_time_revenue_top_end = ((user_rating_count * 100) * money_per_download).round.to_i
64
+ industry = ((user_rating_count * 50) * money_per_download).round.to_i
65
+ life_time_revenue_bottom_end = ((user_rating_count * 20) * money_per_download).round.to_i
66
+ end
67
+
68
+ monthly_revenue_top = life_time_revenue_top_end.fdiv(life_time_of_app).round.to_i
69
+ monthly_revenue_industry = industry.fdiv(life_time_of_app).round.to_i
70
+ monthly_revenue_bottom = life_time_revenue_bottom_end.fdiv(life_time_of_app).round.to_i
71
+
72
+ puts " - At best, this app has made $#{life_time_revenue_top_end.commas} over its lifetime (or $#{monthly_revenue_top.commas} a month)."
73
+ puts " - Based on my own industry measurements, this app probably made $#{industry.commas} over its lifetime (or $#{monthly_revenue_industry.commas} a month)."
74
+ puts " - Conservatively, this app has made $#{life_time_revenue_bottom_end.commas} over its lifetime (or $#{monthly_revenue_bottom.commas} a month)."
75
+ puts ''
76
+ end
77
+ end
78
+ end
79
+
80
+ def self.determine_release_score app
81
+ months_since_last_release = months_between(app[:current_version_release_date], Date.today)
82
+ if months_since_last_release.zero?
83
+ :green
84
+ elsif months_since_last_release < 16
85
+ :yellow
86
+ else
87
+ :red
88
+ end
89
+ end
90
+
91
+ def self.months_between start_date, end_date
92
+ (end_date.year * 12 + end_date.month) - (start_date.year * 12 + start_date.month)
93
+ end
94
+ end
@@ -0,0 +1,80 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'open-uri'
4
+ require 'pp'
5
+ require 'date'
6
+
7
+ class String
8
+ def underscore
9
+ self.gsub(/::/, '/').
10
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
11
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
12
+ tr("-", "_").
13
+ downcase
14
+ end
15
+ end
16
+
17
+ class MotionSteward::AppStoreSearch
18
+ def self.search_for_app term
19
+ results = get 'search',
20
+ media: 'software',
21
+ term: URI.encode(term),
22
+ country: 'us',
23
+ limit: 15
24
+
25
+ pluck results['results'],
26
+ %w(genres price track_name track_id average_user_rating user_rating_count release_date current_version_release_date price)
27
+ end
28
+
29
+ def self.pluck json, values
30
+ if json.is_a? Array
31
+ json.map do |o|
32
+ to_hash(o).select { |k, _| values.include? k.to_s }
33
+ end
34
+ else
35
+ to_hash(json).select { |k, _| values.include? k }
36
+ end
37
+ end
38
+
39
+ def self.construct_uri(path)
40
+ URI.parse('https://itunes.apple.com/' + path)
41
+ end
42
+
43
+ def self.convert_to_date_maybe k, v
44
+ return v if k !~ /date/i
45
+
46
+ begin
47
+ Date.parse(v).to_date
48
+ rescue
49
+ v
50
+ end
51
+ end
52
+
53
+ def self.to_hash h
54
+ h.inject({}) do |memo, (k, v)|
55
+ new_v = convert_to_date_maybe(k, v)
56
+ memo[k.underscore.to_sym] = new_v
57
+ memo
58
+ end
59
+ end
60
+
61
+ def self.get path, querystring
62
+ first = true
63
+ querystring.each do |k, v|
64
+ if first
65
+ path += '?'
66
+ first = false
67
+ else
68
+ path += '&'
69
+ end
70
+ path += "#{k}=#{v}"
71
+ end
72
+
73
+ uri = construct_uri path
74
+ http = Net::HTTP.new(uri.host, uri.port)
75
+ http.use_ssl = true
76
+ req = Net::HTTP::Get.new(uri.request_uri)
77
+ response = http.request(req)
78
+ JSON.parse(response.body)
79
+ end
80
+ end
@@ -0,0 +1,8 @@
1
+ class MotionSteward
2
+ def self.hi
3
+ puts 'hello world'
4
+ end
5
+ end
6
+
7
+ require 'motion-steward/app_store_search'
8
+ require 'motion-steward/app_store_research'
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motion-steward
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Amir Rajan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fastlane
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.32.1
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 2.32.1
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ description: There is a lot that goes into making a successful mobile app. This cli
34
+ tool will help you research possible app ideas and then scaffold a starting point
35
+ for a RubyMotion app.
36
+ email: ar@amirrajan.net
37
+ executables:
38
+ - motion-steward
39
+ extensions: []
40
+ extra_rdoc_files: []
41
+ files:
42
+ - bin/motion-steward
43
+ - lib/motion-steward.rb
44
+ - lib/motion-steward/app_store_research.rb
45
+ - lib/motion-steward/app_store_search.rb
46
+ homepage: http://rubymotion.com
47
+ licenses:
48
+ - MIT
49
+ metadata: {}
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 2.5.1
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: CLI app that helps steward one through RubyMotion.
70
+ test_files: []