app_status_notification 0.9.0.beta1
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 +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +69 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +112 -0
- data/LICENSE +21 -0
- data/README.md +22 -0
- data/app_status_notification.gemspec +46 -0
- data/config/locales/en.yml +10 -0
- data/config/locales/zh.yml +54 -0
- data/config/notification.yml +30 -0
- data/exe/app_status_notification +5 -0
- data/lib/app_status_notification.rb +24 -0
- data/lib/app_status_notification/command.rb +64 -0
- data/lib/app_status_notification/config.rb +234 -0
- data/lib/app_status_notification/connect_api.rb +92 -0
- data/lib/app_status_notification/connect_api/auth.rb +44 -0
- data/lib/app_status_notification/connect_api/clients/app.rb +46 -0
- data/lib/app_status_notification/connect_api/clients/app_store_version.rb +28 -0
- data/lib/app_status_notification/connect_api/clients/build.rb +29 -0
- data/lib/app_status_notification/connect_api/model.rb +152 -0
- data/lib/app_status_notification/connect_api/models/app.rb +27 -0
- data/lib/app_status_notification/connect_api/models/app_store_version.rb +84 -0
- data/lib/app_status_notification/connect_api/models/app_store_version_submission.rb +15 -0
- data/lib/app_status_notification/connect_api/models/build.rb +30 -0
- data/lib/app_status_notification/connect_api/models/pre_release_version.rb +16 -0
- data/lib/app_status_notification/connect_api/response.rb +102 -0
- data/lib/app_status_notification/error.rb +38 -0
- data/lib/app_status_notification/helper.rb +16 -0
- data/lib/app_status_notification/notification.rb +57 -0
- data/lib/app_status_notification/notifications/adapter.rb +25 -0
- data/lib/app_status_notification/notifications/dingtalk.rb +59 -0
- data/lib/app_status_notification/notifications/slack.rb +62 -0
- data/lib/app_status_notification/notifications/wecom.rb +48 -0
- data/lib/app_status_notification/runner.rb +409 -0
- data/lib/app_status_notification/store.rb +68 -0
- data/lib/app_status_notification/version.rb +5 -0
- data/lib/app_status_notification/watchman.rb +42 -0
- metadata +283 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppStatusNotification
|
4
|
+
class ConnectAPI
|
5
|
+
module Client
|
6
|
+
module AppStoreVersion
|
7
|
+
def versions(query = {})
|
8
|
+
get('appStoreVersions', query)
|
9
|
+
end
|
10
|
+
|
11
|
+
def version(id, query = {})
|
12
|
+
get("appStoreVersions/#{id}", query)
|
13
|
+
end
|
14
|
+
|
15
|
+
def select_version_build(id, build_id:)
|
16
|
+
body = {
|
17
|
+
data: {
|
18
|
+
type: 'builds',
|
19
|
+
id: build_id
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
patch("appStoreVersions/#{id}/relationships/build", body: body)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppStatusNotification
|
4
|
+
class ConnectAPI
|
5
|
+
module Client
|
6
|
+
module Build
|
7
|
+
def app_latest_build(id)
|
8
|
+
app_builds(id, limit: 1).to_model
|
9
|
+
end
|
10
|
+
|
11
|
+
def app_builds(id, **kargs)
|
12
|
+
kargs = kargs.merge(filter: { app: id })
|
13
|
+
builds(**kargs)
|
14
|
+
end
|
15
|
+
|
16
|
+
def builds(limit: 200, sort: '-uploadedDate',
|
17
|
+
includes: ConnectAPI::Model::Build::ESSENTIAL_INCLUDES,
|
18
|
+
**kargs)
|
19
|
+
|
20
|
+
kargs = kargs.merge(limit: limit)
|
21
|
+
.merge(sort: sort)
|
22
|
+
.merge(include: includes)
|
23
|
+
|
24
|
+
get('builds', **kargs)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module AppStatusNotification
|
6
|
+
class ConnectAPI
|
7
|
+
module Platform
|
8
|
+
IOS = 'IOS'
|
9
|
+
MAC_OS = 'MAC_OS'
|
10
|
+
TV_OS = 'TV_OS'
|
11
|
+
WATCH_OS = 'WATCH_OS'
|
12
|
+
|
13
|
+
ALL = [IOS, MAC_OS, TV_OS, WATCH_OS]
|
14
|
+
end
|
15
|
+
|
16
|
+
module ProcessStatus
|
17
|
+
PROCESSING = 'PROCESSING'
|
18
|
+
FAILED = 'FAILED'
|
19
|
+
INVALID = 'INVALID'
|
20
|
+
VALID = 'VALID'
|
21
|
+
end
|
22
|
+
|
23
|
+
module Model
|
24
|
+
def self.included(base)
|
25
|
+
Parser.types ||= []
|
26
|
+
Parser.types << base
|
27
|
+
base.extend(Parser)
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :id
|
31
|
+
attr_reader :attributes
|
32
|
+
attr_reader :rate
|
33
|
+
|
34
|
+
def initialize(id, attributes, rate)
|
35
|
+
@id = id
|
36
|
+
@attributes = attributes
|
37
|
+
@rate = rate
|
38
|
+
|
39
|
+
update_attributes(attributes)
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_attributes(attributes)
|
43
|
+
(attributes || []).each do |key, value|
|
44
|
+
|
45
|
+
method = "#{key.to_s.underscore}=".to_sym
|
46
|
+
self.send(method, value) if self.respond_to?(method)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module Parser
|
52
|
+
class << self
|
53
|
+
attr_accessor :types
|
54
|
+
attr_accessor :types_cache
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.parse(response, rate)
|
58
|
+
body = response.body
|
59
|
+
data = body['data']
|
60
|
+
raise ConnectAPIError, 'No data' unless data
|
61
|
+
|
62
|
+
included = body['included'] || []
|
63
|
+
if data.kind_of?(Hash)
|
64
|
+
inflate_model(data, included, rate: rate)
|
65
|
+
elsif data.kind_of?(Array)
|
66
|
+
return data.map do |model_data|
|
67
|
+
inflate_model(model_data, included, rate: rate)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise ConnectAPIError, "'data' is neither a hash nor an array"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.inflate_model(model_data, included, rate: {})
|
75
|
+
# Find class
|
76
|
+
type_class = find_class(model_data)
|
77
|
+
raise "No type class found for #{model_data['type']}" unless type_class
|
78
|
+
|
79
|
+
# Get id and attributes needed for inflating
|
80
|
+
id = model_data['id']
|
81
|
+
attributes = model_data['attributes']
|
82
|
+
|
83
|
+
# Instantiate object and inflate relationships
|
84
|
+
relationships = model_data['relationships'] || []
|
85
|
+
type_instance = type_class.new(id, attributes, rate)
|
86
|
+
|
87
|
+
inflate_model_relationships(type_instance, relationships, included)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.find_class(model_data)
|
91
|
+
# Initialize cache
|
92
|
+
@types_cache ||= {}
|
93
|
+
|
94
|
+
# Find class in cache
|
95
|
+
type_string = model_data['type']
|
96
|
+
type_class = @types_cache[type_string]
|
97
|
+
return type_class if type_class
|
98
|
+
|
99
|
+
# Find class in array
|
100
|
+
type_class = @types.find do |type|
|
101
|
+
type.type == type_string
|
102
|
+
end
|
103
|
+
|
104
|
+
# Cache and return class
|
105
|
+
type_class
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.inflate_model_relationships(type_instance, relationships, included)
|
109
|
+
# Relationship attributes to set
|
110
|
+
attributes = {}
|
111
|
+
|
112
|
+
# 1. Iterate over relationships
|
113
|
+
# 2. Find id and type
|
114
|
+
# 3. Find matching id and type in included
|
115
|
+
# 4. Inflate matching data and set in attributes
|
116
|
+
relationships.each do |key, value|
|
117
|
+
# Validate data exists
|
118
|
+
value_data_or_datas = value['data']
|
119
|
+
next unless value_data_or_datas
|
120
|
+
|
121
|
+
# Map an included data object
|
122
|
+
map_data = lambda do |value_data|
|
123
|
+
id = value_data['id']
|
124
|
+
type = value_data['type']
|
125
|
+
|
126
|
+
relationship_data = included.find do |included_data|
|
127
|
+
id == included_data['id'] && type == included_data['type']
|
128
|
+
end
|
129
|
+
|
130
|
+
inflate_model(relationship_data, included) if relationship_data
|
131
|
+
end
|
132
|
+
|
133
|
+
# Map a hash or an array of data
|
134
|
+
if value_data_or_datas.kind_of?(Hash)
|
135
|
+
attributes[key] = map_data.call(value_data_or_datas)
|
136
|
+
elsif value_data_or_datas.kind_of?(Array)
|
137
|
+
attributes[key] = value_data_or_datas.map(&map_data)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
type_instance.update_attributes(attributes)
|
142
|
+
type_instance
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
require 'app_status_notification/connect_api/models/app'
|
149
|
+
require 'app_status_notification/connect_api/models/app_store_version'
|
150
|
+
require 'app_status_notification/connect_api/models/build'
|
151
|
+
require 'app_status_notification/connect_api/models/pre_release_version'
|
152
|
+
require 'app_status_notification/connect_api/models/app_store_version_submission'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_status_notification/connect_api/model'
|
4
|
+
|
5
|
+
module AppStatusNotification::ConnectAPI::Model
|
6
|
+
class App
|
7
|
+
include AppStatusNotification::ConnectAPI::Model
|
8
|
+
|
9
|
+
attr_accessor :name
|
10
|
+
attr_accessor :bundle_id
|
11
|
+
attr_accessor :sku
|
12
|
+
attr_accessor :primary_locale
|
13
|
+
attr_accessor :is_opted_in_to_distribute_ios_app_on_mac_app_store
|
14
|
+
attr_accessor :removed
|
15
|
+
attr_accessor :is_aag
|
16
|
+
attr_accessor :available_in_new_territories
|
17
|
+
attr_accessor :content_rights_declaration
|
18
|
+
|
19
|
+
# include
|
20
|
+
attr_accessor :app_store_versions
|
21
|
+
attr_accessor :builds
|
22
|
+
|
23
|
+
def self.type
|
24
|
+
'apps'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_status_notification/connect_api/model'
|
4
|
+
|
5
|
+
module AppStatusNotification::ConnectAPI::Model
|
6
|
+
class AppStoreVersion
|
7
|
+
include AppStatusNotification::ConnectAPI::Model
|
8
|
+
|
9
|
+
attr_accessor :platform
|
10
|
+
attr_accessor :version_string
|
11
|
+
attr_accessor :app_store_state
|
12
|
+
attr_accessor :release_type
|
13
|
+
attr_accessor :earliest_release_date # 2020-06-17T12:00:00-07:00
|
14
|
+
attr_accessor :uses_idfa
|
15
|
+
attr_accessor :downloadable
|
16
|
+
attr_accessor :created_date
|
17
|
+
attr_accessor :version
|
18
|
+
attr_accessor :uploaded_date
|
19
|
+
attr_accessor :expiration_date
|
20
|
+
attr_accessor :expired
|
21
|
+
attr_accessor :store_icon
|
22
|
+
attr_accessor :watch_store_icon
|
23
|
+
attr_accessor :copyright
|
24
|
+
attr_accessor :min_os_version
|
25
|
+
|
26
|
+
# include
|
27
|
+
attr_accessor :app
|
28
|
+
attr_accessor :app_store_version_submission
|
29
|
+
attr_accessor :build
|
30
|
+
|
31
|
+
ESSENTIAL_INCLUDES = [
|
32
|
+
'app',
|
33
|
+
'appStoreVersionSubmission',
|
34
|
+
'build'
|
35
|
+
].join(',')
|
36
|
+
|
37
|
+
module AppStoreState
|
38
|
+
READY_FOR_SALE = 'READY_FOR_SALE'
|
39
|
+
PROCESSING_FOR_APP_STORE = 'PROCESSING_FOR_APP_STORE'
|
40
|
+
PENDING_DEVELOPER_RELEASE = 'PENDING_DEVELOPER_RELEASE'
|
41
|
+
IN_REVIEW = 'IN_REVIEW'
|
42
|
+
WAITING_FOR_REVIEW = 'WAITING_FOR_REVIEW'
|
43
|
+
DEVELOPER_REJECTED = 'DEVELOPER_REJECTED'
|
44
|
+
REJECTED = 'REJECTED'
|
45
|
+
PREPARE_FOR_SUBMISSION = 'PREPARE_FOR_SUBMISSION'
|
46
|
+
METADATA_REJECTED = 'METADATA_REJECTED'
|
47
|
+
INVALID_BINARY = 'INVALID_BINARY'
|
48
|
+
end
|
49
|
+
|
50
|
+
module ReleaseType
|
51
|
+
AFTER_APPROVAL = 'AFTER_APPROVAL'
|
52
|
+
MANUAL = 'MANUAL'
|
53
|
+
SCHEDULED = 'SCHEDULED'
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.type
|
57
|
+
'appStoreVersions'
|
58
|
+
end
|
59
|
+
|
60
|
+
def on_sale?
|
61
|
+
app_store_state == AppStoreState::READY_FOR_SALE ||
|
62
|
+
app_store_state == AppStoreState::PROCESSING_FOR_APP_STORE
|
63
|
+
end
|
64
|
+
|
65
|
+
def in_review?
|
66
|
+
app_store_state == AppStoreState::IN_REVIEW
|
67
|
+
end
|
68
|
+
|
69
|
+
def preparing?
|
70
|
+
app_store_state == AppStoreState::PREPARE_FOR_SUBMISSION ||
|
71
|
+
app_store_state == AppStoreState::DEVELOPER_REJECTED
|
72
|
+
end
|
73
|
+
|
74
|
+
def rejected?
|
75
|
+
app_store_state == AppStoreState::REJECTED ||
|
76
|
+
app_store_state == AppStoreState::METADATA_REJECTED ||
|
77
|
+
app_store_state == AppStoreState::INVALID_BINARY
|
78
|
+
end
|
79
|
+
|
80
|
+
def editable?
|
81
|
+
preparing? || rejected?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_status_notification/connect_api/model'
|
4
|
+
|
5
|
+
module AppStatusNotification::ConnectAPI::Model
|
6
|
+
class AppStoreVersionSubmission
|
7
|
+
include AppStatusNotification::ConnectAPI::Model
|
8
|
+
|
9
|
+
attr_accessor :can_reject
|
10
|
+
|
11
|
+
def self.type
|
12
|
+
'appStoreVersionSubmissions'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_status_notification/connect_api/model'
|
4
|
+
|
5
|
+
module AppStatusNotification::ConnectAPI::Model
|
6
|
+
class Build
|
7
|
+
include AppStatusNotification::ConnectAPI::Model
|
8
|
+
|
9
|
+
attr_accessor :version
|
10
|
+
attr_accessor :uploaded_date
|
11
|
+
attr_accessor :expiration_date
|
12
|
+
attr_accessor :expired
|
13
|
+
attr_accessor :min_os_version
|
14
|
+
attr_accessor :icon_asset_token
|
15
|
+
attr_accessor :processing_state
|
16
|
+
attr_accessor :uses_non_exempt_encryption
|
17
|
+
|
18
|
+
attr_accessor :app
|
19
|
+
attr_accessor :beta_app_review_submission
|
20
|
+
attr_accessor :beta_build_metrics
|
21
|
+
attr_accessor :build_beta_detail
|
22
|
+
attr_accessor :pre_release_version
|
23
|
+
|
24
|
+
ESSENTIAL_INCLUDES = 'app,preReleaseVersion'
|
25
|
+
|
26
|
+
def self.type
|
27
|
+
'builds'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_status_notification/connect_api/model'
|
4
|
+
|
5
|
+
module AppStatusNotification::ConnectAPI::Model
|
6
|
+
class PreReleaseVersion
|
7
|
+
include AppStatusNotification::ConnectAPI::Model
|
8
|
+
|
9
|
+
attr_accessor :version
|
10
|
+
attr_accessor :platform
|
11
|
+
|
12
|
+
def self.type
|
13
|
+
return 'preReleaseVersions'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module AppStatusNotification
|
6
|
+
class ConnectAPI
|
7
|
+
class Response
|
8
|
+
include Enumerable
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
attr_reader :response, :connection
|
12
|
+
|
13
|
+
def initialize(response, connection)
|
14
|
+
@response = response
|
15
|
+
@connection = connection
|
16
|
+
end
|
17
|
+
|
18
|
+
def_delegators :@response, :status, :headers
|
19
|
+
|
20
|
+
def request_url
|
21
|
+
@response.env.url
|
22
|
+
end
|
23
|
+
|
24
|
+
def rate
|
25
|
+
return {} unless value = response.headers[:x_rate_limit]
|
26
|
+
|
27
|
+
value.split(';').inject({}) do |r, q|
|
28
|
+
k, v = q.split(':')
|
29
|
+
case k
|
30
|
+
when 'user-hour-lim'
|
31
|
+
r.merge!(limit: v)
|
32
|
+
when 'user-hour-rem'
|
33
|
+
r.merge!(remaining: v)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def next_link
|
39
|
+
return if response.nil?
|
40
|
+
|
41
|
+
links = response[:links] || {}
|
42
|
+
links[:next]
|
43
|
+
end
|
44
|
+
|
45
|
+
# def all_pages
|
46
|
+
# next_pages(count: 0)
|
47
|
+
# end
|
48
|
+
|
49
|
+
# def next_pages(count: 1)
|
50
|
+
# count = count.to_i
|
51
|
+
# count = 0 if count <= 0
|
52
|
+
|
53
|
+
# responses = [self]
|
54
|
+
# counter = 0
|
55
|
+
|
56
|
+
# resp = self
|
57
|
+
# loop do
|
58
|
+
# resp = resp.next_page
|
59
|
+
# break if resp.nil?
|
60
|
+
|
61
|
+
# responses << resp
|
62
|
+
# counter += 1
|
63
|
+
|
64
|
+
# break if counter >= count
|
65
|
+
# end
|
66
|
+
|
67
|
+
# responses
|
68
|
+
# end
|
69
|
+
|
70
|
+
# def next_url
|
71
|
+
# return if response.nil?
|
72
|
+
|
73
|
+
# links = response[:links] || {}
|
74
|
+
# links[:next]
|
75
|
+
# end
|
76
|
+
|
77
|
+
# def next_page
|
78
|
+
# return unless url = next_url
|
79
|
+
|
80
|
+
# Response.new(connection.get(url), connection)
|
81
|
+
# end
|
82
|
+
|
83
|
+
def to_model
|
84
|
+
to_models.first
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_models
|
88
|
+
return [] if response.nil?
|
89
|
+
|
90
|
+
model_or_models = ConnectAPI::Parser.parse(response, rate)
|
91
|
+
[model_or_models].flatten
|
92
|
+
end
|
93
|
+
|
94
|
+
def each(&block)
|
95
|
+
to_models.each do |model|
|
96
|
+
yield(model)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|