class YolodiceClient
YolodiceClient
is a simple JSON-RPC 2.0 client that connects to YOLOdice.com.
Attributes
OpenSSL::SSL::SSLSocket
object created by the connect
method.
Proc for handling notifications from the server. This proc (if set) will be called each time the server sends a notification, i.e. a message that is not a response to any client call. The proc is given a single argument: the message hash. The proc is called in a separate thread.
Public Class Methods
Converts amount from bitcoins (float) to satoshi (integer).
# File lib/yolodice_client.rb, line 310 def btc_to_satoshi v (v * 100_000_000).round end
Initializes the client object. The method accepts an option hash with the following keys:
-
:host
– defaults toapi.yolodice.com
-
:port
– defaults to4444
-
:ssl
– if SSL should be used, defaults totrue
# File lib/yolodice_client.rb, line 29 def initialize opts={} @opts = { host: 'api.yolodice.com', port: 4444, ssl: true }.merge opts @req_id_seq = 0 @current_requests = {} @receive_queues = {} @thread_semaphore = Mutex.new end
Converts amount from satoshi (integer) to amount in bitcoins (float).
# File lib/yolodice_client.rb, line 305 def satoshi_to_btc v (v.to_f / 100_000_000).round(8) end
Returns bet terget given the required multiplier.
# File lib/yolodice_client.rb, line 290 def target_from_multiplier m edge = 0.01 (1_000_000.0 * (1.0 - edge) / m).round end
Returns bet target given the required win probability.
# File lib/yolodice_client.rb, line 298 def target_from_probability p (p * 1_000_000.0).round end
Public Instance Methods
Authenticates the connection by requesting a challenge message, signing it and sending the response back.
Parameters:
-
auth_key
– Base58 encoded private key for the API key
Returns
-
false
if authentication fails, -
user object (Hash with public user attributes) when authentication succeeds.
# File lib/yolodice_client.rb, line 173 def authenticate auth_key auth_key = Bitcoin::Key.from_base58(auth_key) unless auth_key.is_a?(Bitcoin::Key) challenge = generate_auth_challenge user = auth_by_address address: auth_key.addr, signature: auth_key.sign_message(challenge) raise Error, "Authentication failed" unless user log.debug "Authenticated as user #{user['name']}(#{user['id']})" user end
Calls an arbitrary method on the server.
Parameters:
-
method
– method name, -
*arguments
– any arguments for the method, will be passed as theparams
object (optional), -
&blk
– a callback (optional) to be called upon receiving a response for async calls. The callback will receive the response object.
# File lib/yolodice_client.rb, line 191 def call method, *arguments, &blk raise ConnectionClosedError, "Not connected" unless @connection && !@connection.closed? params = if arguments.count == 0 nil elsif arguments.is_a?(Array) && arguments[0].is_a?(Hash) arguments[0] else arguments end id = @thread_semaphore.synchronize{ @req_id_seq += 1 } request = { id: id, method: method } request[:params] = params if params != nil if blk @thread_semaphore.synchronize{ @current_requests[id] = blk } log.debug{ "Calling remote method #{method}(#{params.inspect if params != nil}) with an async callback" } log.debug{ ">>> #{request.to_json}" } @connection.puts request.to_json nil else # a regular blocking request @thread_semaphore.synchronize{ @current_requests[id] = Thread.current.object_id } queue = (@receive_queues[Thread.current.object_id] ||= Queue.new) queue.clear log.debug{ "Calling remote method #{method}(#{params.inspect if params != nil})" } log.debug{ ">>> #{request.to_json}" } @connection.puts request.to_json response = queue.pop if response.has_key? 'result' response['result'] elsif response['error'] raise RemoteError.new response['error'] elsif response['client_error'] && response['client_error'] == 'Connection closed' raise ConnectionClosedError end end end
# File lib/yolodice_client.rb, line 232 def check_connection raise ConnectionClosedError if @connection.closed? end
Closes connection to the host.
# File lib/yolodice_client.rb, line 138 def close log.debug "Closing connection" # Stop threads @connection.close # Send error to all pending requests message = { 'client_error' => 'Connection closed' } while(callback = @thread_semaphore.synchronize{ @current_requests.shift }) do callback = callback[1] if callback.is_a? Integer # it's a thread @receive_queues[callback] << message elsif callback.is_a? Proc # A thread pool would be better. Thread.new do callback.call message end end end # Thread.stop @pinging_thread.kill @listening_thread.kill true end
Connects to the host.
# File lib/yolodice_client.rb, line 54 def connect if @connection && !@connection.closed? raise Error, 'Connection already open' end @connection = if @opts[:ssl] log.debug "Connecting to #{@opts[:host]}:#{@opts[:port]} over SSL" socket = TCPSocket.open @opts[:host], @opts[:port] ssl_socket = OpenSSL::SSL::SSLSocket.new socket ssl_socket.sync_close = true ssl_socket.connect ssl_socket else log.debug "Connecting to #{@opts[:host]}:#{@opts[:port]}" TCPSocket.open @opts[:host], @opts[:port] end log.info "Connected to #{@opts[:host]}:#{@opts[:port]}" # Start a thread that keeps listening on the socket @listening_thread = Thread.new do log.debug 'Listening thread started' loop do begin if @connection.closed? # keep the thread alive for a while sleep 1 end msg = @connection.gets if msg == nil # Connection is closed upstream. close end log.debug{ "<<< #{msg}" } message = JSON.parse msg if message['id'] && (message.has_key?('result') || message.has_key?('error')) # definitealy a response callback = @thread_semaphore.synchronize{ @current_requests.delete message['id'] } raise Error, "Unknown id in response" unless callback if callback.is_a? Integer # it's a thread @receive_queues[callback] << message elsif callback.is_a? Proc # A thread pool would be better. Thread.new do callback.call message end end else if message['id'] # It must be a request from the server. We do not support it yet. else # No id, it must be a notification then. if notification_proc Thread.new do notification_proc.call message end end end end rescue StandardError => e log.error e end end end # Start a thread that pings the server @pinging_thread = Thread.new do log.debug 'Pinging thread started' loop do begin sleep 30 call :ping unless @connection.closed? rescue StandardError => e log.error e end end end true end
Sets a logger for the object.
# File lib/yolodice_client.rb, line 46 def logger=(logger) @log = logger end
Overloading the method_missing
gives a convenience way to call server-side methods. This method calls the call
with the same set of arguments.
# File lib/yolodice_client.rb, line 240 def method_missing method, *args, &blk call method, *args, &blk end
Private Instance Methods
# File lib/yolodice_client.rb, line 244 def log # no logging by default @log ||= Logger.new File::NULL end