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.
@@ -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