class YolodiceClient

YolodiceClient is a simple JSON-RPC 2.0 client that connects to YOLOdice.com.

Attributes

connection[R]

OpenSSL::SSL::SSLSocket object created by the connect method.

notification_proc[RW]

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

btc_to_satoshi(v) click to toggle source

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
new(opts={}) click to toggle source

Initializes the client object. The method accepts an option hash with the following keys:

  • :host – defaults to api.yolodice.com

  • :port – defaults to 4444

  • :ssl – if SSL should be used, defaults to true

# 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
satoshi_to_btc(v) click to toggle source

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
target_from_multiplier(m) click to toggle source

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
target_from_probability(p) click to toggle source

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

authenticate(auth_key) click to toggle source

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
call(method, *arguments, &blk) click to toggle source

Calls an arbitrary method on the server.

Parameters:

  • method – method name,

  • *arguments – any arguments for the method, will be passed as the params 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
check_connection() click to toggle source
# File lib/yolodice_client.rb, line 232
def check_connection
  raise ConnectionClosedError if @connection.closed?
end
close() click to toggle source

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
connect() click to toggle source

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
logger=(logger) click to toggle source

Sets a logger for the object.

# File lib/yolodice_client.rb, line 46
def logger=(logger)
  @log = logger
end
method_missing(method, *args, &blk) click to toggle source

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

log() click to toggle source
# File lib/yolodice_client.rb, line 244
def log
  # no logging by default
  @log ||= Logger.new File::NULL
end