mixin_bot 1.4.0 → 2.0.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/AGENTS.md +75 -0
- data/API_COVERAGE.md +143 -0
- data/CHANGELOG.md +112 -0
- data/README.md +375 -0
- data/docs/agent/cli.md +149 -0
- data/docs/agent/cookbook.md +152 -0
- data/examples/blaze.rb +43 -0
- data/examples/config.yml.example +21 -0
- data/lib/mixin_bot/address.rb +43 -3
- data/lib/mixin_bot/api/app.rb +7 -0
- data/lib/mixin_bot/api/asset.rb +114 -3
- data/lib/mixin_bot/api/auth.rb +19 -10
- data/lib/mixin_bot/api/blaze.rb +81 -0
- data/lib/mixin_bot/api/chain.rb +94 -0
- data/lib/mixin_bot/api/code.rb +16 -0
- data/lib/mixin_bot/api/computer_api.rb +60 -0
- data/lib/mixin_bot/api/conversation.rb +7 -1
- data/lib/mixin_bot/api/deposit.rb +12 -0
- data/lib/mixin_bot/api/encrypted_message.rb +1 -1
- data/lib/mixin_bot/api/fiat.rb +12 -0
- data/lib/mixin_bot/api/inscription.rb +2 -2
- data/lib/mixin_bot/api/legacy_collectible.rb +26 -27
- data/lib/mixin_bot/api/legacy_multisig.rb +20 -21
- data/lib/mixin_bot/api/legacy_output.rb +10 -3
- data/lib/mixin_bot/api/legacy_payment.rb +2 -0
- data/lib/mixin_bot/api/legacy_snapshot.rb +16 -0
- data/lib/mixin_bot/api/legacy_transaction.rb +28 -13
- data/lib/mixin_bot/api/legacy_transfer.rb +11 -8
- data/lib/mixin_bot/api/legacy_user.rb +51 -0
- data/lib/mixin_bot/api/me.rb +99 -3
- data/lib/mixin_bot/api/message.rb +18 -27
- data/lib/mixin_bot/api/multisig.rb +19 -0
- data/lib/mixin_bot/api/network.rb +17 -0
- data/lib/mixin_bot/api/network_asset.rb +27 -0
- data/lib/mixin_bot/api/output.rb +1 -1
- data/lib/mixin_bot/api/pin.rb +16 -3
- data/lib/mixin_bot/api/pin_payload.rb +26 -0
- data/lib/mixin_bot/api/session.rb +14 -0
- data/lib/mixin_bot/api/snapshot.rb +6 -0
- data/lib/mixin_bot/api/tip.rb +74 -1
- data/lib/mixin_bot/api/transaction.rb +106 -17
- data/lib/mixin_bot/api/transfer.rb +141 -14
- data/lib/mixin_bot/api/turn.rb +12 -0
- data/lib/mixin_bot/api/user.rb +148 -45
- data/lib/mixin_bot/api/withdraw.rb +24 -23
- data/lib/mixin_bot/api.rb +248 -3
- data/lib/mixin_bot/bot_auth.rb +71 -0
- data/lib/mixin_bot/cli/api.rb +224 -143
- data/lib/mixin_bot/cli/base.rb +77 -0
- data/lib/mixin_bot/cli/call.rb +71 -0
- data/lib/mixin_bot/cli/errors.rb +56 -0
- data/lib/mixin_bot/cli/node.rb +9 -2
- data/lib/mixin_bot/cli/output.rb +196 -0
- data/lib/mixin_bot/cli/schema.rb +274 -0
- data/lib/mixin_bot/cli/schema_command.rb +21 -0
- data/lib/mixin_bot/cli/utils.rb +114 -18
- data/lib/mixin_bot/cli.rb +124 -48
- data/lib/mixin_bot/client/error_mapper.rb +40 -0
- data/lib/mixin_bot/client.rb +94 -64
- data/lib/mixin_bot/computer.rb +132 -0
- data/lib/mixin_bot/configuration.rb +108 -1
- data/lib/mixin_bot/errors.rb +102 -0
- data/lib/mixin_bot/models/address.rb +11 -0
- data/lib/mixin_bot/models/api_envelope.rb +67 -0
- data/lib/mixin_bot/models/asset.rb +11 -0
- data/lib/mixin_bot/models/ghost_keys.rb +14 -0
- data/lib/mixin_bot/models/output.rb +11 -0
- data/lib/mixin_bot/models/safe_multisig_request.rb +11 -0
- data/lib/mixin_bot/models/sequencer_transaction_request.rb +11 -0
- data/lib/mixin_bot/models/user.rb +11 -0
- data/lib/mixin_bot/models.rb +10 -0
- data/lib/mixin_bot/monitor.rb +77 -0
- data/lib/mixin_bot/transaction/buffer.rb +34 -0
- data/lib/mixin_bot/transaction/decoder.rb +227 -0
- data/lib/mixin_bot/transaction/encoder.rb +255 -0
- data/lib/mixin_bot/transaction.rb +6 -475
- data/lib/mixin_bot/url_scheme.rb +63 -0
- data/lib/mixin_bot/utils/address.rb +17 -80
- data/lib/mixin_bot/utils/crypto.rb +173 -1
- data/lib/mixin_bot/utils/decoder.rb +1 -1
- data/lib/mixin_bot/utils/encoder.rb +13 -0
- data/lib/mixin_bot/utils.rb +45 -0
- data/lib/mixin_bot/uuid.rb +78 -1
- data/lib/mixin_bot/version.rb +11 -1
- data/lib/mixin_bot.rb +172 -18
- data/lib/mvm/bridge.rb +46 -0
- data/lib/mvm/client.rb +60 -0
- data/lib/mvm/nft.rb +4 -2
- data/lib/mvm/registry.rb +2 -1
- data/lib/mvm.rb +93 -0
- data/lib/tasks/api_coverage.rake +20 -0
- data/llms.txt +29 -0
- metadata +77 -9
data/lib/mixin_bot/api.rb
CHANGED
|
@@ -8,10 +8,16 @@ require_relative 'api/asset'
|
|
|
8
8
|
require_relative 'api/attachment'
|
|
9
9
|
require_relative 'api/auth'
|
|
10
10
|
require_relative 'api/blaze'
|
|
11
|
+
require_relative 'api/chain'
|
|
12
|
+
require_relative 'api/code'
|
|
13
|
+
require_relative 'api/computer_api'
|
|
11
14
|
require_relative 'api/conversation'
|
|
15
|
+
require_relative 'api/deposit'
|
|
12
16
|
require_relative 'api/encrypted_message'
|
|
17
|
+
require_relative 'api/fiat'
|
|
13
18
|
require_relative 'api/inscription'
|
|
14
19
|
require_relative 'api/legacy_collectible'
|
|
20
|
+
require_relative 'api/legacy_user'
|
|
15
21
|
require_relative 'api/legacy_multisig'
|
|
16
22
|
require_relative 'api/legacy_output'
|
|
17
23
|
require_relative 'api/legacy_payment'
|
|
@@ -21,21 +27,155 @@ require_relative 'api/legacy_transfer'
|
|
|
21
27
|
require_relative 'api/me'
|
|
22
28
|
require_relative 'api/message'
|
|
23
29
|
require_relative 'api/multisig'
|
|
30
|
+
require_relative 'api/network'
|
|
31
|
+
require_relative 'api/network_asset'
|
|
24
32
|
require_relative 'api/output'
|
|
25
33
|
require_relative 'api/payment'
|
|
34
|
+
require_relative 'api/pin_payload'
|
|
26
35
|
require_relative 'api/pin'
|
|
27
36
|
require_relative 'api/rpc'
|
|
37
|
+
require_relative 'api/session'
|
|
28
38
|
require_relative 'api/snapshot'
|
|
29
39
|
require_relative 'api/tip'
|
|
30
40
|
require_relative 'api/transaction'
|
|
31
41
|
require_relative 'api/transfer'
|
|
42
|
+
require_relative 'api/turn'
|
|
32
43
|
require_relative 'api/user'
|
|
33
44
|
require_relative 'api/withdraw'
|
|
34
45
|
|
|
35
46
|
module MixinBot
|
|
47
|
+
##
|
|
48
|
+
# Main API interface for interacting with Mixin Network.
|
|
49
|
+
#
|
|
50
|
+
# The API class provides access to all Mixin Network endpoints including:
|
|
51
|
+
# - User and bot profile management
|
|
52
|
+
# - Asset management (read assets, check balances)
|
|
53
|
+
# - Transfers and payments (Safe API and legacy)
|
|
54
|
+
# - Messaging (send/receive messages via Blaze)
|
|
55
|
+
# - Conversations and encrypted messages
|
|
56
|
+
# - Multisig operations
|
|
57
|
+
# - NFT and collectible operations
|
|
58
|
+
# - Transaction building and signing
|
|
59
|
+
# - Withdrawal operations
|
|
60
|
+
#
|
|
61
|
+
# == Usage
|
|
62
|
+
#
|
|
63
|
+
# === Using Global Configuration
|
|
64
|
+
#
|
|
65
|
+
# MixinBot.configure do
|
|
66
|
+
# self.app_id = 'your-app-id'
|
|
67
|
+
# self.session_id = 'your-session-id'
|
|
68
|
+
# self.session_private_key = 'your-private-key'
|
|
69
|
+
# self.server_public_key = 'server-public-key'
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
# # Access via global instance
|
|
73
|
+
# MixinBot.api.me
|
|
74
|
+
# MixinBot.api.assets
|
|
75
|
+
#
|
|
76
|
+
# === Creating Dedicated Instances
|
|
77
|
+
#
|
|
78
|
+
# api = MixinBot::API.new(
|
|
79
|
+
# app_id: 'your-app-id',
|
|
80
|
+
# session_id: 'your-session-id',
|
|
81
|
+
# session_private_key: 'your-private-key',
|
|
82
|
+
# server_public_key: 'server-public-key'
|
|
83
|
+
# )
|
|
84
|
+
#
|
|
85
|
+
# api.me
|
|
86
|
+
# api.assets
|
|
87
|
+
#
|
|
88
|
+
# == API Categories
|
|
89
|
+
#
|
|
90
|
+
# === Profile & Users
|
|
91
|
+
# - me, safe_me, update_me - Bot profile operations
|
|
92
|
+
# - read_user, read_users, search_user - User lookup
|
|
93
|
+
# - friends - List bot friends
|
|
94
|
+
#
|
|
95
|
+
# === Assets & Balance
|
|
96
|
+
# - assets, asset - Read asset information
|
|
97
|
+
# - ticker - Get asset ticker data
|
|
98
|
+
# - safe_assets - Read Safe API assets
|
|
99
|
+
#
|
|
100
|
+
# === Transfers & Payments
|
|
101
|
+
# - create_transfer, create_safe_transfer - Send payments
|
|
102
|
+
# - build_safe_transaction - Build raw transactions
|
|
103
|
+
# - sign_safe_transaction - Sign transactions
|
|
104
|
+
# - send_safe_transaction - Submit signed transactions
|
|
105
|
+
#
|
|
106
|
+
# === Messaging
|
|
107
|
+
# - start_blaze_connect - Connect to Blaze WebSocket
|
|
108
|
+
# - send_message - Send text/data messages
|
|
109
|
+
# - send_encrypted_messages - Send encrypted messages
|
|
110
|
+
#
|
|
111
|
+
# === Multisig & UTXOs
|
|
112
|
+
# - safe_outputs - Read unspent outputs
|
|
113
|
+
# - multisig_payments - Multisig payment operations
|
|
114
|
+
# - safe_ghost_keys - Generate ghost keys
|
|
115
|
+
#
|
|
116
|
+
# === NFT & Collectibles
|
|
117
|
+
# - create_collectible_request - Create NFT requests
|
|
118
|
+
# - read_collectibles - Read collectible tokens
|
|
119
|
+
# - inscriptions - Inscription operations
|
|
120
|
+
#
|
|
121
|
+
# == Examples
|
|
122
|
+
#
|
|
123
|
+
# Get bot information:
|
|
124
|
+
#
|
|
125
|
+
# profile = MixinBot.api.me
|
|
126
|
+
# puts profile['full_name']
|
|
127
|
+
#
|
|
128
|
+
# Read assets:
|
|
129
|
+
#
|
|
130
|
+
# assets = MixinBot.api.assets
|
|
131
|
+
# assets.each do |asset|
|
|
132
|
+
# puts "#{asset['symbol']}: #{asset['balance']}"
|
|
133
|
+
# end
|
|
134
|
+
#
|
|
135
|
+
# Send a transfer:
|
|
136
|
+
#
|
|
137
|
+
# result = MixinBot.api.create_transfer(
|
|
138
|
+
# members: ['recipient-user-id'],
|
|
139
|
+
# threshold: 1,
|
|
140
|
+
# asset_id: 'asset-uuid',
|
|
141
|
+
# amount: '0.01',
|
|
142
|
+
# memo: 'Payment for services',
|
|
143
|
+
# trace_id: SecureRandom.uuid
|
|
144
|
+
# )
|
|
145
|
+
#
|
|
36
146
|
class API
|
|
37
|
-
|
|
147
|
+
##
|
|
148
|
+
# @return [MixinBot::Configuration] the configuration for this API instance
|
|
149
|
+
attr_reader :config
|
|
38
150
|
|
|
151
|
+
##
|
|
152
|
+
# @return [MixinBot::Client] the HTTP client for making API requests
|
|
153
|
+
attr_reader :client
|
|
154
|
+
|
|
155
|
+
##
|
|
156
|
+
# Initializes a new API instance.
|
|
157
|
+
#
|
|
158
|
+
# If no parameters are provided, uses the global MixinBot configuration.
|
|
159
|
+
# Otherwise, creates a new configuration with the provided parameters.
|
|
160
|
+
#
|
|
161
|
+
# @param kwargs [Hash] configuration options (see Configuration#initialize)
|
|
162
|
+
# @option kwargs [String] :app_id the application ID
|
|
163
|
+
# @option kwargs [String] :session_id the session ID
|
|
164
|
+
# @option kwargs [String] :session_private_key the session private key
|
|
165
|
+
# @option kwargs [String] :server_public_key the server public key
|
|
166
|
+
# @option kwargs [String] :spend_key the spend private key (for Safe API)
|
|
167
|
+
#
|
|
168
|
+
# @example Using global configuration
|
|
169
|
+
# api = MixinBot::API.new
|
|
170
|
+
#
|
|
171
|
+
# @example With custom configuration
|
|
172
|
+
# api = MixinBot::API.new(
|
|
173
|
+
# app_id: 'your-app-id',
|
|
174
|
+
# session_id: 'your-session-id',
|
|
175
|
+
# session_private_key: 'your-private-key',
|
|
176
|
+
# server_public_key: 'server-public-key'
|
|
177
|
+
# )
|
|
178
|
+
#
|
|
39
179
|
def initialize(**kwargs)
|
|
40
180
|
@config =
|
|
41
181
|
if kwargs.present?
|
|
@@ -47,14 +187,42 @@ module MixinBot
|
|
|
47
187
|
@client = Client.new(@config)
|
|
48
188
|
end
|
|
49
189
|
|
|
190
|
+
##
|
|
191
|
+
# Provides access to utility methods.
|
|
192
|
+
#
|
|
193
|
+
# @return [Module] the Utils module
|
|
194
|
+
#
|
|
50
195
|
def utils
|
|
51
196
|
MixinBot::Utils
|
|
52
197
|
end
|
|
53
198
|
|
|
199
|
+
##
|
|
200
|
+
# Returns the client ID (same as app_id).
|
|
201
|
+
#
|
|
202
|
+
# @return [String] the client ID
|
|
203
|
+
#
|
|
54
204
|
def client_id
|
|
55
205
|
config.app_id
|
|
56
206
|
end
|
|
57
207
|
|
|
208
|
+
##
|
|
209
|
+
# Generates an access token for API authentication.
|
|
210
|
+
#
|
|
211
|
+
# Creates a JWT token signed with the bot's private key for authenticating
|
|
212
|
+
# API requests. The token includes request details and has a limited lifetime.
|
|
213
|
+
#
|
|
214
|
+
# @param method [String] the HTTP method (GET, POST, etc.)
|
|
215
|
+
# @param uri [String] the request URI path
|
|
216
|
+
# @param body [String] the request body
|
|
217
|
+
# @param kwargs [Hash] additional options
|
|
218
|
+
# @option kwargs [Integer] :exp_in (600) token expiration time in seconds
|
|
219
|
+
# @option kwargs [String] :scp ('FULL') token scope
|
|
220
|
+
#
|
|
221
|
+
# @return [String] the JWT access token
|
|
222
|
+
#
|
|
223
|
+
# @example
|
|
224
|
+
# token = api.access_token('GET', '/me', '')
|
|
225
|
+
#
|
|
58
226
|
def access_token(method, uri, body, **kwargs)
|
|
59
227
|
utils.access_token(
|
|
60
228
|
method,
|
|
@@ -67,20 +235,71 @@ module MixinBot
|
|
|
67
235
|
private_key: config.session_private_key
|
|
68
236
|
)
|
|
69
237
|
end
|
|
238
|
+
alias sign_authentication_token access_token
|
|
239
|
+
alias sign_authentication_token_without_body access_token
|
|
240
|
+
alias sign_authentication_token_with_request_id access_token
|
|
70
241
|
|
|
242
|
+
##
|
|
243
|
+
# Encodes a transaction hash to raw transaction format.
|
|
244
|
+
#
|
|
245
|
+
# @param txn [Hash] the transaction hash with keys: version, asset, inputs, outputs, extra
|
|
246
|
+
# @return [String] the hex-encoded raw transaction
|
|
247
|
+
#
|
|
248
|
+
# @example
|
|
249
|
+
# raw = api.encode_raw_transaction(
|
|
250
|
+
# version: 5,
|
|
251
|
+
# asset: 'asset-id',
|
|
252
|
+
# inputs: [...],
|
|
253
|
+
# outputs: [...],
|
|
254
|
+
# extra: 'memo'
|
|
255
|
+
# )
|
|
256
|
+
#
|
|
71
257
|
def encode_raw_transaction(txn)
|
|
72
258
|
utils.encode_raw_transaction txn
|
|
73
259
|
end
|
|
74
260
|
|
|
261
|
+
##
|
|
262
|
+
# Decodes a raw transaction to a hash.
|
|
263
|
+
#
|
|
264
|
+
# @param raw [String] the hex-encoded raw transaction
|
|
265
|
+
# @return [Hash] the decoded transaction
|
|
266
|
+
#
|
|
267
|
+
# @example
|
|
268
|
+
# txn = api.decode_raw_transaction(raw_hex)
|
|
269
|
+
# puts txn['asset']
|
|
270
|
+
# puts txn['inputs']
|
|
271
|
+
#
|
|
75
272
|
def decode_raw_transaction(raw)
|
|
76
273
|
utils.decode_raw_transaction raw
|
|
77
274
|
end
|
|
78
275
|
|
|
276
|
+
##
|
|
277
|
+
# Generates a trace ID from a transaction hash.
|
|
278
|
+
#
|
|
279
|
+
# Creates a deterministic UUID trace ID from a transaction hash,
|
|
280
|
+
# useful for tracking outputs from a transaction.
|
|
281
|
+
#
|
|
282
|
+
# @param hash [String] the transaction hash
|
|
283
|
+
# @param output_index [Integer] the output index (default: 0)
|
|
284
|
+
# @return [String] the generated trace UUID
|
|
285
|
+
#
|
|
286
|
+
# @example
|
|
287
|
+
# trace_id = api.generate_trace_from_hash(tx_hash, 0)
|
|
288
|
+
#
|
|
79
289
|
def generate_trace_from_hash(hash, output_index = 0)
|
|
80
290
|
utils.generate_trace_from_hash hash, output_index
|
|
81
291
|
end
|
|
82
292
|
|
|
83
|
-
|
|
293
|
+
##
|
|
294
|
+
# Encodes a raw transaction using native mixin command-line tool.
|
|
295
|
+
#
|
|
296
|
+
# Requires the 'mixin' command to be installed and available in PATH.
|
|
297
|
+
# This is an alternative to the Ruby implementation.
|
|
298
|
+
#
|
|
299
|
+
# @param json [String] the transaction JSON
|
|
300
|
+
# @return [String] the encoded raw transaction
|
|
301
|
+
# @raise [RuntimeError] if mixin command is not available
|
|
302
|
+
#
|
|
84
303
|
def encode_raw_transaction_native(json)
|
|
85
304
|
ensure_mixin_command_exist
|
|
86
305
|
command = format("mixin signrawtransaction --raw '%<arg>s'", arg: json)
|
|
@@ -91,7 +310,16 @@ module MixinBot
|
|
|
91
310
|
output.chomp
|
|
92
311
|
end
|
|
93
312
|
|
|
94
|
-
|
|
313
|
+
##
|
|
314
|
+
# Decodes a raw transaction using native mixin command-line tool.
|
|
315
|
+
#
|
|
316
|
+
# Requires the 'mixin' command to be installed and available in PATH.
|
|
317
|
+
# This is an alternative to the Ruby implementation.
|
|
318
|
+
#
|
|
319
|
+
# @param raw [String] the hex-encoded raw transaction
|
|
320
|
+
# @return [Hash] the decoded transaction
|
|
321
|
+
# @raise [RuntimeError] if mixin command is not available
|
|
322
|
+
#
|
|
95
323
|
def decode_raw_transaction_native(raw)
|
|
96
324
|
ensure_mixin_command_exist
|
|
97
325
|
command = format("mixin decoderawtransaction --raw '%<arg>s'", arg: raw)
|
|
@@ -108,10 +336,16 @@ module MixinBot
|
|
|
108
336
|
include MixinBot::API::Attachment
|
|
109
337
|
include MixinBot::API::Auth
|
|
110
338
|
include MixinBot::API::Blaze
|
|
339
|
+
include MixinBot::API::Chain
|
|
340
|
+
include MixinBot::API::Code
|
|
341
|
+
include MixinBot::API::ComputerApi
|
|
111
342
|
include MixinBot::API::Conversation
|
|
343
|
+
include MixinBot::API::Deposit
|
|
112
344
|
include MixinBot::API::EncryptedMessage
|
|
345
|
+
include MixinBot::API::Fiat
|
|
113
346
|
include MixinBot::API::Inscription
|
|
114
347
|
include MixinBot::API::LegacyCollectible
|
|
348
|
+
include MixinBot::API::LegacyUser
|
|
115
349
|
include MixinBot::API::LegacyMultisig
|
|
116
350
|
include MixinBot::API::LegacyOutput
|
|
117
351
|
include MixinBot::API::LegacyPayment
|
|
@@ -121,19 +355,30 @@ module MixinBot
|
|
|
121
355
|
include MixinBot::API::Me
|
|
122
356
|
include MixinBot::API::Message
|
|
123
357
|
include MixinBot::API::Multisig
|
|
358
|
+
include MixinBot::API::Network
|
|
359
|
+
include MixinBot::API::NetworkAsset
|
|
124
360
|
include MixinBot::API::Output
|
|
125
361
|
include MixinBot::API::Payment
|
|
126
362
|
include MixinBot::API::Pin
|
|
127
363
|
include MixinBot::API::Rpc
|
|
364
|
+
include MixinBot::API::Session
|
|
128
365
|
include MixinBot::API::Snapshot
|
|
129
366
|
include MixinBot::API::Tip
|
|
367
|
+
include MixinBot::API::PinPayload
|
|
130
368
|
include MixinBot::API::Transaction
|
|
131
369
|
include MixinBot::API::Transfer
|
|
370
|
+
include MixinBot::API::Turn
|
|
132
371
|
include MixinBot::API::User
|
|
133
372
|
include MixinBot::API::Withdraw
|
|
134
373
|
|
|
135
374
|
private
|
|
136
375
|
|
|
376
|
+
def warn_legacy_mixin_api!(api_label)
|
|
377
|
+
MixinBot.deprecator.warn(
|
|
378
|
+
"MixinBot legacy API #{api_label} is deprecated; migrate to the Safe API. See CHANGELOG for 2.0.0."
|
|
379
|
+
)
|
|
380
|
+
end
|
|
381
|
+
|
|
137
382
|
def ensure_mixin_command_exist
|
|
138
383
|
return if command?('mixin')
|
|
139
384
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MixinBot
|
|
4
|
+
# Bot platform request signing (parity with Go BotAuthClient).
|
|
5
|
+
class BotAuth
|
|
6
|
+
class MapCache
|
|
7
|
+
def initialize
|
|
8
|
+
@store = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get(key)
|
|
12
|
+
@store[key]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def put(key, value)
|
|
16
|
+
@store[key] = value
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def delete(key)
|
|
20
|
+
@store.delete(key)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Client
|
|
25
|
+
PLATFORM_PREFIX = 'up_'
|
|
26
|
+
|
|
27
|
+
def initialize(api, cache: MapCache.new)
|
|
28
|
+
@api = api
|
|
29
|
+
@cache = cache
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def sign_request(timestamp, bot_user_id, method, uri, body = nil)
|
|
33
|
+
shared_key = shared_key_for(bot_user_id)
|
|
34
|
+
data = "#{timestamp}#{method}#{uri}"
|
|
35
|
+
data += body.to_s if body.present?
|
|
36
|
+
digest = OpenSSL::HMAC.digest('SHA256', shared_key, data)
|
|
37
|
+
Base64.urlsafe_encode64(@api.config.app_id.b + digest, padding: false)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def shared_key_for(user_id)
|
|
43
|
+
cached = @cache.get(user_id)
|
|
44
|
+
return cached if cached.present? && cached.bytesize >= 32
|
|
45
|
+
|
|
46
|
+
sessions = @api.fetch_user_sessions([user_id])['data']
|
|
47
|
+
session = Array(sessions).first
|
|
48
|
+
raise MixinBot::NotFoundError, "no session for #{user_id}" if session.nil?
|
|
49
|
+
|
|
50
|
+
u_pk = Base64.urlsafe_decode64(session['public_key'])
|
|
51
|
+
sk = @api.config.session_private_key_curve25519
|
|
52
|
+
shared = JOSE::JWA::X25519.x25519(sk, u_pk[0, 32])
|
|
53
|
+
@cache.put(user_id, shared)
|
|
54
|
+
@cache.put("#{PLATFORM_PREFIX}#{user_id}", session['platform'].to_s.b) if session['platform']
|
|
55
|
+
shared
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.new_map_cache
|
|
60
|
+
MapCache.new
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.new_client(api, cache: MapCache.new)
|
|
64
|
+
Client.new(api, cache:)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.new_default_client(api, cache: MapCache.new)
|
|
68
|
+
new_client(api, cache:)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|