xiaomi-push 0.2.4 → 0.3.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.
@@ -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].build
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
- r = RestClient.post url, params, @context.header
55
- data = MultiJson.load r
75
+ @context.post(url, params)
56
76
  else
57
- raise Xiaomi::Push::RequestError, 'Not match message type: reg_id/alias/topic/topics/all'
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, :extras
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 key, value
38
+ instance_variable_set(key, value)
20
39
  else
21
- instance_variable_get key
40
+ instance_variable_get(key)
22
41
  end
23
42
  end
24
43
 
25
- def build
44
+ # 转换为字典
45
+ # @return [Hash] 消息体
46
+ def to_params
26
47
  hash_data = {}
27
48
  instance_variables.each do |ivar|
28
- key = ivar.to_s.delete('@', '')
29
- value = instance_variable_get ivar
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 :description, :badge, :sound, :category, :extras
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.request url, params
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.request url, params
54
+ @context.post(url, params)
18
55
  end
19
56
 
20
57
  private