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