mixin_bot 0.0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/lib/mixin_bot.rb +17 -0
- data/lib/mixin_bot/api.rb +32 -0
- data/lib/mixin_bot/api/auth.rb +39 -0
- data/lib/mixin_bot/api/conversation.rb +33 -0
- data/lib/mixin_bot/api/me.rb +44 -0
- data/lib/mixin_bot/api/message.rb +27 -0
- data/lib/mixin_bot/api/payment.rb +31 -0
- data/lib/mixin_bot/api/pin.rb +57 -0
- data/lib/mixin_bot/api/transfer.rb +44 -0
- data/lib/mixin_bot/api/user.rb +31 -0
- data/lib/mixin_bot/client.rb +108 -0
- data/lib/mixin_bot/errors.rb +21 -0
- data/lib/mixin_bot/version.rb +3 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b67635be46f18811338468b260f58143536dfba2
|
4
|
+
data.tar.gz: e5695a7e88236e880bf997d1d0880f0dca8ac08c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4a80cf2b75cf00e1ce0726e504f70ae83a97683421b8c19dc846060cd6cfccc224898ee896c3f60e5fed29945afb8964a7c28d59e54606f67490a661256e33d5
|
7
|
+
data.tar.gz: e67b7bdc244c2995bd1ae33c044d4a7d7872c1c2bf5a1e70cd8a211f713ce4a1b2a3ded2a011e1904080d84c2e92a74f5704b02e3462796c6d169d1a08ffd282
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 an-lee
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/mixin_bot.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'http'
|
3
|
+
require 'base64'
|
4
|
+
require 'openssl'
|
5
|
+
require 'jwt'
|
6
|
+
require 'jose'
|
7
|
+
require_relative './mixin_bot/api'
|
8
|
+
|
9
|
+
module MixinBot
|
10
|
+
class<< self
|
11
|
+
attr_accessor :client_id, :client_secret, :session_id, :pin_token, :private_key, :scope
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.api
|
15
|
+
@api ||= MixinBot::API.new(options={})
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative './client'
|
2
|
+
require_relative './errors'
|
3
|
+
require_relative './api/auth'
|
4
|
+
require_relative './api/me'
|
5
|
+
require_relative './api/payment'
|
6
|
+
require_relative './api/pin'
|
7
|
+
require_relative './api/transfer'
|
8
|
+
require_relative './api/user'
|
9
|
+
|
10
|
+
module MixinBot
|
11
|
+
class API
|
12
|
+
attr_reader :client_id, :client_secret, :session_id, :pin_token, :private_key, :scope
|
13
|
+
attr_reader :client
|
14
|
+
|
15
|
+
def initialize(options={})
|
16
|
+
@client_id = options[:client_id] || MixinBot.client_id
|
17
|
+
@client_secret = options[:client_secret] || MixinBot.client_secret
|
18
|
+
@session_id = options[:session_id] || MixinBot.session_id
|
19
|
+
@pin_token = Base64.decode64 options[:pin_token] || MixinBot.pin_token
|
20
|
+
@private_key = OpenSSL::PKey::RSA.new options[:private_key] || MixinBot.private_key
|
21
|
+
@scope = options[:scope] || MixinBot.scope || 'PROFILE:READ+PHONE:READ+ASSETS:READ'
|
22
|
+
@client = Client.new
|
23
|
+
end
|
24
|
+
|
25
|
+
include MixinBot::API::Auth
|
26
|
+
include MixinBot::API::Me
|
27
|
+
include MixinBot::API::Payment
|
28
|
+
include MixinBot::API::Pin
|
29
|
+
include MixinBot::API::Transfer
|
30
|
+
include MixinBot::API::User
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MixinBot
|
2
|
+
class API
|
3
|
+
module Auth
|
4
|
+
def access_token(method, uri, body)
|
5
|
+
sig = Digest::SHA256.hexdigest (method + uri + body)
|
6
|
+
iat = Time.now.utc.to_i
|
7
|
+
exp = (Time.now.utc + 1.day).to_i
|
8
|
+
jti = SecureRandom.uuid
|
9
|
+
payload = {
|
10
|
+
'uid': client_id,
|
11
|
+
'sid': session_id,
|
12
|
+
'iat': iat,
|
13
|
+
'exp': exp,
|
14
|
+
'jti': jti,
|
15
|
+
'sig': sig
|
16
|
+
}
|
17
|
+
JWT.encode payload, private_key, 'RS512'
|
18
|
+
end
|
19
|
+
|
20
|
+
def oauth_token(code)
|
21
|
+
path = 'oauth/token'
|
22
|
+
payload = {
|
23
|
+
client_id: client_id,
|
24
|
+
client_secret: client_secret,
|
25
|
+
code: code
|
26
|
+
}
|
27
|
+
r = client.post(path, json: payload)
|
28
|
+
|
29
|
+
raise r.inspect if r['error'].present?
|
30
|
+
|
31
|
+
return r['data']['access_token']
|
32
|
+
end
|
33
|
+
|
34
|
+
def request_oauth
|
35
|
+
format('https://mixin.one/oauth/authorize?client_id=%s&scope=%s', client_id, scope)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MixinBot
|
2
|
+
class API
|
3
|
+
module Conversation
|
4
|
+
def create_conversation
|
5
|
+
path = '/conversations'
|
6
|
+
access_token ||= self.access_token('GET', path, '')
|
7
|
+
params = {
|
8
|
+
category: category,
|
9
|
+
conversation_id: conversation_id,
|
10
|
+
participants: [
|
11
|
+
{
|
12
|
+
action: 'ADD',
|
13
|
+
role: '',
|
14
|
+
user_id: user_id
|
15
|
+
}
|
16
|
+
]
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def unique_conversation_id(user_id)
|
21
|
+
md5 = Digest::MD5.new
|
22
|
+
md5 << user_id
|
23
|
+
md5 << client_id
|
24
|
+
digest = md5.digest
|
25
|
+
digest_6 = (digest[6].ord & 0x0f | 0x30).chr
|
26
|
+
digest_8 = (digest[8].ord & 0x3f | 0x80).chr
|
27
|
+
cipher = digest[0...6] + digest_6 + digest[7] + digest_8 + digest[9..-1]
|
28
|
+
hex = cipher.unpack('H*').first
|
29
|
+
conversation_id = format('%s-%s-%s-%s-%s', hex[0..7], hex[8..11], hex[12..15], hex[16..19], hex[20..-1])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module MixinBot
|
2
|
+
class API
|
3
|
+
module Me
|
4
|
+
def read_me(access_token=nil)
|
5
|
+
path = '/me'
|
6
|
+
access_token ||= self.access_token('GET', path, '')
|
7
|
+
authorization = format('Bearer %s', access_token)
|
8
|
+
client.get(path, headers: { 'Authorization': authorization })
|
9
|
+
end
|
10
|
+
|
11
|
+
def update_me(full_name, avatar_base64, access_token=nil)
|
12
|
+
path = '/me'
|
13
|
+
payload = {
|
14
|
+
"full_name": full_name,
|
15
|
+
"avatar_base64": avatar_base64
|
16
|
+
}
|
17
|
+
access_token ||= self.access_token('POST', path, payload.to_json)
|
18
|
+
authorization = format('Bearer %s', access_token)
|
19
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
20
|
+
end
|
21
|
+
|
22
|
+
def read_assets(access_token=nil)
|
23
|
+
path = '/assets'
|
24
|
+
access_token ||= self.access_token('GET', path, '')
|
25
|
+
authorization = format('Bearer %s', access_token)
|
26
|
+
client.get(path, headers: { 'Authorization': authorization })
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_asset(asset_id, access_token=nil)
|
30
|
+
path = format('/assets/%s', asset_id)
|
31
|
+
access_token ||= self.access_token('GET', path, '')
|
32
|
+
authorization = format('Bearer %s', access_token)
|
33
|
+
client.get(path, headers: { 'Authorization': authorization })
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_friends(access_token=nil)
|
37
|
+
path = '/friends'
|
38
|
+
access_token ||= self.access_token('GET', path, '')
|
39
|
+
authorization = format('Bearer %s', access_token)
|
40
|
+
client.get(path, headers: { 'Authorization': authorization })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module MixinBot
|
2
|
+
class API
|
3
|
+
module Message
|
4
|
+
def read_message(data)
|
5
|
+
io = StringIO.new(data.pack('c*'), 'rb')
|
6
|
+
gzip = Zlib::GzipReader.new io
|
7
|
+
msg = gzip.read
|
8
|
+
gzip.close
|
9
|
+
return msg
|
10
|
+
end
|
11
|
+
|
12
|
+
def write_message(action, params)
|
13
|
+
msg = {
|
14
|
+
"id": SecureRandom.uuid,
|
15
|
+
"action": action,
|
16
|
+
"params": params
|
17
|
+
}.to_json
|
18
|
+
|
19
|
+
io = StringIO.new 'wb'
|
20
|
+
gzip = Zlib::GzipWriter.new io
|
21
|
+
gzip.write msg
|
22
|
+
gzip.close
|
23
|
+
data = io.string.unpack('c*')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MixinBot
|
2
|
+
class API
|
3
|
+
module Payment
|
4
|
+
def pay_url(options)
|
5
|
+
options = options.with_indifferent_access
|
6
|
+
recipient_id = options.fetch('recipient_id')
|
7
|
+
asset_id = options.fetch('asset_id')
|
8
|
+
amount = options.fetch('amount')
|
9
|
+
memo = options.fetch('memo')
|
10
|
+
trace = options.fetch('trace')
|
11
|
+
url = format('https://mixin.one/pay?recipient=%s&asset=%s&amount=%s&trace=%s&memo=%s', recipient_id, asset_id, amount, trace, memo)
|
12
|
+
end
|
13
|
+
|
14
|
+
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
|
+
path = 'payments'
|
21
|
+
payload = {
|
22
|
+
asset_id: asset_id,
|
23
|
+
opponent_id: recipient_id,
|
24
|
+
amount: amount,
|
25
|
+
trace_id: trace,
|
26
|
+
}
|
27
|
+
client.post(path, json: payload)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module MixinBot
|
2
|
+
class API
|
3
|
+
module Pin
|
4
|
+
def verify_pin(pin_code, access_token=nil)
|
5
|
+
path = '/pin/verify'
|
6
|
+
payload = {
|
7
|
+
pin: encrypt_pin(pin_code)
|
8
|
+
}
|
9
|
+
|
10
|
+
access_token ||= self.access_token('POST', path, payload.to_json)
|
11
|
+
authorization = format('Bearer %s', access_token)
|
12
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
13
|
+
end
|
14
|
+
|
15
|
+
def decrypt_pin(msg)
|
16
|
+
msg = Base64.strict_decode64 msg
|
17
|
+
iv = msg[0..15]
|
18
|
+
cipher = msg[16..47]
|
19
|
+
aes_key = JOSE::JWA::PKCS1::rsaes_oaep_decrypt('SHA256', pin_token, private_key, session_id)
|
20
|
+
alg = "AES-256-CBC"
|
21
|
+
decode_cipher = OpenSSL::Cipher.new(alg)
|
22
|
+
decode_cipher.decrypt
|
23
|
+
decode_cipher.iv = iv
|
24
|
+
decode_cipher.key = aes_key
|
25
|
+
plain = decode_cipher.update(cipher)
|
26
|
+
return plain
|
27
|
+
end
|
28
|
+
|
29
|
+
def encrypt_pin(pin_code)
|
30
|
+
aes_key = JOSE::JWA::PKCS1::rsaes_oaep_decrypt('SHA256', pin_token, private_key, session_id)
|
31
|
+
ts = Time.now.utc.to_i
|
32
|
+
tszero = ts % 0x100
|
33
|
+
tsone = (ts % 0x10000) >> 8
|
34
|
+
tstwo = (ts % 0x1000000) >> 16
|
35
|
+
tsthree = (ts % 0x100000000) >> 24
|
36
|
+
tsstring = tszero.chr + tsone.chr + tstwo.chr + tsthree.chr + "\0\0\0\0"
|
37
|
+
encrypt_content = pin_code + tsstring + tsstring
|
38
|
+
pad_count = 16 - encrypt_content.length % 16
|
39
|
+
if pad_count > 0
|
40
|
+
padded_content = encrypt_content + pad_count.chr * pad_count
|
41
|
+
else
|
42
|
+
padded_content = encrypt_content
|
43
|
+
end
|
44
|
+
|
45
|
+
alg = "AES-256-CBC"
|
46
|
+
aes = OpenSSL::Cipher.new(alg)
|
47
|
+
iv = OpenSSL::Cipher.new(alg).random_iv
|
48
|
+
aes.encrypt
|
49
|
+
aes.key = aes_key
|
50
|
+
aes.iv = iv
|
51
|
+
cipher = aes.update(padded_content)
|
52
|
+
msg = iv + cipher
|
53
|
+
return Base64.strict_encode64 msg
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module MixinBot
|
2
|
+
class API
|
3
|
+
module Transfer
|
4
|
+
def create_transfer(pin, options)
|
5
|
+
# data for test:
|
6
|
+
# asset_id = '965e5c6e-434c-3fa9-b780-c50f43cd955c'
|
7
|
+
# opponent_id = '7ed9292d-7c95-4333-aa48-a8c640064186'
|
8
|
+
# amount = '1'
|
9
|
+
# encrypted_pin = MixinBot.api_pin.encrypted_pin
|
10
|
+
# memo = 'test'
|
11
|
+
|
12
|
+
options = options.with_indifferent_access
|
13
|
+
|
14
|
+
asset_id = options.fetch('asset_id')
|
15
|
+
opponent_id = options.fetch('opponent_id')
|
16
|
+
amount = options.fetch('amount')
|
17
|
+
memo = options.fetch('memo')
|
18
|
+
trace_id = options.fetch('trace_id')
|
19
|
+
trace_id ||= SecureRandom.uuid
|
20
|
+
|
21
|
+
path = '/transfers'
|
22
|
+
payload = {
|
23
|
+
asset_id: asset_id,
|
24
|
+
opponent_id: opponent_id,
|
25
|
+
pin: pin,
|
26
|
+
amount: amount,
|
27
|
+
trace_id: trace_id,
|
28
|
+
memo: memo
|
29
|
+
}
|
30
|
+
|
31
|
+
access_token ||= self.access_token('POST', path, payload.to_json)
|
32
|
+
authorization = format('Bearer %s', access_token)
|
33
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_transfer(trace_id)
|
37
|
+
path = format('/transfers/trace/%s', trace_id)
|
38
|
+
access_token ||= self.access_token('GET', path, '')
|
39
|
+
authorization = format('Bearer %s', access_token)
|
40
|
+
client.get(path, headers: { 'Authorization': authorization })
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MixinBot
|
2
|
+
class API
|
3
|
+
module User
|
4
|
+
def read_user(user_id, access_token=nil)
|
5
|
+
# user_id: Mixin User Id
|
6
|
+
path = format('/users/%s', user_id)
|
7
|
+
access_token ||= self.access_token('GET', path, '')
|
8
|
+
authorization = format('Bearer %s', access_token)
|
9
|
+
client.get(path, headers: { 'Authorization': authorization })
|
10
|
+
end
|
11
|
+
|
12
|
+
def search_user(q, access_token=nil)
|
13
|
+
# q: Mixin Id or Phone Number
|
14
|
+
path = format('/search/%s', q)
|
15
|
+
access_token ||= self.access_token('GET', path, '')
|
16
|
+
authorization = format('Bearer %s', access_token)
|
17
|
+
client.get(path, headers: { 'Authorization': authorization })
|
18
|
+
end
|
19
|
+
|
20
|
+
def fetch_users(user_ids, access_token=nil)
|
21
|
+
# user_ids: a array of user_ids
|
22
|
+
path = '/users/fetch'
|
23
|
+
user_ids = [user_ids] if user_ids.is_a? String
|
24
|
+
payload = user_ids
|
25
|
+
access_token ||= self.access_token('POST', path, payload.to_json)
|
26
|
+
authorization = format('Bearer %s', access_token)
|
27
|
+
client.post(path, headers: { 'Authorization': authorization }, json: payload)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module MixinBot
|
2
|
+
class Client
|
3
|
+
SERVER_SCHEME = 'https'.freeze
|
4
|
+
SERVER_HOST = 'api.mixin.one'.freeze
|
5
|
+
|
6
|
+
def get(path, options = {})
|
7
|
+
request(:get, path, options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def post(path, options = {})
|
11
|
+
request(:post, path, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def request(verb, path, options = {})
|
17
|
+
uri = uri_for(path)
|
18
|
+
options = options.with_indifferent_access
|
19
|
+
|
20
|
+
options['headers'] ||= {}
|
21
|
+
if options['headers']['Content-Type'].blank?
|
22
|
+
options['headers']['Content-Type'] = 'application/json'
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
response = HTTP.timeout(:global, connect: 5, write: 5, read: 5).request(verb, uri, options)
|
27
|
+
rescue HTTP::Error => ex
|
28
|
+
Rails.logger.error format('%s (%s):', ex.class.name, ex.message)
|
29
|
+
Rails.logger.error ex.backtrace.join("\n")
|
30
|
+
raise Errors::HttpError, ex.message
|
31
|
+
end
|
32
|
+
|
33
|
+
unless response.status.success?
|
34
|
+
raise Errors::APIError.new(nil, response.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
parse_response(response) do |parse_as, result|
|
38
|
+
case parse_as
|
39
|
+
when :json
|
40
|
+
break result if result[:errcode].blank? || result[:errcode].zero?
|
41
|
+
raise Errors::APIError.new(result[:errcode], result[:errmsg])
|
42
|
+
else
|
43
|
+
result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def uri_for(path)
|
49
|
+
uri_options = {
|
50
|
+
scheme: SERVER_SCHEME,
|
51
|
+
host: SERVER_HOST,
|
52
|
+
path: path
|
53
|
+
}
|
54
|
+
Addressable::URI.new(uri_options)
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_response(response)
|
58
|
+
content_type = response.headers[:content_type]
|
59
|
+
parse_as = {
|
60
|
+
%r{^application\/json} => :json,
|
61
|
+
%r{^image\/.*} => :file,
|
62
|
+
%r{^text\/html} => :xml,
|
63
|
+
%r{^text\/plain} => :plain
|
64
|
+
}.each_with_object([]) { |match, memo| memo << match[1] if content_type =~ match[0] }.first || :plain
|
65
|
+
|
66
|
+
if parse_as == :plain
|
67
|
+
result = ActiveSupport::JSON.decode(response.body.to_s).with_indifferent_access rescue nil
|
68
|
+
if result
|
69
|
+
return yield(:json, result)
|
70
|
+
else
|
71
|
+
return yield(:plain, response.body)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
case parse_as
|
76
|
+
when :json
|
77
|
+
result = ActiveSupport::JSON.decode(response.body.to_s).with_indifferent_access
|
78
|
+
when :file
|
79
|
+
if response.headers[:content_type] =~ %r{^image\/.*}
|
80
|
+
extension =
|
81
|
+
case response.headers['content-type']
|
82
|
+
when 'image/gif' then '.gif'
|
83
|
+
when 'image/jpeg' then '.jpg'
|
84
|
+
when 'image/png' then '.png'
|
85
|
+
end
|
86
|
+
else
|
87
|
+
extension = ''
|
88
|
+
end
|
89
|
+
|
90
|
+
begin
|
91
|
+
file = Tempfile.new(['mixin-file-', extension])
|
92
|
+
file.binmode
|
93
|
+
file.write(response.body)
|
94
|
+
ensure
|
95
|
+
file&.close
|
96
|
+
end
|
97
|
+
|
98
|
+
result = file
|
99
|
+
when :xml
|
100
|
+
result = Hash.from_xml(response.body.to_s)
|
101
|
+
else
|
102
|
+
result = response.body
|
103
|
+
end
|
104
|
+
|
105
|
+
yield(parse_as, result)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module MixinBot
|
2
|
+
module Errors
|
3
|
+
# 通用异常
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
|
6
|
+
# HTTP 异常,比如请求超时等
|
7
|
+
HttpError = Class.new(Error)
|
8
|
+
|
9
|
+
# API 异常,比如返回失败状态码
|
10
|
+
class APIError < Error
|
11
|
+
attr_reader :errcode, :errmsg
|
12
|
+
|
13
|
+
def initialize(errcode, errmsg)
|
14
|
+
@errcode = errcode
|
15
|
+
@errmsg = errmsg
|
16
|
+
|
17
|
+
super(format('[%s]: %s', @errcode, @errmsg))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mixin_bot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- an-lee
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: An API wrapper for Mixin Nexwork
|
14
|
+
email:
|
15
|
+
- an.lee.work@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- MIT-LICENSE
|
21
|
+
- lib/mixin_bot.rb
|
22
|
+
- lib/mixin_bot/api.rb
|
23
|
+
- lib/mixin_bot/api/auth.rb
|
24
|
+
- lib/mixin_bot/api/conversation.rb
|
25
|
+
- lib/mixin_bot/api/me.rb
|
26
|
+
- lib/mixin_bot/api/message.rb
|
27
|
+
- lib/mixin_bot/api/payment.rb
|
28
|
+
- lib/mixin_bot/api/pin.rb
|
29
|
+
- lib/mixin_bot/api/transfer.rb
|
30
|
+
- lib/mixin_bot/api/user.rb
|
31
|
+
- lib/mixin_bot/client.rb
|
32
|
+
- lib/mixin_bot/errors.rb
|
33
|
+
- lib/mixin_bot/version.rb
|
34
|
+
homepage: https://github.com/an-lee/mixin_bot
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 2.6.14
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: An API wrapper for Mixin Nexwork
|
58
|
+
test_files: []
|