Class: YolodiceClient

Inherits:
Object
  • Object
show all
Defined in:
lib/yolodice_client.rb

Overview

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

Defined Under Namespace

Classes: Error, RemoteError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ YolodiceClient

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



29
30
31
32
33
34
35
36
37
38
39
40
# 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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &blk) ⇒ Object

Overloading the method_missing gives a convenience way to call server-side methods. This method calls the call with the same set of arguments.



206
207
208
# File 'lib/yolodice_client.rb', line 206

def method_missing method, *args, &blk
  call method, *args, &blk
end

Instance Attribute Details

#connectionObject (readonly)

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



14
15
16
# File 'lib/yolodice_client.rb', line 14

def connection
  @connection
end

#notification_procObject

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.



21
22
23
# File 'lib/yolodice_client.rb', line 21

def notification_proc
  @notification_proc
end

Class Method Details

.btc_to_satoshi(v) ⇒ Object

Converts amount from bitcoins (float) to satoshi (integer).



271
272
273
# File 'lib/yolodice_client.rb', line 271

def btc_to_satoshi v
  (v * 100_000_000).round
end

.satoshi_to_btc(v) ⇒ Object

Converts amount from satoshi (integer) to amount in bitcoins (float).



266
267
268
# File 'lib/yolodice_client.rb', line 266

def satoshi_to_btc v
  (v.to_f / 100_000_000).round(8)
end

.target_from_multiplier(m) ⇒ Object

Returns bet terget given the required multiplier.



251
252
253
254
# File 'lib/yolodice_client.rb', line 251

def target_from_multiplier m
  edge = 0.01
  (1_000_000.0 * (1.0 - edge) / m).round
end

.target_from_probability(p) ⇒ Object

Returns bet target given the required win probability.



259
260
261
# File 'lib/yolodice_client.rb', line 259

def target_from_probability p
  (p * 1_000_000.0).round
end

Instance Method Details

#authenticate(auth_key) ⇒ Object

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.

Raises:



146
147
148
149
150
151
152
153
# File 'lib/yolodice_client.rb', line 146

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) ⇒ Object

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.

Raises:



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/yolodice_client.rb', line 164

def call method, *arguments, &blk
  raise Error, "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']
    end
  end
end

#closeObject

Closes connection to the host.



126
127
128
129
130
131
132
133
# File 'lib/yolodice_client.rb', line 126

def close
  log.debug "Closing connection"
  # Stop threads
  @connection.close
  @listening_thread.exit
  @pinging_thread.exit
  true
end

#connectObject

Connects to the host.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/yolodice_client.rb', line 54

def connect
  @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
        msg = @connection.gets
        next if msg == nil
        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
      rescue StandardError => e
        log.error e
      end
    end
  end
  true
end

#logger=(logger) ⇒ Object

Sets a logger for the object.



46
47
48
# File 'lib/yolodice_client.rb', line 46

def logger=(logger)
  @log = logger
end