mixin_bot 0.0.1.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/mixin_bot/api/auth.rb +11 -5
- data/lib/mixin_bot/api/conversation.rb +21 -11
- data/lib/mixin_bot/api/me.rb +25 -16
- data/lib/mixin_bot/api/message.rb +248 -33
- data/lib/mixin_bot/api/payment.rb +16 -16
- data/lib/mixin_bot/api/pin.rb +41 -19
- data/lib/mixin_bot/api/snapshot.rb +21 -15
- data/lib/mixin_bot/api/transfer.rb +13 -14
- data/lib/mixin_bot/api/user.rb +45 -12
- data/lib/mixin_bot/api/withdraw.rb +78 -0
- data/lib/mixin_bot/api.rb +6 -2
- data/lib/mixin_bot/client.rb +25 -31
- data/lib/mixin_bot/errors.rb +3 -1
- data/lib/mixin_bot/version.rb +3 -1
- data/lib/mixin_bot.rb +3 -2
- metadata +50 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29930a091b973b490ea096e14f6344a0bc95f67b5023f081ac47fca36bc78adb
|
4
|
+
data.tar.gz: eb58283464badcf4573f417ad8108e06216811bce36e0990bf10022d8e4802c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51e9e665540a634c383ee266d7b12ce67824c11a06a67fce43f66d3666a317d7c80e1ec974f7401ddb634230a73d0163702792876789c28210e91b8a43c68871
|
7
|
+
data.tar.gz: 7e503ad0ca5116e99cf36423049b3017109e1d4c2ced08b1625630c55448cc5fbbe4047d0fb3c4ea4bf4abdfa7b7ffc89592671116e8d4d0739bfef9b05c1f52
|
data/lib/mixin_bot/api/auth.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class API
|
3
5
|
module Auth
|
4
|
-
def access_token(method, uri, body, exp_in=
|
5
|
-
sig = Digest::SHA256.hexdigest
|
6
|
+
def access_token(method, uri, body = '', exp_in = 600)
|
7
|
+
sig = Digest::SHA256.hexdigest(method + uri + body)
|
6
8
|
iat = Time.now.utc.to_i
|
7
9
|
exp = (Time.now.utc + exp_in).to_i
|
8
10
|
jti = SecureRandom.uuid
|
@@ -28,12 +30,16 @@ module MixinBot
|
|
28
30
|
|
29
31
|
raise r.inspect if r['error'].present?
|
30
32
|
|
31
|
-
|
33
|
+
r['data']&.[]('access_token')
|
32
34
|
end
|
33
35
|
|
34
|
-
def request_oauth(scope=nil)
|
36
|
+
def request_oauth(scope = nil)
|
35
37
|
scope ||= (MixinBot.scope || 'PROFILE:READ+PHONE:READ')
|
36
|
-
format(
|
38
|
+
format(
|
39
|
+
'https://mixin.one/oauth/authorize?client_id=%<client_id>s&scope=%<scope>s',
|
40
|
+
client_id: client_id,
|
41
|
+
scope: scope
|
42
|
+
)
|
37
43
|
end
|
38
44
|
end
|
39
45
|
end
|
@@ -1,16 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class API
|
3
5
|
module Conversation
|
4
6
|
def read_conversation(conversation_id)
|
5
|
-
path = format('/conversations
|
6
|
-
|
7
|
-
authorization = format('Bearer
|
7
|
+
path = format('/conversations/%<conversation_id>s', conversation_id: conversation_id)
|
8
|
+
access_token ||= access_token('GET', path, '')
|
9
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
8
10
|
client.get(path, headers: { 'Authorization': authorization })
|
9
11
|
end
|
10
12
|
|
11
13
|
def read_conversation_by_user_id(user_id)
|
12
14
|
conversation_id = unique_conversation_id(user_id)
|
13
|
-
|
15
|
+
read_conversation(conversation_id)
|
14
16
|
end
|
15
17
|
|
16
18
|
def create_contact_conversation(user_id)
|
@@ -26,8 +28,8 @@ module MixinBot
|
|
26
28
|
}
|
27
29
|
]
|
28
30
|
}
|
29
|
-
|
30
|
-
authorization = format('Bearer
|
31
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
32
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
31
33
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
32
34
|
end
|
33
35
|
|
@@ -36,11 +38,19 @@ module MixinBot
|
|
36
38
|
md5 << [user_id, client_id].min
|
37
39
|
md5 << [user_id, client_id].max
|
38
40
|
digest = md5.digest
|
39
|
-
|
40
|
-
|
41
|
-
cipher = digest[0...6] +
|
42
|
-
hex = cipher.
|
43
|
-
|
41
|
+
digest6 = (digest[6].ord & 0x0f | 0x30).chr
|
42
|
+
digest8 = (digest[8].ord & 0x3f | 0x80).chr
|
43
|
+
cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..-1]
|
44
|
+
hex = cipher.unpack1('H*')
|
45
|
+
|
46
|
+
format(
|
47
|
+
'%<first>s-%<second>s-%<third>s-%<forth>s-%<fifth>s',
|
48
|
+
first: hex[0..7],
|
49
|
+
second: hex[8..11],
|
50
|
+
third: hex[12..15],
|
51
|
+
forth: hex[16..19],
|
52
|
+
fifth: hex[20..-1]
|
53
|
+
)
|
44
54
|
end
|
45
55
|
end
|
46
56
|
end
|
data/lib/mixin_bot/api/me.rb
CHANGED
@@ -1,42 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class API
|
3
5
|
module Me
|
4
|
-
|
6
|
+
# https://developers.mixin.one/api/beta-mixin-message/read-profile/
|
7
|
+
def read_me
|
5
8
|
path = '/me'
|
6
|
-
access_token
|
7
|
-
authorization = format('Bearer
|
9
|
+
access_token = access_token('GET', path, '')
|
10
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
8
11
|
client.get(path, headers: { 'Authorization': authorization })
|
9
12
|
end
|
10
13
|
|
11
|
-
|
14
|
+
# https://developers.mixin.one/api/beta-mixin-message/update-profile/
|
15
|
+
# avatar_base64:
|
16
|
+
# String: Base64 of image, supports format png, jpeg and gif, base64 image size > 1024.
|
17
|
+
def update_me(full_name:, avatar_base64: nil)
|
12
18
|
path = '/me'
|
13
19
|
payload = {
|
14
20
|
full_name: full_name,
|
15
21
|
avatar_base64: avatar_base64
|
16
22
|
}
|
17
|
-
access_token
|
18
|
-
authorization = format('Bearer
|
23
|
+
access_token = access_token('POST', path, payload.to_json)
|
24
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
19
25
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
20
26
|
end
|
21
27
|
|
22
|
-
|
28
|
+
# https://developers.mixin.one/api/alpha-mixin-network/read-assets/
|
29
|
+
def read_assets
|
23
30
|
path = '/assets'
|
24
|
-
access_token
|
25
|
-
authorization = format('Bearer
|
31
|
+
access_token = access_token('GET', path, '')
|
32
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
26
33
|
client.get(path, headers: { 'Authorization': authorization })
|
27
34
|
end
|
28
35
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
# https://developers.mixin.one/api/alpha-mixin-network/read-asset/
|
37
|
+
def read_asset(asset_id)
|
38
|
+
path = format('/assets/%<asset_id>s', asset_id: asset_id)
|
39
|
+
access_token = access_token('GET', path, '')
|
40
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
33
41
|
client.get(path, headers: { 'Authorization': authorization })
|
34
42
|
end
|
35
43
|
|
36
|
-
|
44
|
+
# https://developers.mixin.one/api/beta-mixin-message/friends/
|
45
|
+
def read_friends
|
37
46
|
path = '/friends'
|
38
|
-
access_token
|
39
|
-
authorization = format('Bearer
|
47
|
+
access_token = access_token('GET', path, '')
|
48
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
40
49
|
client.get(path, headers: { 'Authorization': authorization })
|
41
50
|
end
|
42
51
|
end
|
@@ -1,69 +1,236 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class API
|
5
|
+
# https://developers.mixin.one/api/beta-mixin-message/websocket-messages/
|
3
6
|
module Message
|
4
7
|
def list_pending_message
|
5
|
-
|
8
|
+
write_ws_message(action: 'LIST_PENDING_MESSAGES', params: {})
|
6
9
|
end
|
7
10
|
|
11
|
+
# ACKNOWLEDGE_MESSAGE_RECEIPT ack server received message
|
12
|
+
# {
|
13
|
+
# "id": "UUID",
|
14
|
+
# "action": "ACKNOWLEDGE_MESSAGE_RECEIPT",
|
15
|
+
# "params": {
|
16
|
+
# "message_id": "UUID // message_id is you received message's message_id",
|
17
|
+
# "status": "READ"
|
18
|
+
# }
|
19
|
+
# }
|
8
20
|
def acknowledge_message_receipt(message_id)
|
9
21
|
params = {
|
10
22
|
message_id: message_id,
|
11
23
|
status: 'READ'
|
12
24
|
}
|
13
|
-
|
25
|
+
write_ws_message(action: 'ACKNOWLEDGE_MESSAGE_RECEIPT', params: params)
|
14
26
|
end
|
15
27
|
|
16
|
-
|
17
|
-
|
28
|
+
# {
|
29
|
+
# "id": "UUID // generated by client",
|
30
|
+
# "action": "CREATE_MESSAGE",
|
31
|
+
# "params": {
|
32
|
+
# "conversation_id": "UUID",
|
33
|
+
# "category": "PLAIN_TEXT",
|
34
|
+
# "status": "SENT",
|
35
|
+
# "message_id": "UUID // generated by client",
|
36
|
+
# "data": "Base64 encoded data" ,
|
37
|
+
# }
|
38
|
+
# }
|
39
|
+
def plain_text(options)
|
40
|
+
options.merge!(category: 'PLAIN_TEXT')
|
41
|
+
base_message_params(options)
|
42
|
+
end
|
18
43
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
44
|
+
# {
|
45
|
+
# "id": "UUID",
|
46
|
+
# "action": "CREATE_MESSAGE",
|
47
|
+
# "params": {
|
48
|
+
# "conversation_id": "UUID"
|
49
|
+
# "category": "PLAIN_IMAGE"
|
50
|
+
# "status": "SENT",
|
51
|
+
# "message_id": "UUID",
|
52
|
+
# "data": "Base64 encoded data"
|
53
|
+
# }
|
54
|
+
# }
|
55
|
+
# data format:
|
56
|
+
# {
|
57
|
+
# "attachment_id":
|
58
|
+
# "Read From POST /attachments",
|
59
|
+
# "mime_type": "",
|
60
|
+
# "width": 1024,
|
61
|
+
# "height": 1024,
|
62
|
+
# "size": 1024,
|
63
|
+
# "thumbnail": "base64 encoded"
|
64
|
+
# }
|
65
|
+
def plain_image(options)
|
66
|
+
options.merge!(category: 'PLAIN_IMAGE')
|
67
|
+
base_message_params(options)
|
68
|
+
end
|
26
69
|
|
27
|
-
|
70
|
+
# {
|
71
|
+
# "id": "UUID",
|
72
|
+
# "action": "CREATE_MESSAGE",
|
73
|
+
# "params": {
|
74
|
+
# "conversation_id": "UUID",
|
75
|
+
# "category": "PLAIN_DATA",
|
76
|
+
# "status": "SENT",
|
77
|
+
# "message_id": "UUID",
|
78
|
+
# "data": "Base64 encoded data",
|
79
|
+
# }
|
80
|
+
# }
|
81
|
+
# data format:
|
82
|
+
# {
|
83
|
+
# "attachment_id": "Read From POST /attachments",
|
84
|
+
# "mime_type": "",
|
85
|
+
# "size": 1024,
|
86
|
+
# "name": "Share"
|
87
|
+
# }
|
88
|
+
def plain_data(options)
|
89
|
+
options.merge!(category: 'PLAIN_DATA')
|
90
|
+
base_message_params(options)
|
28
91
|
end
|
29
92
|
|
30
|
-
|
31
|
-
|
93
|
+
# {
|
94
|
+
# "id": "UUID",
|
95
|
+
# "action": "CREATE_MESSAGE",
|
96
|
+
# "params": {
|
97
|
+
# "conversation_id": "UUID",
|
98
|
+
# "category": "PLAIN_STICKER",
|
99
|
+
# "status": "SENT",
|
100
|
+
# "message_id": "UUID",
|
101
|
+
# "data": "Base64 encoded data"
|
102
|
+
# }
|
103
|
+
# }
|
104
|
+
# data format:
|
105
|
+
# {
|
106
|
+
# "name": "hello",
|
107
|
+
# "album_id": "UUID"
|
108
|
+
# }
|
109
|
+
def plain_sticker(options)
|
110
|
+
options.merge!(category: 'PLAIN_STICKER')
|
111
|
+
base_message_params(options)
|
32
112
|
end
|
33
113
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
114
|
+
# {
|
115
|
+
# "id": "UUID",
|
116
|
+
# "action": "CREATE_MESSAGE",
|
117
|
+
# "params": {
|
118
|
+
# "conversation_id": "UUID",
|
119
|
+
# "category": "PLAIN_CONTACT"
|
120
|
+
# "status": "SENT",
|
121
|
+
# "message_id": "UUID",
|
122
|
+
# "data": "Base64 encoded data"
|
123
|
+
# }
|
124
|
+
# }
|
125
|
+
# data format:
|
126
|
+
# { "user_id": "UUID"}
|
127
|
+
def plain_contact(options)
|
128
|
+
options.merge!(category: 'PLAIN_CONTACT')
|
129
|
+
base_message_params(options)
|
130
|
+
end
|
39
131
|
|
40
|
-
|
41
|
-
|
132
|
+
# {
|
133
|
+
# "id": "UUID",
|
134
|
+
# "action": "CREATE_MESSAGE",
|
135
|
+
# "params": {
|
136
|
+
# "conversation_id": "UUID",
|
137
|
+
# "category": "APP_CARD",
|
138
|
+
# "status": "SENT",
|
139
|
+
# "message_id": "UUID",
|
140
|
+
# "data": "Base64 encoded data"
|
141
|
+
# }
|
142
|
+
# }
|
143
|
+
# data format:
|
144
|
+
# {
|
145
|
+
# "icon_url": "https://mixin.one/assets/98b586edb270556d1972112bd7985e9e.png",
|
146
|
+
# "title": "Mixin",
|
147
|
+
# "description": "A free and lightning fast peer-to-peer transactional network for digital assets.",
|
148
|
+
# "action": "https://mixin.one"
|
149
|
+
# }
|
150
|
+
def app_card(options)
|
151
|
+
options.merge!(category: 'APP_CARD')
|
152
|
+
base_message_params(options)
|
153
|
+
end
|
42
154
|
|
43
|
-
|
155
|
+
# {
|
156
|
+
# "id": "UUID",
|
157
|
+
# "action": "CREATE_MESSAGE",
|
158
|
+
# "params": {
|
159
|
+
# "conversation_id": "UUID",
|
160
|
+
# "category": "APP_BUTTON_GROUP",
|
161
|
+
# "status": "SENT",
|
162
|
+
# "message_id": "UUID",
|
163
|
+
# "data": "Base64 encoded data"
|
164
|
+
# }
|
165
|
+
# }
|
166
|
+
# data format:
|
167
|
+
# [
|
168
|
+
# {
|
169
|
+
# "label": "Mixin Website",
|
170
|
+
# "color": "#ABABAB",
|
171
|
+
# "action": "https://mixin.one"
|
172
|
+
# },
|
173
|
+
# ...
|
174
|
+
# ]
|
175
|
+
def app_button_group(options)
|
176
|
+
options.merge!(category: 'APP_BUTTON_GROUP')
|
177
|
+
base_message_params(options)
|
178
|
+
end
|
179
|
+
|
180
|
+
# {
|
181
|
+
# "id": "UUID",
|
182
|
+
# "action": "CREATE_MESSAGE",
|
183
|
+
# "params": {
|
184
|
+
# "conversation_id": "UUID",
|
185
|
+
# "category": "PLAIN_VIDEO",
|
186
|
+
# "status": "SENT",
|
187
|
+
# "message_id": "UUID",
|
188
|
+
# "data": "Base64 encoded data"
|
189
|
+
# }
|
190
|
+
# }
|
191
|
+
# data format:
|
192
|
+
# {
|
193
|
+
# "attachment_id": "Read From POST /attachments",
|
194
|
+
# "mime_type": "",
|
195
|
+
# "width": 1024,
|
196
|
+
# "height": 1024,
|
197
|
+
# "size": 1024,
|
198
|
+
# "duration": 1024,
|
199
|
+
# "thumbnail": "base64 encoded"
|
200
|
+
# }
|
201
|
+
def plain_video(options)
|
202
|
+
options.merge!(category: 'PLAIN_VIDEO')
|
203
|
+
base_message_params(options)
|
204
|
+
end
|
205
|
+
|
206
|
+
# base format of message params
|
207
|
+
def base_message_params(conversation_id:, category:, data:, quote_message_id: nil, message_id: nil)
|
208
|
+
data = data.is_a?(String) ? data : data.to_json
|
209
|
+
{
|
44
210
|
conversation_id: conversation_id,
|
45
|
-
|
46
|
-
category: 'APP_BUTTON_GROUP',
|
211
|
+
category: category,
|
47
212
|
status: 'SENT',
|
48
|
-
|
49
|
-
|
213
|
+
quote_message_id: quote_message_id,
|
214
|
+
message_id: message_id || SecureRandom.uuid,
|
215
|
+
data: Base64.encode64(data)
|
50
216
|
}
|
51
|
-
|
52
|
-
write_message('CREATE_MESSAGE', params)
|
53
217
|
end
|
54
218
|
|
55
|
-
|
219
|
+
# read the gzipped message form websocket
|
220
|
+
def read_ws_message(data)
|
56
221
|
io = StringIO.new(data.pack('c*'), 'rb')
|
57
222
|
gzip = Zlib::GzipReader.new io
|
58
223
|
msg = gzip.read
|
59
224
|
gzip.close
|
60
|
-
|
225
|
+
|
226
|
+
msg
|
61
227
|
end
|
62
228
|
|
63
|
-
|
229
|
+
# gzip the message for websocket
|
230
|
+
def write_ws_message(action: 'CREATE_MESSAGE', params:)
|
64
231
|
msg = {
|
65
232
|
id: SecureRandom.uuid,
|
66
|
-
action:
|
233
|
+
action: action,
|
67
234
|
params: params
|
68
235
|
}.to_json
|
69
236
|
|
@@ -71,7 +238,55 @@ module MixinBot
|
|
71
238
|
gzip = Zlib::GzipWriter.new io
|
72
239
|
gzip.write msg
|
73
240
|
gzip.close
|
74
|
-
|
241
|
+
io.string.unpack('c*')
|
242
|
+
end
|
243
|
+
|
244
|
+
# use HTTP to send message
|
245
|
+
def send_text_message(options)
|
246
|
+
send_message plain_text(options)
|
247
|
+
end
|
248
|
+
|
249
|
+
def send_contact_message(options)
|
250
|
+
send_message plain_contact(options)
|
251
|
+
end
|
252
|
+
|
253
|
+
def send_app_card_message(options)
|
254
|
+
send_message app_card(options)
|
255
|
+
end
|
256
|
+
|
257
|
+
def send_app_button_group_message(options)
|
258
|
+
send_message app_button_group(options)
|
259
|
+
end
|
260
|
+
|
261
|
+
# {
|
262
|
+
# "id": "UUID",
|
263
|
+
# "action": "CREATE_PLAIN_MESSAGES",
|
264
|
+
# "params": {
|
265
|
+
# "messages": [
|
266
|
+
# {
|
267
|
+
# "conversation_id": "UUID",
|
268
|
+
# "recipient_id": "UUID",
|
269
|
+
# "message_id": "UUID",
|
270
|
+
# "representative_id": "UUID (optional, only supported in peer to peer conversation)",
|
271
|
+
# "quote_message_id": "UUID (optional, only supported text, e.g. PLAIN_TEXT)",
|
272
|
+
# "category": "Only support plain category e.g.: PLAIN_TEXT, PLAIN_STICKER etc",
|
273
|
+
# "data": "Correspond to category."
|
274
|
+
# },
|
275
|
+
# ...
|
276
|
+
# ]
|
277
|
+
# }
|
278
|
+
# }
|
279
|
+
# not verified yet
|
280
|
+
def send_plain_messages(messages)
|
281
|
+
send_message(messages: messages)
|
282
|
+
end
|
283
|
+
|
284
|
+
# http post request
|
285
|
+
def send_message(payload)
|
286
|
+
path = '/messages'
|
287
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
288
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
289
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
75
290
|
end
|
76
291
|
end
|
77
292
|
end
|
@@ -1,29 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class API
|
3
5
|
module Payment
|
4
6
|
def pay_url(options)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
format(
|
8
|
+
'https://mixin.one/pay?recipient=%<recipient_id>s&asset=%<asset>s&amount=%<amount>s&trace=%<trace>s&memo=%<memo>s',
|
9
|
+
recipient_id: options[:recipient_id],
|
10
|
+
asset: options[:asset_id],
|
11
|
+
amount: options[:amount].to_s,
|
12
|
+
trace: options[:trace],
|
13
|
+
memo: options[:memo]
|
14
|
+
)
|
12
15
|
end
|
13
16
|
|
17
|
+
# https://developers.mixin.one/api/alpha-mixin-network/verify-payment/
|
14
18
|
def verify_payment(options)
|
15
|
-
options = options.with_indifferent_access
|
16
|
-
recipient_id = options.fetch('recipient_id')
|
17
|
-
asset_id = options.fetch('asset_id')
|
18
|
-
amount = options.fetch('amount')
|
19
|
-
trace = options.fetch('trace')
|
20
19
|
path = 'payments'
|
21
20
|
payload = {
|
22
|
-
asset_id: asset_id,
|
23
|
-
opponent_id:
|
24
|
-
amount: amount,
|
25
|
-
trace_id: trace
|
21
|
+
asset_id: options[:asset_id],
|
22
|
+
opponent_id: options[:opponent_id],
|
23
|
+
amount: options[:amount].to_s,
|
24
|
+
trace_id: options[:trace]
|
26
25
|
}
|
26
|
+
|
27
27
|
client.post(path, json: payload)
|
28
28
|
end
|
29
29
|
end
|
data/lib/mixin_bot/api/pin.rb
CHANGED
@@ -1,46 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class API
|
3
5
|
module Pin
|
4
|
-
|
6
|
+
# https://developers.mixin.one/api/alpha-mixin-network/verify-pin/
|
7
|
+
def verify_pin(pin_code)
|
5
8
|
path = '/pin/verify'
|
6
9
|
payload = {
|
7
10
|
pin: encrypt_pin(pin_code)
|
8
11
|
}
|
9
12
|
|
10
|
-
access_token
|
11
|
-
authorization = format('Bearer
|
13
|
+
access_token = access_token('POST', path, payload.to_json)
|
14
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
15
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
16
|
+
end
|
17
|
+
|
18
|
+
# not verified yet
|
19
|
+
# https://developers.mixin.one/api/alpha-mixin-network/create-pin/
|
20
|
+
def update_pin(old_pin:, new_pin:)
|
21
|
+
path = '/pin/update'
|
22
|
+
timestamp = Time.now.utc.to_i
|
23
|
+
payload = {
|
24
|
+
old_pin: old_pin.nil? ? '' : encrypt_pin(old_pin, timestamp: timestamp),
|
25
|
+
pin: encrypt_pin(new_pin, timestamp: timestamp)
|
26
|
+
}
|
27
|
+
|
28
|
+
access_token = access_token('POST', path, payload.to_json)
|
29
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
12
30
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
13
31
|
end
|
14
32
|
|
33
|
+
# decrypt the encrpted pin, just for test
|
15
34
|
def decrypt_pin(msg)
|
16
35
|
msg = Base64.strict_decode64 msg
|
17
36
|
iv = msg[0..15]
|
18
37
|
cipher = msg[16..47]
|
19
|
-
aes_key = JOSE::JWA::PKCS1
|
38
|
+
aes_key = JOSE::JWA::PKCS1.rsaes_oaep_decrypt('SHA256', pin_token, private_key, session_id)
|
20
39
|
alg = 'AES-256-CBC'
|
21
40
|
decode_cipher = OpenSSL::Cipher.new(alg)
|
22
41
|
decode_cipher.decrypt
|
23
42
|
decode_cipher.iv = iv
|
24
43
|
decode_cipher.key = aes_key
|
25
|
-
|
26
|
-
|
44
|
+
decoded = decode_cipher.update(cipher)
|
45
|
+
decoded[0..5]
|
27
46
|
end
|
28
47
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
48
|
+
# https://developers.mixin.one/api/alpha-mixin-network/encrypted-pin/
|
49
|
+
# use timestamp(timestamp) for iterator as default: must be bigger than the previous, the first time must be greater than 0. After a new session created, it will be reset to 0.
|
50
|
+
def encrypt_pin(pin_code, timestamp: nil)
|
51
|
+
aes_key = JOSE::JWA::PKCS1.rsaes_oaep_decrypt('SHA256', pin_token, private_key, session_id)
|
52
|
+
timestamp ||= Time.now.utc.to_i
|
53
|
+
tszero = timestamp % 0x100
|
54
|
+
tsone = (timestamp % 0x10000) >> 8
|
55
|
+
tstwo = (timestamp % 0x1000000) >> 16
|
56
|
+
tsthree = (timestamp % 0x100000000) >> 24
|
36
57
|
tsstring = tszero.chr + tsone.chr + tstwo.chr + tsthree.chr + "\0\0\0\0"
|
37
58
|
encrypt_content = pin_code + tsstring + tsstring
|
38
59
|
pad_count = 16 - encrypt_content.length % 16
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
60
|
+
padded_content =
|
61
|
+
if pad_count.positive?
|
62
|
+
encrypt_content + pad_count.chr * pad_count
|
63
|
+
else
|
64
|
+
encrypt_content
|
65
|
+
end
|
44
66
|
|
45
67
|
alg = 'AES-256-CBC'
|
46
68
|
aes = OpenSSL::Cipher.new(alg)
|
@@ -50,7 +72,7 @@ module MixinBot
|
|
50
72
|
aes.iv = iv
|
51
73
|
cipher = aes.update(padded_content)
|
52
74
|
msg = iv + cipher
|
53
|
-
|
75
|
+
Base64.strict_encode64 msg
|
54
76
|
end
|
55
77
|
end
|
56
78
|
end
|
@@ -1,25 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class API
|
3
5
|
module Snapshot
|
4
|
-
def read_snapshots(options)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
def read_snapshots(options = {})
|
7
|
+
path = format(
|
8
|
+
'/network/snapshots?limit=%<limit>s&offset=%<offset>s&asset=%<asset>s&order=%<order>s',
|
9
|
+
limit: options[:limit],
|
10
|
+
offset: options[:offset],
|
11
|
+
asset: options[:asset],
|
12
|
+
order: options[:order]
|
13
|
+
)
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
if options[:private]
|
16
|
+
# TODO:
|
17
|
+
# read private snapshots
|
18
|
+
access_token = access_token('GET', path)
|
19
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
20
|
+
client.get(path, headers: { 'Authorization': authorization, 'Content-length': 0 })
|
21
|
+
else
|
22
|
+
# read public snapshots as default
|
23
|
+
client.get(path)
|
24
|
+
end
|
19
25
|
end
|
20
26
|
|
21
27
|
def read_snapshot(snapshot_id)
|
22
|
-
path = format('network/snapshots
|
28
|
+
path = format('network/snapshots/%<snapshot_id>s', snapshot_id: snapshot_id)
|
23
29
|
client.get(path)
|
24
30
|
end
|
25
31
|
end
|
@@ -1,35 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class API
|
3
5
|
module Transfer
|
4
6
|
def create_transfer(pin, options)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
memo = options.fetch('memo')
|
11
|
-
trace_id = options.fetch('trace_id')
|
12
|
-
trace_id ||= SecureRandom.uuid
|
7
|
+
asset_id = options[:asset_id]
|
8
|
+
opponent_id = options[:opponent_id]
|
9
|
+
amount = options[:amount]
|
10
|
+
memo = options[:memo]
|
11
|
+
trace_id = options[:trace_id] || SecureRandom.uuid
|
13
12
|
|
14
13
|
path = '/transfers'
|
15
14
|
payload = {
|
16
15
|
asset_id: asset_id,
|
17
16
|
opponent_id: opponent_id,
|
18
17
|
pin: pin,
|
19
|
-
amount: amount,
|
18
|
+
amount: amount.to_s,
|
20
19
|
trace_id: trace_id,
|
21
20
|
memo: memo
|
22
21
|
}
|
23
22
|
|
24
|
-
access_token ||=
|
25
|
-
authorization = format('Bearer
|
23
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
24
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
26
25
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
27
26
|
end
|
28
27
|
|
29
28
|
def read_transfer(trace_id)
|
30
|
-
path = format('/transfers/trace
|
31
|
-
access_token ||=
|
32
|
-
authorization = format('Bearer
|
29
|
+
path = format('/transfers/trace/%<trace_id>s', trace_id: trace_id)
|
30
|
+
access_token ||= access_token('GET', path, '')
|
31
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
33
32
|
client.get(path, headers: { 'Authorization': authorization })
|
34
33
|
end
|
35
34
|
end
|
data/lib/mixin_bot/api/user.rb
CHANGED
@@ -1,29 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class API
|
3
5
|
module User
|
4
|
-
def read_user(user_id, access_token=nil)
|
6
|
+
def read_user(user_id, access_token = nil)
|
5
7
|
# user_id: Mixin User Id
|
6
|
-
path = format('/users
|
7
|
-
access_token ||=
|
8
|
-
authorization = format('Bearer
|
8
|
+
path = format('/users/%<user_id>s', user_id: user_id)
|
9
|
+
access_token ||= access_token('GET', path, '')
|
10
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
9
11
|
client.get(path, headers: { 'Authorization': authorization })
|
10
12
|
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
# https://developers.mixin.one/api/alpha-mixin-network/app-user/
|
15
|
+
# Create a new Mixin Network user (like a normal Mixin Messenger user). You should keep PrivateKey which is used to sign an AuthenticationToken and encrypted PIN for the user.
|
16
|
+
def create_user(full_name, rsa_key = nil)
|
17
|
+
rsa_key ||= generate_rsa_key
|
18
|
+
session_secret = rsa_key[:public_key]
|
19
|
+
session_secret.gsub!(/^-----.*PUBLIC KEY-----$/, '').strip!
|
20
|
+
|
21
|
+
payload = {
|
22
|
+
full_name: full_name,
|
23
|
+
session_secret: session_secret
|
24
|
+
}
|
25
|
+
|
26
|
+
access_token = access_token('POST', '/users', payload.to_json)
|
27
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
28
|
+
res = client.post('/users', headers: { 'Authorization': authorization }, json: payload)
|
29
|
+
|
30
|
+
res.merge(rsa_key: rsa_key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_rsa_key
|
34
|
+
rsa_key = OpenSSL::PKey::RSA.new 1024
|
35
|
+
{
|
36
|
+
private_key: rsa_key.to_pem,
|
37
|
+
public_key: rsa_key.public_key.to_pem
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# https://developers.mixin.one/api/beta-mixin-message/search-user/
|
42
|
+
# search by Mixin Id or Phone Number
|
43
|
+
def search_user(query, access_token = nil)
|
44
|
+
path = format('/search/%<query>s', query: query)
|
45
|
+
|
46
|
+
access_token ||= access_token('GET', path, '')
|
47
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
17
48
|
client.get(path, headers: { 'Authorization': authorization })
|
18
49
|
end
|
19
50
|
|
20
|
-
|
51
|
+
# https://developers.mixin.one/api/beta-mixin-message/read-users/
|
52
|
+
def fetch_users(user_ids, access_token = nil)
|
21
53
|
# user_ids: a array of user_ids
|
22
54
|
path = '/users/fetch'
|
23
55
|
user_ids = [user_ids] if user_ids.is_a? String
|
24
56
|
payload = user_ids
|
25
|
-
|
26
|
-
|
57
|
+
|
58
|
+
access_token ||= access_token('POST', path, payload.to_json)
|
59
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
27
60
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
28
61
|
end
|
29
62
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module Withdraw
|
6
|
+
# https://developers.mixin.one/api/alpha-mixin-network/create-address/
|
7
|
+
def create_withdraw_address(options)
|
8
|
+
path = '/addresses'
|
9
|
+
encrypted_pin = encrypt_pin(options[:pin])
|
10
|
+
payload =
|
11
|
+
# for EOS withdraw, account_name & account_tag must be valid
|
12
|
+
if options[:public_key].nil?
|
13
|
+
{
|
14
|
+
asset_id: options[:asset_id],
|
15
|
+
account_name: options[:account_name],
|
16
|
+
account_tag: options[:account_tag],
|
17
|
+
label: options[:label],
|
18
|
+
pin: encrypted_pin
|
19
|
+
}
|
20
|
+
# for other withdraw
|
21
|
+
else
|
22
|
+
{
|
23
|
+
asset_id: options[:asset_id],
|
24
|
+
public_key: options[:public_key],
|
25
|
+
label: options[:label],
|
26
|
+
pin: encrypted_pin
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
access_token = access_token('POST', path, payload.to_json)
|
31
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
32
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
33
|
+
end
|
34
|
+
|
35
|
+
# https://developers.mixin.one/api/alpha-mixin-network/read-address/
|
36
|
+
def get_withdraw_address(address)
|
37
|
+
path = format('/addresses/%<address>s', address: address)
|
38
|
+
access_token = access_token('GET', path, '')
|
39
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
40
|
+
client.get(path, headers: { 'Authorization': authorization })
|
41
|
+
end
|
42
|
+
|
43
|
+
# https://developers.mixin.one/api/alpha-mixin-network/delete-address/
|
44
|
+
def delete_withdraw_address(address, pin)
|
45
|
+
path = format('/addresses/%<address>s/delete', address: address)
|
46
|
+
payload = {
|
47
|
+
pin: encrypt_pin(pin)
|
48
|
+
}
|
49
|
+
|
50
|
+
access_token = access_token('POST', path, payload.to_json)
|
51
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
52
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
53
|
+
end
|
54
|
+
|
55
|
+
# https://developers.mixin.one/api/alpha-mixin-network/withdrawal-addresses/
|
56
|
+
def withdrawals(options)
|
57
|
+
address_id = options[:address_id]
|
58
|
+
pin = options[:pin]
|
59
|
+
amount = options[:amount]
|
60
|
+
trace_id = options[:trace_id]
|
61
|
+
memo = options[:memo]
|
62
|
+
|
63
|
+
path = '/withdrawals'
|
64
|
+
payload = {
|
65
|
+
address_id: address_id,
|
66
|
+
amount: amount,
|
67
|
+
trace_id: trace_id,
|
68
|
+
memo: memo,
|
69
|
+
pin: encrypt_pin(pin)
|
70
|
+
}
|
71
|
+
|
72
|
+
access_token = access_token('POST', path, payload.to_json)
|
73
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
74
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/mixin_bot/api.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative './client'
|
2
4
|
require_relative './errors'
|
3
5
|
require_relative './api/auth'
|
@@ -9,14 +11,15 @@ require_relative './api/pin'
|
|
9
11
|
require_relative './api/snapshot'
|
10
12
|
require_relative './api/transfer'
|
11
13
|
require_relative './api/user'
|
14
|
+
require_relative './api/withdraw'
|
12
15
|
|
13
16
|
module MixinBot
|
14
17
|
class API
|
15
18
|
attr_reader :client_id, :client_secret, :session_id, :pin_token, :private_key
|
16
19
|
attr_reader :client
|
17
20
|
|
18
|
-
def initialize(options={})
|
19
|
-
@client_id = options[:client_id]
|
21
|
+
def initialize(options = {})
|
22
|
+
@client_id = options[:client_id] || MixinBot.client_id
|
20
23
|
@client_secret = options[:client_secret] || MixinBot.client_secret
|
21
24
|
@session_id = options[:session_id] || MixinBot.session_id
|
22
25
|
@pin_token = Base64.decode64 options[:pin_token] || MixinBot.pin_token
|
@@ -33,5 +36,6 @@ module MixinBot
|
|
33
36
|
include MixinBot::API::Snapshot
|
34
37
|
include MixinBot::API::Transfer
|
35
38
|
include MixinBot::API::User
|
39
|
+
include MixinBot::API::Withdraw
|
36
40
|
end
|
37
41
|
end
|
data/lib/mixin_bot/client.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
class Client
|
3
|
-
SERVER_SCHEME = 'https'
|
4
|
-
SERVER_HOST = 'api.mixin.one'
|
5
|
+
SERVER_SCHEME = 'https'
|
6
|
+
SERVER_HOST = 'api.mixin.one'
|
5
7
|
|
6
8
|
def get(path, options = {})
|
7
9
|
request(:get, path, options)
|
@@ -14,30 +16,24 @@ module MixinBot
|
|
14
16
|
private
|
15
17
|
|
16
18
|
def request(verb, path, options = {})
|
17
|
-
uri = uri_for
|
18
|
-
options = options.with_indifferent_access
|
19
|
+
uri = uri_for path
|
19
20
|
|
20
|
-
options[
|
21
|
-
|
22
|
-
options['headers']['Content-Type'] = 'application/json'
|
23
|
-
end
|
21
|
+
options[:headers] ||= {}
|
22
|
+
options[:headers]['Content-Type'] ||= 'application/json'
|
24
23
|
|
25
24
|
begin
|
26
25
|
response = HTTP.timeout(connect: 5, write: 5, read: 5).request(verb, uri, options)
|
27
|
-
rescue HTTP::Error =>
|
28
|
-
|
29
|
-
Rails.logger.error ex.backtrace.join("\n")
|
30
|
-
raise Errors::HttpError, ex.message
|
26
|
+
rescue HTTP::Error => e
|
27
|
+
raise Errors::HttpError, e.message
|
31
28
|
end
|
32
29
|
|
33
|
-
unless response.status.success?
|
34
|
-
raise Errors::APIError.new(nil, response.to_s)
|
35
|
-
end
|
30
|
+
raise Errors::APIError.new(nil, response.to_s) unless response.status.success?
|
36
31
|
|
37
32
|
parse_response(response) do |parse_as, result|
|
38
33
|
case parse_as
|
39
34
|
when :json
|
40
|
-
break result if result[:errcode].
|
35
|
+
break result if result[:errcode].nil? || result[:errcode].zero?
|
36
|
+
|
41
37
|
raise Errors::APIError.new(result[:errcode], result[:errmsg])
|
42
38
|
else
|
43
39
|
result
|
@@ -58,34 +54,32 @@ module MixinBot
|
|
58
54
|
content_type = response.headers[:content_type]
|
59
55
|
parse_as = {
|
60
56
|
%r{^application\/json} => :json,
|
61
|
-
%r{^image\/.*}
|
62
|
-
%r{^text\/html}
|
63
|
-
%r{^text\/plain}
|
57
|
+
%r{^image\/.*} => :file,
|
58
|
+
%r{^text\/html} => :xml,
|
59
|
+
%r{^text\/plain} => :plain
|
64
60
|
}.each_with_object([]) { |match, memo| memo << match[1] if content_type =~ match[0] }.first || :plain
|
65
61
|
|
66
62
|
if parse_as == :plain
|
67
|
-
result =
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
return yield(:plain, response.body)
|
72
|
-
end
|
63
|
+
result = JSON.parse(response&.body&.to_s)
|
64
|
+
result && yield(:json, result)
|
65
|
+
|
66
|
+
yield(:plain, response.body)
|
73
67
|
end
|
74
68
|
|
75
69
|
case parse_as
|
76
70
|
when :json
|
77
|
-
result =
|
71
|
+
result = JSON.parse(response.body.to_s)
|
78
72
|
when :file
|
79
|
-
|
80
|
-
|
73
|
+
extension =
|
74
|
+
if response.headers[:content_type] =~ %r{^image\/.*}
|
81
75
|
case response.headers['content-type']
|
82
76
|
when 'image/gif' then '.gif'
|
83
77
|
when 'image/jpeg' then '.jpg'
|
84
78
|
when 'image/png' then '.png'
|
85
79
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
80
|
+
else
|
81
|
+
''
|
82
|
+
end
|
89
83
|
|
90
84
|
begin
|
91
85
|
file = Tempfile.new(['mixin-file-', extension])
|
data/lib/mixin_bot/errors.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MixinBot
|
2
4
|
module Errors
|
3
5
|
# 通用异常
|
@@ -14,7 +16,7 @@ module MixinBot
|
|
14
16
|
@errcode = errcode
|
15
17
|
@errmsg = errmsg
|
16
18
|
|
17
|
-
super(format('[
|
19
|
+
super(format('[%<errcode>s]: %<errmsg>s', errcode: @errcode, errmsg: @errmsg))
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
data/lib/mixin_bot/version.rb
CHANGED
data/lib/mixin_bot.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'http'
|
3
4
|
require 'base64'
|
4
5
|
require 'openssl'
|
@@ -12,6 +13,6 @@ module MixinBot
|
|
12
13
|
end
|
13
14
|
|
14
15
|
def self.api
|
15
|
-
@api ||= MixinBot::API.new
|
16
|
+
@api ||= MixinBot::API.new
|
16
17
|
end
|
17
18
|
end
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mixin_bot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- an-lee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: bcrypt
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: http
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: jwt
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -67,13 +67,55 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
|
-
type: :
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop-rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
77
119
|
prerelease: false
|
78
120
|
version_requirements: !ruby/object:Gem::Requirement
|
79
121
|
requirements:
|
@@ -99,6 +141,7 @@ files:
|
|
99
141
|
- lib/mixin_bot/api/snapshot.rb
|
100
142
|
- lib/mixin_bot/api/transfer.rb
|
101
143
|
- lib/mixin_bot/api/user.rb
|
144
|
+
- lib/mixin_bot/api/withdraw.rb
|
102
145
|
- lib/mixin_bot/client.rb
|
103
146
|
- lib/mixin_bot/errors.rb
|
104
147
|
- lib/mixin_bot/version.rb
|