motion-steward 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []