xiaomi-push 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +13 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile +0 -1
- data/README.md +180 -44
- data/Rakefile +57 -26
- data/bin/xmp +1 -1
- data/lib/xiaomi/push.rb +6 -3
- data/lib/xiaomi/push/client.rb +70 -16
- data/lib/xiaomi/push/commands.rb +1 -0
- data/lib/xiaomi/push/commands/feedback.rb +4 -5
- data/lib/xiaomi/push/commands/message.rb +21 -12
- data/lib/xiaomi/push/commands/user.rb +69 -0
- data/lib/xiaomi/push/const.rb +20 -2
- data/lib/xiaomi/push/platforms/android.rb +17 -0
- data/lib/xiaomi/push/platforms/ios.rb +20 -0
- data/lib/xiaomi/push/services/feedback.rb +9 -2
- data/lib/xiaomi/push/services/job.rb +36 -0
- data/lib/xiaomi/push/services/message.rb +49 -56
- data/lib/xiaomi/push/services/messages.rb +91 -0
- data/lib/xiaomi/push/services/messages/android.rb +11 -1
- data/lib/xiaomi/push/services/messages/base.rb +108 -6
- data/lib/xiaomi/push/services/messages/ios.rb +21 -1
- data/lib/xiaomi/push/services/topic.rb +39 -2
- data/lib/xiaomi/push/services/user.rb +60 -0
- data/lib/xiaomi/push/version.rb +1 -1
- data/xiaomi-push.gemspec +8 -8
- metadata +27 -51
- data/.travis.yml +0 -3
- data/lib/xiaomi/push/devices/android.rb +0 -6
- data/lib/xiaomi/push/devices/ios.rb +0 -9
- data/lib/xiaomi/push/services/multi_messages.rb +0 -0
@@ -5,8 +5,15 @@ require 'xiaomi/push/services/messages/android'
|
|
5
5
|
module Xiaomi
|
6
6
|
module Push
|
7
7
|
module Services
|
8
|
+
# 单条消息类 API
|
9
|
+
#
|
10
|
+
# 允许向单个设备或多个设备发送同样的推送消息
|
11
|
+
#
|
12
|
+
# 设备的标识支持 reg_id/alias/user/topic/topics/all
|
13
|
+
#
|
14
|
+
# @attr [Client] context
|
8
15
|
class Message
|
9
|
-
|
16
|
+
# 消息类型模板
|
10
17
|
MESSAGE_TYPE = {
|
11
18
|
reg_id: {
|
12
19
|
uri: 'regid',
|
@@ -16,6 +23,10 @@ module Xiaomi
|
|
16
23
|
uri: 'alias',
|
17
24
|
query: 'alias'
|
18
25
|
},
|
26
|
+
user: {
|
27
|
+
uri: 'user_account',
|
28
|
+
query: 'user_account'
|
29
|
+
},
|
19
30
|
topic: {
|
20
31
|
uri: 'topic',
|
21
32
|
query: 'topic'
|
@@ -35,35 +46,67 @@ module Xiaomi
|
|
35
46
|
}
|
36
47
|
|
37
48
|
attr_reader :context
|
49
|
+
|
38
50
|
def initialize(context)
|
39
51
|
@context = context
|
40
52
|
end
|
41
53
|
|
54
|
+
# 推送消息
|
55
|
+
#
|
56
|
+
# @see https://dev.mi.com/console/doc/detail?pId=1163#_0
|
57
|
+
#
|
58
|
+
# @param (see Xiaomi::Push::Message::Base#initialize)
|
59
|
+
# @param [Hash, Message::IOS, Message::Android] options Hash 结构消息体 (详见 {Xiaomi::Push::Message::IOS}, {Message::Android})
|
60
|
+
# @return [Hash] 小米返回数据结构
|
61
|
+
#
|
62
|
+
# @raise [RequestError] 推送消息不满足 reg_id/alias/user/topic/topics/all 会引发异常
|
42
63
|
def send(**options)
|
43
64
|
type, value = fetch_message_type(options)
|
44
65
|
if type && value
|
45
66
|
url = @context.build_uri("message/#{type[:uri]}")
|
46
67
|
if options[:message].kind_of?Xiaomi::Push::Message::Base
|
47
68
|
options[:message].type(type[:query], value)
|
48
|
-
params = options[:message].
|
69
|
+
params = options[:message].to_params
|
49
70
|
else
|
50
71
|
params = options[:message]
|
51
72
|
params[type[:query].to_sym] = value
|
52
73
|
end
|
53
74
|
|
54
|
-
|
55
|
-
data = MultiJson.load r
|
75
|
+
@context.post(url, params)
|
56
76
|
else
|
57
|
-
raise Xiaomi::Push::RequestError, '
|
77
|
+
raise Xiaomi::Push::RequestError, '无效的消息类型,请检查是否符合这些类型: reg_id/alias/topic/topics/all'
|
58
78
|
end
|
59
79
|
end
|
60
80
|
|
81
|
+
# 获取消息的统计数据
|
82
|
+
#
|
83
|
+
# @example 获取 2017-09-01 到 2017-09-30 应用 com.icyleaf.app.helloworld 统计数据
|
84
|
+
# counters('20170901', '20170930', 'com.icyleaf.app.helloworld')
|
85
|
+
#
|
86
|
+
# @see https://dev.mi.com/console/doc/detail?pId=1163#_2
|
87
|
+
#
|
88
|
+
# @param [String] start_date 开始日期,格式 yyyyMMdd
|
89
|
+
# @param [String] end_date 结束日期,必须小于 30 天。格式 yyyyMMdd
|
90
|
+
# @param [String] package_name 包名,Android 为 package name,iOS 为 Bundle identifier
|
91
|
+
# @return [Hash] 小米返回数据结构
|
92
|
+
def counters(start_date, end_date, package_name)
|
93
|
+
url = @context.build_uri('stats/message/counters')
|
94
|
+
params = {
|
95
|
+
start_date: start_date,
|
96
|
+
end_date: end_date,
|
97
|
+
restricted_package_name: package_name
|
98
|
+
}
|
99
|
+
|
100
|
+
@context.get(url, params)
|
101
|
+
end
|
102
|
+
|
61
103
|
private
|
62
104
|
|
105
|
+
# 获取消息类型
|
63
106
|
def fetch_message_type(data)
|
64
107
|
type, value = nil
|
65
108
|
MESSAGE_TYPE.select do |k,v|
|
66
|
-
if data.has_key?k
|
109
|
+
if data.has_key?(k)
|
67
110
|
type = v
|
68
111
|
value = data[k]
|
69
112
|
break
|
@@ -72,56 +115,6 @@ module Xiaomi
|
|
72
115
|
|
73
116
|
[type, value]
|
74
117
|
end
|
75
|
-
|
76
|
-
def valid?(params)
|
77
|
-
validates = {
|
78
|
-
'payload' => {
|
79
|
-
require: true,
|
80
|
-
},
|
81
|
-
'restricted_package_name' => {
|
82
|
-
require: true,
|
83
|
-
},
|
84
|
-
'pass_through' => {
|
85
|
-
require: true,
|
86
|
-
},
|
87
|
-
'title' => {
|
88
|
-
require: true,
|
89
|
-
},
|
90
|
-
'description' => {
|
91
|
-
require: true,
|
92
|
-
},
|
93
|
-
'notify_type' => {
|
94
|
-
require: true,
|
95
|
-
values: {
|
96
|
-
'DEFAULT_ALL' => -1,
|
97
|
-
'DEFAULT_SOUND' => 1,
|
98
|
-
'DEFAULT_VIBRATE' => 2,
|
99
|
-
'DEFAULT_LIGHTS' => 3
|
100
|
-
}
|
101
|
-
},
|
102
|
-
'time_to_live' => {
|
103
|
-
require: false,
|
104
|
-
},
|
105
|
-
'time_to_send' => {
|
106
|
-
require: false,
|
107
|
-
},
|
108
|
-
'notify_id' => {
|
109
|
-
require: false,
|
110
|
-
},
|
111
|
-
'extra.sound_uri' => {
|
112
|
-
require: false,
|
113
|
-
},
|
114
|
-
'extra.ticker' => {
|
115
|
-
require: false,
|
116
|
-
},
|
117
|
-
'extra.notify_foreground' => {
|
118
|
-
require: false,
|
119
|
-
},
|
120
|
-
'extra.notify_effect' => {
|
121
|
-
require: false,
|
122
|
-
},
|
123
|
-
}
|
124
|
-
end
|
125
118
|
end
|
126
119
|
end
|
127
120
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'xiaomi/push/services/messages/base'
|
2
|
+
require 'xiaomi/push/services/messages/ios'
|
3
|
+
require 'xiaomi/push/services/messages/android'
|
4
|
+
|
5
|
+
module Xiaomi
|
6
|
+
module Push
|
7
|
+
module Services
|
8
|
+
# 消息类 API
|
9
|
+
#
|
10
|
+
# 允许向多个设备发送不同的推送消息
|
11
|
+
#
|
12
|
+
# 设备的标识支持 reg_id/alias/user_account
|
13
|
+
#
|
14
|
+
# @attr [Client] context
|
15
|
+
class Messages
|
16
|
+
# 消息类型模板
|
17
|
+
MESSAGE_TYPE = {
|
18
|
+
reg_id: {
|
19
|
+
uri: 'regids',
|
20
|
+
keys: [:reg_id, :regid, :registration_id]
|
21
|
+
},
|
22
|
+
alias: {
|
23
|
+
uri: 'aliases',
|
24
|
+
keys: [:alias, :aliass, :aliases]
|
25
|
+
},
|
26
|
+
user: {
|
27
|
+
uri: 'user_accounts',
|
28
|
+
keys: [:user, :account, :useraccount, :user_account]
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
attr_reader :context
|
33
|
+
|
34
|
+
def initialize(context)
|
35
|
+
@context = context
|
36
|
+
end
|
37
|
+
|
38
|
+
# 推送消息
|
39
|
+
#
|
40
|
+
# @see https://dev.mi.com/console/doc/detail?pId=1163#_1_0
|
41
|
+
#
|
42
|
+
# @param [Hash] type 发送消息类型,可选 :reg_id, :alias, :user
|
43
|
+
# @param [Array] messages 消息结构消息体的数组 (详见 {Xiaomi::Push::Message::IOS}, {Message::Android})
|
44
|
+
# @return [Hash] 小米返回数据结构
|
45
|
+
#
|
46
|
+
# @raise [RequestError] 推送消息不满足 reg_id/alias/user 会引发异常
|
47
|
+
# @raise [RequestError] 消息体没有包含关键 target key 会引发异常
|
48
|
+
# @raise [RequestError] messages 不是数组存储的消息体时会引发异常
|
49
|
+
def send(type, messages = [])
|
50
|
+
url = @context.build_uri("multi_messages/#{request_uri(type)}")
|
51
|
+
params = request_params(type, messages)
|
52
|
+
@context.post(url, params)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# 获取消息类型
|
58
|
+
def request_uri(type)
|
59
|
+
MESSAGE_TYPE[type][:uri]
|
60
|
+
rescue NoMethodError
|
61
|
+
raise RequestError, '无效的消息类型,请检查是否符合这些类型: reg_id/alias/user'
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_params(type, messages)
|
65
|
+
raise RequestError, '消息必须是数组类型' unless messages.kind_of?(Array)
|
66
|
+
|
67
|
+
messages.each_with_object([]) do |message, obj|
|
68
|
+
message = options[:message].to_params if message.kind_of?(Xiaomi::Push::Message::Base)
|
69
|
+
target_key = target_key(type, message)
|
70
|
+
|
71
|
+
raise RequestError, "#{type.to_s} 消息缺失关键 Key,可设置为 #{MESSAGE_TYPE[type][:keys].join('/')} 均可:#{message}" unless target_key
|
72
|
+
|
73
|
+
obj.push({
|
74
|
+
target: message.delete(target_key),
|
75
|
+
message: message,
|
76
|
+
})
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def target_key(type, message)
|
81
|
+
keys = MESSAGE_TYPE[type][:keys]
|
82
|
+
message.each do |key, _|
|
83
|
+
return key if keys.include?(key)
|
84
|
+
end
|
85
|
+
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -1,8 +1,18 @@
|
|
1
1
|
module Xiaomi
|
2
2
|
module Push
|
3
3
|
module Message
|
4
|
+
# Android 消息数据体
|
5
|
+
#
|
6
|
+
# @attr [String] title 标题
|
7
|
+
# @attr [String] description 描述
|
8
|
+
# @attr [String] badge 角标, 默认 1
|
9
|
+
# @attr [String] sound 声音,默认 default
|
10
|
+
# @attr [String] pass_through 是否为穿透, 取值 0(默认,通知栏消息)/1(穿透)
|
11
|
+
# @attr [String] notify_type 提醒类型,取值 DEFAULT_ALL(默认)/DEFAULT_SOUND(提示音)/DEFAULT_VIBRATE(振动)/DEFAULT_LIGHTS(指示灯)
|
12
|
+
# @attr [Integer] notify_id 提醒,默认情况下,通知栏只显示一条推送消息。如果通知栏要显示多条推送消息,需要针对不同的消息设置不同的 notify_id
|
4
13
|
class Android < Base
|
5
|
-
attr_accessor :title, :description, :badge, :sound, :pass_through, :notify_type, :notify_id
|
14
|
+
attr_accessor :title, :description, :badge, :sound, :pass_through, :notify_type, :notify_id
|
15
|
+
|
6
16
|
def initialize(**params)
|
7
17
|
@title = params[:title]
|
8
18
|
@description = params[:description]
|
@@ -1,9 +1,23 @@
|
|
1
1
|
module Xiaomi
|
2
2
|
module Push
|
3
3
|
module Message
|
4
|
+
# 消息体的基本数据
|
5
|
+
#
|
6
|
+
# @abstract
|
7
|
+
# @attr [String] registration_id reg id
|
8
|
+
# @attr [String] alias 别名
|
9
|
+
# @attr [String] topic 标签
|
10
|
+
# @attr [String] user_account user account
|
11
|
+
# @attr [String] topics 多个标签
|
12
|
+
# @attr [String] topic_op 配合 topics 使用
|
13
|
+
# @attr [String] extras 额外参数
|
4
14
|
class Base
|
5
|
-
attr_accessor :registration_id, :alias, :topic, :topics, :topic_op, :extras
|
15
|
+
attr_accessor :registration_id, :alias, :topic, :user_account, :topics, :topic_op, :extras
|
6
16
|
|
17
|
+
# 设置或获取附加数据
|
18
|
+
# @param [String] key
|
19
|
+
# @param [String] value
|
20
|
+
# @return [void]
|
7
21
|
def extra(key, value = nil)
|
8
22
|
unless value
|
9
23
|
@extras[key]
|
@@ -12,21 +26,35 @@ module Xiaomi
|
|
12
26
|
end
|
13
27
|
end
|
14
28
|
|
29
|
+
# 设置或获取基本数据
|
30
|
+
#
|
31
|
+
# @param [String] key 设置 :registration_id, :alias, :topic, :user_account, :topics, :topic_op, :extras
|
32
|
+
# @param [String] value
|
33
|
+
# @return [void]
|
15
34
|
def type(key, value = nil)
|
16
35
|
key = "@#{key}"
|
17
36
|
|
18
37
|
if value
|
19
|
-
instance_variable_set
|
38
|
+
instance_variable_set(key, value)
|
20
39
|
else
|
21
|
-
instance_variable_get
|
40
|
+
instance_variable_get(key)
|
22
41
|
end
|
23
42
|
end
|
24
43
|
|
25
|
-
|
44
|
+
# 转换为字典
|
45
|
+
# @return [Hash] 消息体
|
46
|
+
def to_params
|
26
47
|
hash_data = {}
|
27
48
|
instance_variables.each do |ivar|
|
28
|
-
key = ivar
|
29
|
-
|
49
|
+
key = instance_key(ivar)
|
50
|
+
|
51
|
+
key = if ios? && ios10_struct?
|
52
|
+
ios10_struct(key)
|
53
|
+
else
|
54
|
+
extra_key(key)
|
55
|
+
end
|
56
|
+
|
57
|
+
value = instance_variable_get(ivar)
|
30
58
|
|
31
59
|
next unless value
|
32
60
|
|
@@ -42,6 +70,80 @@ module Xiaomi
|
|
42
70
|
|
43
71
|
hash_data
|
44
72
|
end
|
73
|
+
|
74
|
+
# 检查是否为 iOS 10 消息体
|
75
|
+
#
|
76
|
+
# @return [Bool]
|
77
|
+
def ios10_struct?
|
78
|
+
return @ios10_struct unless @ios10_struct.nil?
|
79
|
+
|
80
|
+
@ios10_struct = false
|
81
|
+
|
82
|
+
keys = instance_variables.map {|e| instance_key(e) }
|
83
|
+
%w(title subtitle).each do |key|
|
84
|
+
return @ios10_struct = true if keys.include?(key)
|
85
|
+
end
|
86
|
+
|
87
|
+
@ios10_struct
|
88
|
+
end
|
89
|
+
|
90
|
+
# 转换 iOS 10 消息的参数
|
91
|
+
#
|
92
|
+
# 仅转换 title, subtitle, body 和 description
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# ios10_struct('title') # => 'aps_proper_fields.title'
|
96
|
+
# ios10_struct('description') # => 'aps_proper_fields.body'
|
97
|
+
# ios10_struct('badge') # => 'badge'
|
98
|
+
#
|
99
|
+
# @param [String] key
|
100
|
+
# @return [Bool]
|
101
|
+
def ios10_struct(key)
|
102
|
+
key = 'body' if key == 'description'
|
103
|
+
return extra_key(key) unless %w(title subtitle body mutable-content).include?(key)
|
104
|
+
|
105
|
+
"aps_proper_fields.#{key}"
|
106
|
+
end
|
107
|
+
|
108
|
+
# 检测是否是 iOS 消息体
|
109
|
+
#
|
110
|
+
# @return [Bool]
|
111
|
+
def ios?
|
112
|
+
current == 'IOS'
|
113
|
+
end
|
114
|
+
|
115
|
+
# 检测是否是 Android 消息体
|
116
|
+
#
|
117
|
+
# @return [Bool]
|
118
|
+
def android?
|
119
|
+
current == 'ANDROID'
|
120
|
+
end
|
121
|
+
|
122
|
+
# 当前消息体类型
|
123
|
+
#
|
124
|
+
# @return [String] IOS/ANDROID
|
125
|
+
def current
|
126
|
+
@current ||= self.class.name.split('::')[-1].upcase
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def instance_key(var)
|
132
|
+
var.to_s.gsub('_', '-').delete('@')
|
133
|
+
end
|
134
|
+
|
135
|
+
def extra_key?(key)
|
136
|
+
%w(badge sound category).include?(key)
|
137
|
+
end
|
138
|
+
|
139
|
+
def extra_key(key)
|
140
|
+
if extra_key?(key)
|
141
|
+
key = 'sound_url' if key.include?('sound')
|
142
|
+
key = "extra.#{key}"
|
143
|
+
else
|
144
|
+
key
|
145
|
+
end
|
146
|
+
end
|
45
147
|
end
|
46
148
|
end
|
47
149
|
end
|
@@ -1,10 +1,30 @@
|
|
1
1
|
module Xiaomi
|
2
2
|
module Push
|
3
3
|
module Message
|
4
|
+
# iOS 数据消息体
|
5
|
+
# @attr [String] title 标题(仅适用于 iOS 10 以上设备)
|
6
|
+
# @attr [String] subtitle 副标题(仅适用于 iOS 10 以上设备)
|
7
|
+
# @attr [String] body 描述(仅适用于 iOS 10 以上设备)
|
8
|
+
# @attr [Integer] mutable_content 可变内容,默认 nil 不启用
|
9
|
+
# @attr [String] image 图片地址(仅适用于 iOS 10 以上设备,填写后自动启用 mutable_content)
|
10
|
+
# @attr [String] description 描述(如果设置了 title 或 subtitle 将会启用变为 {#body})
|
11
|
+
# @attr [Integer] badge 角标, 默认 1
|
12
|
+
# @attr [String] sound 声音,默认 default
|
13
|
+
# @attr [String] category iOS 8 以上可设置推送消息快速回复类别
|
4
14
|
class IOS < Base
|
5
|
-
attr_accessor :
|
15
|
+
attr_accessor :title, :subtitle, :body, :mutable_content
|
16
|
+
attr_accessor :description, :badge, :sound, :category
|
17
|
+
|
6
18
|
def initialize(**params)
|
19
|
+
@title = params[:title]
|
20
|
+
@subtitle = params[:subtitle]
|
7
21
|
@description = params[:description]
|
22
|
+
@body = params[:body] || @description
|
23
|
+
|
24
|
+
@image = params[:image]
|
25
|
+
@mutable_content = params[:mutable_content]
|
26
|
+
@mutable_content = 1 if @image
|
27
|
+
|
8
28
|
@badge = params[:badge] || 1
|
9
29
|
@sound = params[:sound] || 'default'
|
10
30
|
@category = params[:category]
|
@@ -1,20 +1,57 @@
|
|
1
1
|
module Xiaomi
|
2
2
|
module Push
|
3
3
|
module Services
|
4
|
+
# 标签类 API
|
5
|
+
#
|
6
|
+
# @attr [Client] context
|
4
7
|
class Topic
|
5
8
|
attr_reader :context
|
9
|
+
|
10
|
+
# 初始化
|
11
|
+
#
|
12
|
+
# @param [Client] context
|
6
13
|
def initialize(context)
|
7
14
|
@context = context
|
8
15
|
end
|
9
16
|
|
17
|
+
# 订阅标签
|
18
|
+
#
|
19
|
+
# 可使用 reg id 或 alias 的方式订阅标签
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# subscribe(reg_id: 'abc', topic: 'beijing')
|
23
|
+
# subscribe(alias: 'abc', topic: 'beijing')
|
24
|
+
# subscribe(alias: 'abc,def,ghi,jkl', topic: 'beijing')
|
25
|
+
#
|
26
|
+
# @param [Hash] options
|
27
|
+
# @option options [String] :reg_id 订阅 reg id,多个以逗号分割,最多 1000 个
|
28
|
+
# @option options [String] :aliases 订阅 alias,多个以逗号分割,最多 1000 个
|
29
|
+
# @option options [String] :topic 订阅名
|
30
|
+
# @option options [String] :category 分类,可选项
|
31
|
+
# @return [Hash] 小米返回数据结构
|
10
32
|
def subscribe(**options)
|
11
33
|
url, params = prepare_params(__method__.to_s, options)
|
12
|
-
@context.
|
34
|
+
@context.post(url, params)
|
13
35
|
end
|
14
36
|
|
37
|
+
# 取消订阅标签
|
38
|
+
#
|
39
|
+
# 可使用 reg id 或 alias 的方式取消订阅标签
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# unsubscribe(reg_id: 'abc', topic: 'beijing')
|
43
|
+
# unsubscribe(alias: 'abc', topic: 'beijing')
|
44
|
+
# unsubscribe(alias: 'abc,def,ghi,jkl', topic: 'beijing')
|
45
|
+
#
|
46
|
+
# @param [Hash] options
|
47
|
+
# @option options [String] :reg_id 订阅 reg id,多个以逗号分割,最多 1000 个
|
48
|
+
# @option options [String] :aliases 订阅 alias,多个以逗号分割,最多 1000 个
|
49
|
+
# @option options [String] :topic 订阅名
|
50
|
+
# @option options [String] :category 分类,可选项
|
51
|
+
# @return [Hash] 小米返回数据结构
|
15
52
|
def unsubscribe(**options)
|
16
53
|
url, params = prepare_params(__method__.to_s, options)
|
17
|
-
@context.
|
54
|
+
@context.post(url, params)
|
18
55
|
end
|
19
56
|
|
20
57
|
private
|