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.
data/bin/xmp CHANGED
@@ -6,7 +6,7 @@ require 'bundler/setup'
6
6
  require 'xiaomi/push'
7
7
 
8
8
  program :version, Xiaomi::Push::VERSION
9
- program :description, 'xiaomi push command line tool'
9
+ program :description, '小米推送命令行工具'
10
10
 
11
11
  program :help, 'Author', 'icyleaf'
12
12
  program :help, 'Website', 'icyleaf.cn@gmail.com'
@@ -3,9 +3,12 @@ require 'xiaomi/push/error'
3
3
  require 'xiaomi/push/const'
4
4
  require 'xiaomi/push/client'
5
5
 
6
- require 'xiaomi/push/devices/ios'
7
- require 'xiaomi/push/devices/android'
6
+ require 'xiaomi/push/platforms/ios'
7
+ require 'xiaomi/push/platforms/android'
8
8
 
9
9
  require 'xiaomi/push/services/message'
10
+ require 'xiaomi/push/services/messages'
10
11
  require 'xiaomi/push/services/topic'
11
- require 'xiaomi/push/services/feedback'
12
+ require 'xiaomi/push/services/user'
13
+ require 'xiaomi/push/services/job'
14
+ require 'xiaomi/push/services/feedback'
@@ -1,42 +1,96 @@
1
- require "rest-client"
2
- require "multi_json"
1
+ require 'http'
2
+ require 'json'
3
3
 
4
4
  module Xiaomi
5
5
  module Push
6
+ # 小米推送内置客户端
7
+ #
8
+ # 实际情况并不会直接被使用,而是使用 iOS 或 Android 的实例化
9
+ #
10
+ # @example 实例化 iOS 推送客户端(环境使用 :sandbox)
11
+ # client = Xiaomi::Push::IOS('Fill your app secret', :sandbox)
12
+ # @example 实例化 Android 推送客户端
13
+ # client = Xiaomi::Push::Android('Fill your app secret')
14
+ #
15
+ # @see https://dev.mi.com/console/doc/detail?pId=68 小米推送服务启用指南
6
16
  class Client
7
17
  include Const
8
18
 
9
- attr_reader :device, :secret, :header
10
- def initialize(secret)
11
- @device = self.class.name.split('::')[-1].upcase
12
- @secret = secret
19
+ attr_reader :device, :client
13
20
 
14
- unless DEVICES.include?@device
15
- raise NameError, 'Instance using Xiaomi::Push::Android or Xiaomi::Push::IOS'
16
- end
21
+ # 实例化一个新的客户端
22
+ #
23
+ # @see https://dev.mi.com/console/doc/detail?pId=68 小米推送服务启用指南
24
+ #
25
+ # @param [String] secret 小米应用的 App Secret
26
+ # @param [Symbol] env 推送环境,可用的环境有 :production/:sandbox
27
+ #
28
+ # @raise [RequestError] 必须使用 Xiaomi::Push::Android 或 Xiaomi::Push::IOS 实例化
29
+ # @raise [RequestError] Android 使用 sandbox 推送环境引发异常
30
+ def initialize(secret, env = :production)
31
+ @device = self.class.name.split('::')[-1].upcase
32
+ @client = HTTP.headers(authorization: "key=#{secret}")
17
33
 
18
- @header = {
19
- 'Authorization' => "key=#{@secret}"
20
- }
34
+ determine_platform!(env)
21
35
 
22
- use_production!
36
+ env == :production ? use_production! : use_sandbox!
23
37
  end
24
38
 
39
+ # 单条消息
25
40
  def message
26
41
  @message ||= Services::Message.new(self)
27
42
  end
28
43
 
44
+ # 多条消息
45
+ def messages
46
+ @messages ||= Services::Messages.new(self)
47
+ end
48
+
49
+ # 标签
29
50
  def topic
30
51
  @topic ||= Services::Topic.new(self)
31
52
  end
32
53
 
54
+ # 用户查询
55
+ def user
56
+ @user ||= Services::User.new(self)
57
+ end
58
+
59
+ # Feedback
33
60
  def feedback
34
61
  @feedback ||= Services::Feedback.new(self)
35
62
  end
36
63
 
37
- def request(url, params)
38
- r = RestClient.post url, params, @header
39
- data = MultiJson.load r
64
+ # GET 方式的网络请求
65
+ #
66
+ # @param [String] url
67
+ # @param [Hash] params
68
+ # @return [Hash] 小米返回数据结构
69
+ def get(url, params = nil)
70
+ r = @client.get(url, params: params)
71
+ data = JSON.parse(r)
72
+ end
73
+
74
+ # 以 POST 方式的网络请求
75
+ #
76
+ # @param [String] url
77
+ # @param [Hash] params
78
+ # @return [Hash] 小米返回数据结构
79
+ def post(url, params = nil)
80
+ r = @client.post(url, form: params)
81
+ data = JSON.parse(r)
82
+ end
83
+
84
+ private
85
+
86
+ def determine_platform!(env)
87
+ unless DEVICES.include?@device
88
+ raise RequestError, '必须使用 Xiaomi::Push::Android 或 Xiaomi::Push::IOS 实例化'
89
+ end
90
+
91
+ if env == :sandbox && @device == 'ANDROID'
92
+ raise RequestError, 'Android 环境不能支持 sandbox 测试环境'
93
+ end
40
94
  end
41
95
  end
42
96
  end
@@ -1,2 +1,3 @@
1
1
  require 'xiaomi/push/commands/message'
2
+ require 'xiaomi/push/commands/user'
2
3
  require 'xiaomi/push/commands/feedback'
@@ -1,10 +1,10 @@
1
1
  command :feedback do |c|
2
2
  c.syntax = 'xmp feedback [options]'
3
3
  c.summary = '获取小米无效的设备列表'
4
- c.description = ''
4
+ c.description = '获取小米无效的设备列表'
5
5
 
6
6
  # normal params
7
- c.option '--device DEVICE', %w('android', 'ios'), '设备类型'
7
+ c.option '--device DEVICE', %w(android, ios), '设备类型'
8
8
  c.option '--secret SECRET', '应用密钥'
9
9
 
10
10
  c.action do |args, options|
@@ -24,16 +24,15 @@ command :feedback do |c|
24
24
  def feedback!
25
25
  client = Xiaomi::Push.const_get(@device).new(@secret)
26
26
  r = client.feedback.invalid
27
-
28
27
  puts r
29
28
  end
30
29
 
31
30
  def determine_device!
32
31
  devices = %w(Android iOS).freeze
33
- @device = choose "选择推送设备:", *devices
32
+ @device = choose("选择推送设备:", *devices)
34
33
  end
35
34
 
36
35
  def determine_secret!
37
- @secret ||= ask '小米应用密钥:'
36
+ @secret ||= ask('小米应用密钥:')
38
37
  end
39
38
  end
@@ -1,19 +1,23 @@
1
1
  command :message do |c|
2
2
  c.syntax = 'xmp message [options]'
3
3
  c.summary = '发送小米推送消息'
4
- c.description = '使用小米推送消息(目前仅支持 regid/alias/topic 推送方式)'
4
+ c.description = '使用小米推送消息(目前仅支持 regid/alias/user/topic 推送方式)'
5
5
 
6
- # normal params
7
- c.option '--device DEVICE', %w('android', 'ios'), '设备类型'
6
+ # 必须参数
7
+ c.option '--device DEVICE', %w(android ios), '设备类型'
8
8
  c.option '--secret SECRET', '应用密钥'
9
9
 
10
- # type
10
+ # 推送类型
11
11
  c.option '--regid REGID', 'reg id'
12
12
  c.option '--alias ALIAS', '别名'
13
- c.option '--topic TOPIC', '订阅名'
14
-
15
- # message
16
- c.option '-i', '--title TITLE', '消息标题(仅 Android 有效)'
13
+ c.option '--user USER', '用户'
14
+ c.option '--topic TOPIC', '标签/群组'
15
+
16
+ # 消息体
17
+ c.option '-i', '--title TITLE', '消息标题(适用于 Android 或 iOS 10 以上设备)'
18
+ c.option '-s', '--subtitle SUBTITLE', '消息副标题(仅适用于 iOS 10 以上设备)'
19
+ c.option '-m', '--image IMAGE', '消息图片地址(仅适用于 iOS 10 以上设备)'
20
+ # c.option '-c', '--image IMAGE', '消息图片地址(仅适用于 iOS 10 以上设备)'
17
21
  c.option '-d', '--description DESCRIPTION', '消息主体描述'
18
22
  c.option '-b', '--badge BADGE', Integer, '消息数字'
19
23
  c.option '-e', '--extras KEY=VALUE', Array, '自定义数据(使用 KEY=VALUE 方式,多个以逗号不带空格分隔)'
@@ -62,7 +66,10 @@ command :message do |c|
62
66
 
63
67
  def determine_ios_message!(options)
64
68
  @message = Xiaomi::Push::Message::IOS.new(
69
+ title: @title,
70
+ subtitle: @subtitle,
65
71
  description: @description,
72
+ image: @image,
66
73
  badge: @badge,
67
74
  extras: @extras
68
75
  )
@@ -70,7 +77,9 @@ command :message do |c|
70
77
 
71
78
  def determine_message!(options)
72
79
  @title = options.title
80
+ @subtitle = options.subtitle
73
81
  @description = options.description
82
+ @image = options.image
74
83
  @badge = options.badge
75
84
 
76
85
  @extras =
@@ -90,7 +99,7 @@ command :message do |c|
90
99
 
91
100
  def determine_device!
92
101
  devices = %w(Android iOS).freeze
93
- @device = choose "选择推送设备:", *devices
102
+ @device = choose('选择推送设备:', *devices)
94
103
  end
95
104
 
96
105
  def determine_secret!
@@ -98,15 +107,15 @@ command :message do |c|
98
107
  end
99
108
 
100
109
  def determine_channel!(options)
101
- channles = %w(regid alias topic).freeze
110
+ channles = %w(regid alias user topic).freeze
102
111
  @channel = channles.select { |k| options.__hash__.key?k.to_sym }
103
112
 
104
113
  if @channel.count > 0
105
114
  @channel = @channel[0]
106
115
  @channel_id = options.__hash__[@channel.to_sym]
107
116
  else
108
- @channel = choose "选择推送方式:", *channles
109
- @channel_id = ask "输入 #{@channel} 的值:"
117
+ @channel = choose('选择推送方式:', *channles)
118
+ @channel_id = ask("输入 #{@channel} 的值:")
110
119
  end
111
120
  end
112
121
  end
@@ -0,0 +1,69 @@
1
+ command :user do |c|
2
+ c.syntax = 'xmp user [options]'
3
+ c.summary = '小米 aliases/topics 查询工具'
4
+ c.description = '通过 reg id 查找用户绑定的 alias 和 topic'
5
+
6
+ # normal params
7
+ c.option '--device DEVICE', %w(android, ios), '设备类型'
8
+ c.option '--secret SECRET', '应用密钥'
9
+ c.option '--reg-id REG_ID', 'Reg id'
10
+ c.option '--package-name PACKAGE_NAME', '包的唯一标识,android 是 package name, ios 是 bundle identiflier'
11
+
12
+ c.action do |args, options|
13
+ puts options if $verbose
14
+
15
+ @device = options.device.capitalize if options.device
16
+ @secret = options.secret
17
+ @reg_id = options.reg_id
18
+ @package_name = options.package_name
19
+
20
+ determine_device! unless @device
21
+ determine_secret! unless @secret
22
+
23
+ @client = Xiaomi::Push.const_get(@device).new(@secret)
24
+
25
+ find_aliases!
26
+ find_topics!
27
+ end
28
+
29
+ private
30
+
31
+ def find_aliases!
32
+ r = @client.user.aliases(@reg_id, @package_name)
33
+ if r['result'] == 'ok'
34
+ print_data(r, :alias)
35
+ else
36
+ user_error(r, :alias)
37
+ end
38
+ end
39
+
40
+ def find_topics!
41
+ r = @client.user.topices(@reg_id, @package_name)
42
+ if r['result'] == 'ok'
43
+ print_data(r, :topic)
44
+ else
45
+ user_error(r, :topic)
46
+ end
47
+ end
48
+
49
+ def print_data(data, type)
50
+ say "#{type.to_s} count: #{data['data']['list'].count}"
51
+ data['data']['list'].each do |key|
52
+ say_ok " * #{key}"
53
+ end
54
+ end
55
+
56
+ def user_error(data, type)
57
+ say_error "#{type.to_s} error."
58
+ say_error "[#{data['code']}] #{data['description']}: #{data['reason']}"
59
+ end
60
+
61
+ def determine_device!
62
+ devices = %w(Android iOS).freeze
63
+ @device = choose("选择推送设备:", *devices)
64
+ end
65
+
66
+ def determine_secret!
67
+ @secret ||= ask('小米应用密钥:')
68
+ end
69
+ end
@@ -3,31 +3,49 @@ require 'uri'
3
3
  module Xiaomi
4
4
  module Push
5
5
  module Const
6
+ # 支持设备
6
7
  DEVICES = %w(ANDROID IOS).freeze
7
-
8
+ # 产品环境
8
9
  PRODUCTION_URL = 'https://api.xmpush.xiaomi.com'.freeze
10
+ # 沙盒环境(仅支持 iOS)
9
11
  SANDBOX_URL = 'https://sandbox.xmpush.xiaomi.com'.freeze
10
12
 
11
13
  attr_reader :base_url
12
14
 
15
+ # 切换产品环境
13
16
  def use_production!
14
17
  production
15
18
  end
16
19
 
20
+ # 切换沙盒环境
17
21
  def use_sandbox!
18
22
  sandbox
19
23
  end
20
24
 
25
+ # :nodoc:
21
26
  def production
22
27
  @base_url ||= PRODUCTION_URL
23
28
  end
24
29
 
30
+ # :nodoc:
25
31
  def sandbox
26
32
  @base_url ||= SANDBOX_URL
27
33
  end
28
34
 
35
+ # :nodoc:
29
36
  def build_uri(uri)
30
- URI.join(@base_url, "v2/#{uri}").to_s
37
+ version =
38
+ if uri.start_with?('stats', 'trace', 'alias', 'reg_id') || uri == 'topic/all'
39
+ # 获取消息的统计数据/追踪消息状态/某个用户目前设置的所有 Alias 和订阅的所有 Topic
40
+ 'v1'
41
+ elsif uri.start_with?('message')
42
+ # 发送消息支持多包使用 v3 版本
43
+ 'v3'
44
+ else
45
+ 'v2'
46
+ end
47
+
48
+ File.join(@base_url, version, uri)
31
49
  end
32
50
  end
33
51
  end
@@ -0,0 +1,17 @@
1
+ module Xiaomi
2
+ module Push
3
+ # Android 推送
4
+ #
5
+ # @example
6
+ # Xiaomi::Push::Message::Android.new(
7
+ # title:'标题要有吸引力',
8
+ # description:'描述可以在手机显示两行',
9
+ # notify_type:'DEFAULT_ALL',
10
+ # extras: {
11
+ # source: 'mpush'
12
+ # }
13
+ # )
14
+ class Android < Xiaomi::Push::Client
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module Xiaomi
2
+ module Push
3
+ # iOS 推送
4
+ #
5
+ # @example
6
+ # Xiaomi::Push::Message::iOS.new(
7
+ # description:'iOS 主要显示描述',
8
+ # badge:10,
9
+ # extras: {
10
+ # uri: 'app://bbs?id=8624',
11
+ # source: 'mpush'
12
+ # }
13
+ # )
14
+ class IOS < Xiaomi::Push::Client
15
+ end
16
+
17
+ # 用于 cli 的别名
18
+ Ios = IOS
19
+ end
20
+ end
@@ -1,6 +1,9 @@
1
1
  module Xiaomi
2
2
  module Push
3
3
  module Services
4
+ # 适用于 iOS 推送获取设备 Feedback API
5
+ #
6
+ # @attr [Client] context
4
7
  class Feedback
5
8
  attr_reader :context
6
9
 
@@ -8,10 +11,14 @@ module Xiaomi
8
11
  @context = context
9
12
  end
10
13
 
14
+ # 获取失效的 device token
15
+ #
16
+ # @see https://dev.mi.com/console/doc/detail?pId=1163#_4_1
17
+ #
18
+ # @return [Hash] 小米返回数据结构
11
19
  def invalid
12
20
  url = 'https://feedback.xmpush.xiaomi.com/v1/feedback/fetch_invalid_regids'
13
- r = RestClient.get url, @context.header
14
- MultiJson.load r
21
+ @context.get(url)
15
22
  end
16
23
  end
17
24
  end
@@ -0,0 +1,36 @@
1
+ module Xiaomi
2
+ module Push
3
+ module Services
4
+ # 定时任务类 API
5
+ #
6
+ # @see https://dev.mi.com/console/doc/detail?pId=1163#_7
7
+ #
8
+ # @attr [Client] context
9
+ class Job
10
+ attr_reader :context
11
+
12
+ def initialize(context)
13
+ @context = context
14
+ end
15
+
16
+ # 检测定时任务是否存在
17
+ #
18
+ # @param [String] job_id 任务 id
19
+ # @return [Hash] 小米返回数据结构
20
+ def exist?(job_id)
21
+ url = @context.build_uri('schedule_job/exist')
22
+ @context.post(url, { job_id: job_id })
23
+ end
24
+
25
+ # 删除定时任务
26
+ #
27
+ # @param [String] job_id 任务 id
28
+ # @return [Hash] 小米返回数据结构
29
+ def destory(job_id)
30
+ url = @context.build_uri('schedule_job/delete')
31
+ @context.post(url, { job_id: job_id })
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end