mixin_bot 0.1.1 → 0.3.2
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/bin/mixinbot +6 -0
- data/lib/mixin_bot.rb +11 -4
- data/lib/mixin_bot/api.rb +43 -5
- data/lib/mixin_bot/api/app.rb +31 -0
- data/lib/mixin_bot/api/attachment.rb +32 -0
- data/lib/mixin_bot/api/auth.rb +14 -4
- data/lib/mixin_bot/api/blaze.rb +62 -0
- data/lib/mixin_bot/api/conversation.rb +6 -5
- data/lib/mixin_bot/api/me.rb +4 -4
- data/lib/mixin_bot/api/message.rb +46 -9
- data/lib/mixin_bot/api/multisig.rb +335 -0
- data/lib/mixin_bot/api/pin.rb +30 -16
- data/lib/mixin_bot/api/snapshot.rb +23 -14
- data/lib/mixin_bot/api/transfer.rb +4 -3
- data/lib/mixin_bot/api/user.rb +21 -7
- data/lib/mixin_bot/api/withdraw.rb +10 -10
- data/lib/mixin_bot/cli.rb +128 -0
- data/lib/mixin_bot/cli/me.rb +40 -0
- data/lib/mixin_bot/cli/multisig.rb +11 -0
- data/lib/mixin_bot/cli/node.rb +107 -0
- data/lib/mixin_bot/client.rb +17 -12
- data/lib/mixin_bot/version.rb +1 -1
- metadata +130 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11583daf7567ac673396219c79076e30a6694848e9d9887cc214c8db3a12c7cb
|
4
|
+
data.tar.gz: 668a5ffd78cfa55628e3fa49f44e0f1e028f74bf850265ec27b8ef7b95dfb49b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01be5ec88fcc8f9b6b511d0f0ae6f679075aa965a3993133cdd31c5e7fd463d4c2864075a105c4034d35fad5b787940befa6fd602b486e8433139422475184f4
|
7
|
+
data.tar.gz: 5e86eff08885ea64958d2e79e51de906e2c727cb70b87437927c495933efa90756a0df38e712c40c1e4666fe8c8b6fe81345121fcd2eafa7e35dbb1da03fd4cd
|
data/bin/mixinbot
ADDED
data/lib/mixin_bot.rb
CHANGED
@@ -1,15 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'English'
|
4
4
|
require 'base64'
|
5
|
-
require '
|
6
|
-
require '
|
5
|
+
require 'digest'
|
6
|
+
require 'faye/websocket'
|
7
|
+
require 'http'
|
7
8
|
require 'jose'
|
9
|
+
require 'msgpack'
|
10
|
+
require 'open3'
|
11
|
+
require 'openssl'
|
12
|
+
require 'rbnacl'
|
8
13
|
require_relative './mixin_bot/api'
|
14
|
+
require_relative './mixin_bot/cli'
|
15
|
+
require_relative './mixin_bot/version'
|
9
16
|
|
10
17
|
module MixinBot
|
11
18
|
class<< self
|
12
|
-
attr_accessor :client_id, :client_secret, :session_id, :pin_token, :private_key, :scope
|
19
|
+
attr_accessor :client_id, :client_secret, :session_id, :pin_token, :private_key, :scope, :api_host, :blaze_host
|
13
20
|
end
|
14
21
|
|
15
22
|
def self.api
|
data/lib/mixin_bot/api.rb
CHANGED
@@ -2,10 +2,14 @@
|
|
2
2
|
|
3
3
|
require_relative './client'
|
4
4
|
require_relative './errors'
|
5
|
+
require_relative './api/app'
|
6
|
+
require_relative './api/attachment'
|
5
7
|
require_relative './api/auth'
|
8
|
+
require_relative './api/blaze'
|
6
9
|
require_relative './api/conversation'
|
7
10
|
require_relative './api/me'
|
8
11
|
require_relative './api/message'
|
12
|
+
require_relative './api/multisig'
|
9
13
|
require_relative './api/payment'
|
10
14
|
require_relative './api/pin'
|
11
15
|
require_relative './api/snapshot'
|
@@ -15,27 +19,61 @@ require_relative './api/withdraw'
|
|
15
19
|
|
16
20
|
module MixinBot
|
17
21
|
class API
|
18
|
-
attr_reader :client_id, :client_secret, :session_id, :pin_token, :private_key
|
19
|
-
attr_reader :client
|
22
|
+
attr_reader :client_id, :client_secret, :session_id, :pin_token, :private_key, :client, :blaze_host, :schmoozer
|
20
23
|
|
21
24
|
def initialize(options = {})
|
22
25
|
@client_id = options[:client_id] || MixinBot.client_id
|
23
26
|
@client_secret = options[:client_secret] || MixinBot.client_secret
|
24
27
|
@session_id = options[:session_id] || MixinBot.session_id
|
25
|
-
@pin_token = Base64.
|
26
|
-
@
|
27
|
-
@
|
28
|
+
@pin_token = Base64.urlsafe_decode64 options[:pin_token] || MixinBot.pin_token
|
29
|
+
@client = Client.new(MixinBot.api_host || 'api.mixin.one')
|
30
|
+
@blaze_host = MixinBot.blaze_host || 'blaze.mixin.one'
|
31
|
+
_private_key = options[:private_key] || MixinBot.private_key
|
32
|
+
@private_key =
|
33
|
+
if /^-----BEGIN RSA PRIVATE KEY-----/.match? _private_key
|
34
|
+
_private_key.gsub('\\r\\n', "\n").gsub("\r\n", "\n")
|
35
|
+
else
|
36
|
+
Base64.urlsafe_decode64 _private_key
|
37
|
+
end
|
28
38
|
end
|
29
39
|
|
40
|
+
# Use a mixin software to implement transaction build
|
41
|
+
def build_transaction(json)
|
42
|
+
ensure_mixin_command_exist
|
43
|
+
command = format("mixin signrawtransaction --raw '%<arg>s'", arg: json)
|
44
|
+
|
45
|
+
output, error = Open3.capture3(command)
|
46
|
+
raise error unless error.empty?
|
47
|
+
|
48
|
+
output.chomp
|
49
|
+
end
|
50
|
+
|
51
|
+
include MixinBot::API::App
|
52
|
+
include MixinBot::API::Attachment
|
30
53
|
include MixinBot::API::Auth
|
54
|
+
include MixinBot::API::Blaze
|
31
55
|
include MixinBot::API::Conversation
|
32
56
|
include MixinBot::API::Me
|
33
57
|
include MixinBot::API::Message
|
58
|
+
include MixinBot::API::Multisig
|
34
59
|
include MixinBot::API::Payment
|
35
60
|
include MixinBot::API::Pin
|
36
61
|
include MixinBot::API::Snapshot
|
37
62
|
include MixinBot::API::Transfer
|
38
63
|
include MixinBot::API::User
|
39
64
|
include MixinBot::API::Withdraw
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def ensure_mixin_command_exist
|
69
|
+
return if command?('mixin')
|
70
|
+
|
71
|
+
raise '`mixin` command is not valid!'
|
72
|
+
end
|
73
|
+
|
74
|
+
def command?(name)
|
75
|
+
`which #{name}`
|
76
|
+
$CHILD_STATUS.success?
|
77
|
+
end
|
40
78
|
end
|
41
79
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module App
|
6
|
+
def add_favorite_app(app_id, access_token: nil)
|
7
|
+
path = format('/apps/%<id>s/favorite', id: app_id)
|
8
|
+
|
9
|
+
access_token ||= access_token('POST', path, {})
|
10
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
11
|
+
client.post(path, headers: { 'Authorization': authorization })
|
12
|
+
end
|
13
|
+
|
14
|
+
def remove_favorite_app(app_id, access_token: nil)
|
15
|
+
path = format('/apps/%<id>s/unfavorite', id: app_id)
|
16
|
+
|
17
|
+
access_token ||= access_token('POST', path, '')
|
18
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
19
|
+
client.post(path, headers: { 'Authorization': authorization })
|
20
|
+
end
|
21
|
+
|
22
|
+
def favorite_apps(user_id, access_token: nil)
|
23
|
+
path = format('/users/%<id>s/apps/favorite', id: user_id)
|
24
|
+
|
25
|
+
access_token ||= access_token('GET', path, '')
|
26
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
27
|
+
client.get(path, headers: { 'Authorization': authorization })
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module Attachment
|
6
|
+
# https://developers.mixin.one/api/beta-mixin-message/create-attachment/
|
7
|
+
# Sample Response
|
8
|
+
# {
|
9
|
+
# "data":{
|
10
|
+
# "type":"attachment",
|
11
|
+
# "attachment_id":"7a54e394-1626-4cd4-b967-543932c2a032",
|
12
|
+
# "upload_url":"https://moments-shou-tv.s3.amazonaws.com/mixin/attachments/xxx",
|
13
|
+
# "view_url":"https://moments.shou.tv/mixin/attachments/1526305123xxxx"
|
14
|
+
# }
|
15
|
+
# }
|
16
|
+
# Once get the upload_url, use it to upload the your file via PUT request
|
17
|
+
def create_attachment
|
18
|
+
path = '/attachments'
|
19
|
+
access_token ||= access_token('POST', path, {}.to_json)
|
20
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
21
|
+
client.post(path, headers: { 'Authorization': authorization }, json: {})
|
22
|
+
end
|
23
|
+
|
24
|
+
def read_attachment(attachment_id)
|
25
|
+
path = format('/attachments/%<id>s', id: attachment_id)
|
26
|
+
access_token ||= access_token('GET', path, '')
|
27
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
28
|
+
client.get(path, headers: { 'Authorization': authorization })
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/mixin_bot/api/auth.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module MixinBot
|
4
4
|
class API
|
5
5
|
module Auth
|
6
|
-
def access_token(method, uri, body = '', exp_in
|
6
|
+
def access_token(method, uri, body = '', exp_in: 600, scp: 'FULL')
|
7
7
|
sig = Digest::SHA256.hexdigest(method + uri + body)
|
8
8
|
iat = Time.now.utc.to_i
|
9
9
|
exp = (Time.now.utc + exp_in).to_i
|
@@ -14,9 +14,19 @@ module MixinBot
|
|
14
14
|
iat: iat,
|
15
15
|
exp: exp,
|
16
16
|
jti: jti,
|
17
|
-
sig: sig
|
17
|
+
sig: sig,
|
18
|
+
scp: scp
|
18
19
|
}
|
19
|
-
|
20
|
+
if pin_token.size == 32
|
21
|
+
jwk = JOSE::JWK.from_okp [:Ed25519, private_key]
|
22
|
+
jws = JOSE::JWS.from({ 'alg' => 'EdDSA' })
|
23
|
+
else
|
24
|
+
jwk = JOSE::JWK.from_pem private_key
|
25
|
+
jws = JOSE::JWS.from({ 'alg' => 'RS512' })
|
26
|
+
end
|
27
|
+
|
28
|
+
jwt = JOSE::JWT.from payload
|
29
|
+
JOSE::JWT.sign(jwk, jws, jwt).compact
|
20
30
|
end
|
21
31
|
|
22
32
|
def oauth_token(code)
|
@@ -34,7 +44,7 @@ module MixinBot
|
|
34
44
|
end
|
35
45
|
|
36
46
|
def request_oauth(scope = nil)
|
37
|
-
scope ||= (MixinBot.scope || 'PROFILE:READ
|
47
|
+
scope ||= (MixinBot.scope || 'PROFILE:READ')
|
38
48
|
format(
|
39
49
|
'https://mixin.one/oauth/authorize?client_id=%<client_id>s&scope=%<scope>s',
|
40
50
|
client_id: client_id,
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MixinBot
|
4
|
+
class API
|
5
|
+
module Blaze
|
6
|
+
def blaze
|
7
|
+
access_token = access_token('GET', '/', '')
|
8
|
+
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
9
|
+
Faye::WebSocket::Client.new(
|
10
|
+
format('wss://%<host>s/', host: blaze_host),
|
11
|
+
['Mixin-Blaze-1'],
|
12
|
+
headers: { 'Authorization' => authorization },
|
13
|
+
ping: 60
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_blaze_connect(reconnect: true, &_block)
|
18
|
+
ws ||= blaze
|
19
|
+
yield if block_given?
|
20
|
+
|
21
|
+
ws.on :open do |event|
|
22
|
+
if defined? on_open
|
23
|
+
on_open ws, event
|
24
|
+
else
|
25
|
+
p [Time.now.to_s, :open]
|
26
|
+
ws.send list_pending_message
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
ws.on :message do |event|
|
31
|
+
if defined? on_message
|
32
|
+
on_message ws, event
|
33
|
+
else
|
34
|
+
raw = JSON.parse read_ws_message(event.data)
|
35
|
+
p [Time.now.to_s, :message, raw&.[]('action')]
|
36
|
+
|
37
|
+
ws.send acknowledge_message_receipt(raw['data']['message_id']) unless raw&.[]('data')&.[]('message_id').nil?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
ws.on :error do |event|
|
42
|
+
if defined? on_error
|
43
|
+
on_error ws, event
|
44
|
+
else
|
45
|
+
p [Time.now.to_s, :error]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
ws.on :close do |event|
|
50
|
+
if defined? on_close
|
51
|
+
on_close ws, event
|
52
|
+
else
|
53
|
+
p [Time.now.to_s, :close, event.code, event.reason]
|
54
|
+
end
|
55
|
+
|
56
|
+
ws = nil
|
57
|
+
start_blaze_connect(&block) if reconnect
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -33,14 +33,15 @@ module MixinBot
|
|
33
33
|
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
34
34
|
end
|
35
35
|
|
36
|
-
def unique_conversation_id(user_id)
|
36
|
+
def unique_conversation_id(user_id, opponent_id = nil)
|
37
|
+
opponent_id ||= client_id
|
37
38
|
md5 = Digest::MD5.new
|
38
|
-
md5 << [user_id,
|
39
|
-
md5 << [user_id,
|
39
|
+
md5 << [user_id, opponent_id].min
|
40
|
+
md5 << [user_id, opponent_id].max
|
40
41
|
digest = md5.digest
|
41
42
|
digest6 = (digest[6].ord & 0x0f | 0x30).chr
|
42
43
|
digest8 = (digest[8].ord & 0x3f | 0x80).chr
|
43
|
-
cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9
|
44
|
+
cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..]
|
44
45
|
hex = cipher.unpack1('H*')
|
45
46
|
|
46
47
|
format(
|
@@ -49,7 +50,7 @@ module MixinBot
|
|
49
50
|
second: hex[8..11],
|
50
51
|
third: hex[12..15],
|
51
52
|
forth: hex[16..19],
|
52
|
-
fifth: hex[20
|
53
|
+
fifth: hex[20..]
|
53
54
|
)
|
54
55
|
end
|
55
56
|
end
|
data/lib/mixin_bot/api/me.rb
CHANGED
@@ -4,7 +4,7 @@ module MixinBot
|
|
4
4
|
class API
|
5
5
|
module Me
|
6
6
|
# https://developers.mixin.one/api/beta-mixin-message/read-profile/
|
7
|
-
def read_me(access_token
|
7
|
+
def read_me(access_token: nil)
|
8
8
|
path = '/me'
|
9
9
|
access_token ||= access_token('GET', path, '')
|
10
10
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
@@ -26,7 +26,7 @@ module MixinBot
|
|
26
26
|
end
|
27
27
|
|
28
28
|
# https://developers.mixin.one/api/alpha-mixin-network/read-assets/
|
29
|
-
def read_assets(access_token
|
29
|
+
def read_assets(access_token: nil)
|
30
30
|
path = '/assets'
|
31
31
|
access_token ||= access_token('GET', path, '')
|
32
32
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
@@ -34,7 +34,7 @@ module MixinBot
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# https://developers.mixin.one/api/alpha-mixin-network/read-asset/
|
37
|
-
def read_asset(asset_id, access_token
|
37
|
+
def read_asset(asset_id, access_token: nil)
|
38
38
|
path = format('/assets/%<asset_id>s', asset_id: asset_id)
|
39
39
|
access_token ||= access_token('GET', path, '')
|
40
40
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
@@ -42,7 +42,7 @@ module MixinBot
|
|
42
42
|
end
|
43
43
|
|
44
44
|
# https://developers.mixin.one/api/beta-mixin-message/friends/
|
45
|
-
def read_friends(access_token
|
45
|
+
def read_friends(access_token: nil)
|
46
46
|
path = '/friends'
|
47
47
|
access_token ||= access_token('GET', path, '')
|
48
48
|
authorization = format('Bearer %<access_token>s', access_token: access_token)
|
@@ -41,6 +41,22 @@ module MixinBot
|
|
41
41
|
base_message_params(options)
|
42
42
|
end
|
43
43
|
|
44
|
+
# {
|
45
|
+
# "id": "UUID // generated by client",
|
46
|
+
# "action": "CREATE_MESSAGE",
|
47
|
+
# "params": {
|
48
|
+
# "conversation_id": "UUID",
|
49
|
+
# "category": "PLAIN_POST",
|
50
|
+
# "status": "SENT",
|
51
|
+
# "message_id": "UUID // generated by client",
|
52
|
+
# "data": "Base64 encoded data content is markdown" ,
|
53
|
+
# }
|
54
|
+
# }
|
55
|
+
def plain_post(options)
|
56
|
+
options.merge!(category: 'PLAIN_POST')
|
57
|
+
base_message_params(options)
|
58
|
+
end
|
59
|
+
|
44
60
|
# {
|
45
61
|
# "id": "UUID",
|
46
62
|
# "action": "CREATE_MESSAGE",
|
@@ -203,15 +219,29 @@ module MixinBot
|
|
203
219
|
base_message_params(options)
|
204
220
|
end
|
205
221
|
|
222
|
+
def recall_message_params(message_id, options)
|
223
|
+
raise 'recipient_id is required!' if options[:recipient_id].nil?
|
224
|
+
|
225
|
+
options.merge!(
|
226
|
+
category: 'MESSAGE_RECALL',
|
227
|
+
data: {
|
228
|
+
message_id: message_id
|
229
|
+
}
|
230
|
+
)
|
231
|
+
base_message_params(options)
|
232
|
+
end
|
233
|
+
|
206
234
|
# base format of message params
|
207
|
-
def base_message_params(
|
208
|
-
data = data.is_a?(String) ? data : data.to_json
|
235
|
+
def base_message_params(options)
|
236
|
+
data = options[:data].is_a?(String) ? options[:data] : options[:data].to_json
|
209
237
|
{
|
210
|
-
conversation_id: conversation_id,
|
211
|
-
|
238
|
+
conversation_id: options[:conversation_id],
|
239
|
+
recipient_id: options[:recipient_id],
|
240
|
+
representative_id: options[:representative_id],
|
241
|
+
category: options[:category],
|
212
242
|
status: 'SENT',
|
213
|
-
quote_message_id: quote_message_id,
|
214
|
-
message_id: message_id || SecureRandom.uuid,
|
243
|
+
quote_message_id: options[:quote_message_id],
|
244
|
+
message_id: options[:message_id] || SecureRandom.uuid,
|
215
245
|
data: Base64.encode64(data)
|
216
246
|
}
|
217
247
|
end
|
@@ -227,7 +257,7 @@ module MixinBot
|
|
227
257
|
end
|
228
258
|
|
229
259
|
# gzip the message for websocket
|
230
|
-
def write_ws_message(action: 'CREATE_MESSAGE'
|
260
|
+
def write_ws_message(params:, action: 'CREATE_MESSAGE')
|
231
261
|
msg = {
|
232
262
|
id: SecureRandom.uuid,
|
233
263
|
action: action,
|
@@ -246,6 +276,10 @@ module MixinBot
|
|
246
276
|
send_message plain_text(options)
|
247
277
|
end
|
248
278
|
|
279
|
+
def send_post_message(options)
|
280
|
+
send_message plain_post(options)
|
281
|
+
end
|
282
|
+
|
249
283
|
def send_contact_message(options)
|
250
284
|
send_message plain_contact(options)
|
251
285
|
end
|
@@ -258,6 +292,10 @@ module MixinBot
|
|
258
292
|
send_message app_button_group(options)
|
259
293
|
end
|
260
294
|
|
295
|
+
def recall_message(message_id, options)
|
296
|
+
send_message [recall_message_params(message_id, options)]
|
297
|
+
end
|
298
|
+
|
261
299
|
# {
|
262
300
|
# "id": "UUID",
|
263
301
|
# "action": "CREATE_PLAIN_MESSAGES",
|
@@ -276,9 +314,8 @@ module MixinBot
|
|
276
314
|
# ]
|
277
315
|
# }
|
278
316
|
# }
|
279
|
-
# not verified yet
|
280
317
|
def send_plain_messages(messages)
|
281
|
-
send_message
|
318
|
+
send_message messages
|
282
319
|
end
|
283
320
|
|
284
321
|
# http post request
|