mixin_bot 0.0.1.4 → 0.1.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.
- 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
|