mixin_bot 0.0.1.4 → 0.3.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/bin/mixinbot +6 -0
- data/lib/mixin_bot.rb +14 -6
- data/lib/mixin_bot/api.rb +49 -7
- 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 +24 -8
- data/lib/mixin_bot/api/blaze.rb +62 -0
- data/lib/mixin_bot/api/conversation.rb +25 -14
- data/lib/mixin_bot/api/me.rb +25 -16
- data/lib/mixin_bot/api/message.rb +286 -34
- data/lib/mixin_bot/api/multisig.rb +335 -0
- data/lib/mixin_bot/api/payment.rb +16 -16
- data/lib/mixin_bot/api/pin.rb +58 -22
- data/lib/mixin_bot/api/snapshot.rb +32 -17
- data/lib/mixin_bot/api/transfer.rb +17 -17
- data/lib/mixin_bot/api/user.rb +61 -13
- data/lib/mixin_bot/api/withdraw.rb +78 -0
- 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 +37 -38
- data/lib/mixin_bot/errors.rb +3 -1
- data/lib/mixin_bot/version.rb +3 -1
- metadata +150 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 490beec97c1c3ba36f339f4a947f7717ee7eafa0aad01ba7d36c0ad8962af0e2
|
4
|
+
data.tar.gz: e00974b0b39f0002d60ced9f3ad416ce4b0addc63ba2d6e14584fc5b3f64a4c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccf975f89a77c9421151ec6235939a9b1e78359253597daa81e70e7e4badd1362e9c617425e547681ac25be72bdad25c0d56334dc9cc9a56b97abe0b08913dd7
|
7
|
+
data.tar.gz: c6d2dabfd14fa960f32549e2ba082d275a618f058b25ab54217e900197d42d10659c48a36e484aff55ed58c92e3355c35a121e599e6b110286bbd9ac3fc06b22
|
data/bin/mixinbot
ADDED
data/lib/mixin_bot.rb
CHANGED
@@ -1,17 +1,25 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
3
4
|
require 'base64'
|
4
|
-
require '
|
5
|
-
require '
|
5
|
+
require 'digest'
|
6
|
+
require 'faye/websocket'
|
7
|
+
require 'http'
|
6
8
|
require 'jose'
|
9
|
+
require 'jwt'
|
10
|
+
require 'msgpack'
|
11
|
+
require 'open3'
|
12
|
+
require 'openssl'
|
7
13
|
require_relative './mixin_bot/api'
|
14
|
+
require_relative './mixin_bot/cli'
|
15
|
+
require_relative './mixin_bot/version'
|
8
16
|
|
9
17
|
module MixinBot
|
10
18
|
class<< self
|
11
|
-
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
|
12
20
|
end
|
13
21
|
|
14
22
|
def self.api
|
15
|
-
@api ||= MixinBot::API.new
|
23
|
+
@api ||= MixinBot::API.new
|
16
24
|
end
|
17
25
|
end
|
data/lib/mixin_bot/api.rb
CHANGED
@@ -1,37 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative './client'
|
2
4
|
require_relative './errors'
|
5
|
+
require_relative './api/app'
|
6
|
+
require_relative './api/attachment'
|
3
7
|
require_relative './api/auth'
|
8
|
+
require_relative './api/blaze'
|
4
9
|
require_relative './api/conversation'
|
5
10
|
require_relative './api/me'
|
6
11
|
require_relative './api/message'
|
12
|
+
require_relative './api/multisig'
|
7
13
|
require_relative './api/payment'
|
8
14
|
require_relative './api/pin'
|
9
15
|
require_relative './api/snapshot'
|
10
16
|
require_relative './api/transfer'
|
11
17
|
require_relative './api/user'
|
18
|
+
require_relative './api/withdraw'
|
12
19
|
|
13
20
|
module MixinBot
|
14
21
|
class API
|
15
|
-
attr_reader :client_id, :client_secret, :session_id, :pin_token, :private_key
|
16
|
-
attr_reader :client
|
22
|
+
attr_reader :client_id, :client_secret, :session_id, :pin_token, :private_key, :client, :blaze_host, :schmoozer
|
17
23
|
|
18
|
-
def initialize(options={})
|
19
|
-
@client_id = options[:client_id]
|
24
|
+
def initialize(options = {})
|
25
|
+
@client_id = options[:client_id] || MixinBot.client_id
|
20
26
|
@client_secret = options[:client_secret] || MixinBot.client_secret
|
21
27
|
@session_id = options[:session_id] || MixinBot.session_id
|
22
|
-
@pin_token = Base64.
|
23
|
-
@
|
24
|
-
@
|
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
|
38
|
+
end
|
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
|
25
49
|
end
|
26
50
|
|
51
|
+
include MixinBot::API::App
|
52
|
+
include MixinBot::API::Attachment
|
27
53
|
include MixinBot::API::Auth
|
54
|
+
include MixinBot::API::Blaze
|
28
55
|
include MixinBot::API::Conversation
|
29
56
|
include MixinBot::API::Me
|
30
57
|
include MixinBot::API::Message
|
58
|
+
include MixinBot::API::Multisig
|
31
59
|
include MixinBot::API::Payment
|
32
60
|
include MixinBot::API::Pin
|
33
61
|
include MixinBot::API::Snapshot
|
34
62
|
include MixinBot::API::Transfer
|
35
63
|
include MixinBot::API::User
|
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
|
36
78
|
end
|
37
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
@@ -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, scp: 'FULL')
|
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
|
@@ -12,9 +14,19 @@ module MixinBot
|
|
12
14
|
iat: iat,
|
13
15
|
exp: exp,
|
14
16
|
jti: jti,
|
15
|
-
sig: sig
|
17
|
+
sig: sig,
|
18
|
+
scp: scp
|
16
19
|
}
|
17
|
-
|
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
|
18
30
|
end
|
19
31
|
|
20
32
|
def oauth_token(code)
|
@@ -28,12 +40,16 @@ module MixinBot
|
|
28
40
|
|
29
41
|
raise r.inspect if r['error'].present?
|
30
42
|
|
31
|
-
|
43
|
+
r['data']&.[]('access_token')
|
32
44
|
end
|
33
45
|
|
34
|
-
def request_oauth(scope=nil)
|
35
|
-
scope ||= (MixinBot.scope || 'PROFILE:READ
|
36
|
-
format(
|
46
|
+
def request_oauth(scope = nil)
|
47
|
+
scope ||= (MixinBot.scope || 'PROFILE:READ')
|
48
|
+
format(
|
49
|
+
'https://mixin.one/oauth/authorize?client_id=%<client_id>s&scope=%<scope>s',
|
50
|
+
client_id: client_id,
|
51
|
+
scope: scope
|
52
|
+
)
|
37
53
|
end
|
38
54
|
end
|
39
55
|
end
|
@@ -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
|
@@ -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,21 +28,30 @@ 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
|
|
34
|
-
def unique_conversation_id(user_id)
|
36
|
+
def unique_conversation_id(user_id, opponent_id = nil)
|
37
|
+
opponent_id ||= client_id
|
35
38
|
md5 = Digest::MD5.new
|
36
|
-
md5 << [user_id,
|
37
|
-
md5 << [user_id,
|
39
|
+
md5 << [user_id, opponent_id].min
|
40
|
+
md5 << [user_id, opponent_id].max
|
38
41
|
digest = md5.digest
|
39
|
-
|
40
|
-
|
41
|
-
cipher = digest[0...6] +
|
42
|
-
hex = cipher.
|
43
|
-
|
42
|
+
digest6 = (digest[6].ord & 0x0f | 0x30).chr
|
43
|
+
digest8 = (digest[8].ord & 0x3f | 0x80).chr
|
44
|
+
cipher = digest[0...6] + digest6 + digest[7] + digest8 + digest[9..]
|
45
|
+
hex = cipher.unpack1('H*')
|
46
|
+
|
47
|
+
format(
|
48
|
+
'%<first>s-%<second>s-%<third>s-%<forth>s-%<fifth>s',
|
49
|
+
first: hex[0..7],
|
50
|
+
second: hex[8..11],
|
51
|
+
third: hex[12..15],
|
52
|
+
forth: hex[16..19],
|
53
|
+
fifth: hex[20..]
|
54
|
+
)
|
44
55
|
end
|
45
56
|
end
|
46
57
|
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(access_token: nil)
|
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, access_token: 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(access_token: nil)
|
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, access_token: nil)
|
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(access_token: nil)
|
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
|