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
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, '
|
9
|
+
program :description, '小米推送命令行工具'
|
10
10
|
|
11
11
|
program :help, 'Author', 'icyleaf'
|
12
12
|
program :help, 'Website', 'icyleaf.cn@gmail.com'
|
data/lib/xiaomi/push.rb
CHANGED
@@ -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/
|
7
|
-
require 'xiaomi/push/
|
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/
|
12
|
+
require 'xiaomi/push/services/user'
|
13
|
+
require 'xiaomi/push/services/job'
|
14
|
+
require 'xiaomi/push/services/feedback'
|
data/lib/xiaomi/push/client.rb
CHANGED
@@ -1,42 +1,96 @@
|
|
1
|
-
require
|
2
|
-
require
|
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, :
|
10
|
-
def initialize(secret)
|
11
|
-
@device = self.class.name.split('::')[-1].upcase
|
12
|
-
@secret = secret
|
19
|
+
attr_reader :device, :client
|
13
20
|
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
data/lib/xiaomi/push/commands.rb
CHANGED
@@ -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(
|
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
|
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
|
-
#
|
7
|
-
c.option '--device DEVICE', %w(
|
6
|
+
# 必须参数
|
7
|
+
c.option '--device DEVICE', %w(android ios), '设备类型'
|
8
8
|
c.option '--secret SECRET', '应用密钥'
|
9
9
|
|
10
|
-
#
|
10
|
+
# 推送类型
|
11
11
|
c.option '--regid REGID', 'reg id'
|
12
12
|
c.option '--alias ALIAS', '别名'
|
13
|
-
c.option '--
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
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
|
109
|
-
@channel_id = ask
|
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
|
data/lib/xiaomi/push/const.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|