Class: Whatsapp::Client

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

Overview

Main interface for interacting with a WhatsApp account.

Manages the bridge lifecycle, sends messages, and dispatches events. Each Client instance owns one bridge process and one WhatsApp session.

Examples:

client = Whatsapp::Client.new
client.connect
client.on_qr { |qr| puts "Scan this: #{qr}" }
client.on_message { |msg| puts "#{msg.from}: #{msg.text}" }
client.send_message(to: "+33612345678", text: "Hello!")

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config: Whatsapp.configuration) ⇒ Client

Returns a new instance of Client.

Parameters:

  • config (Configuration) (defaults to: Whatsapp.configuration)

    gem configuration



25
26
27
28
29
30
31
32
# File 'lib/whatsapp/client.rb', line 25

def initialize(config: Whatsapp.configuration)
  @config = config
  @connection = Connection.new(config)
  @bridge = nil
  @events = Events.new
  @rate_limiter = RateLimiter.new(config)
  @connected = false
end

Instance Attribute Details

#bridgeBridge? (readonly)

Returns the bridge communication layer.

Returns:

  • (Bridge, nil)

    the bridge communication layer



22
23
24
# File 'lib/whatsapp/client.rb', line 22

def bridge
  @bridge
end

#eventsEvents (readonly)

Returns event emitter for this client.

Returns:

  • (Events)

    event emitter for this client



16
17
18
# File 'lib/whatsapp/client.rb', line 16

def events
  @events
end

#rate_limiterRateLimiter (readonly)

Returns rate limiter instance.

Returns:



19
20
21
# File 'lib/whatsapp/client.rb', line 19

def rate_limiter
  @rate_limiter
end

Instance Method Details

#add_group_participants(group_id, participants:) ⇒ Hash

Add participants to a group.

Parameters:

  • group_id (String)

    group JID

  • participants (Array<String>)

    phone numbers or JIDs

Returns:

  • (Hash)

    bridge response

Raises:



366
367
368
369
# File 'lib/whatsapp/client.rb', line 366

def add_group_participants(group_id, participants:)
  ensure_bridge!
  @bridge.post("/groups/#{group_id}/participants", {action: "add", participants: participants})
end

#business_profile(phone) ⇒ Hash

Get a contact's business profile.

Parameters:

  • phone (String)

    phone number or JID

Returns:

  • (Hash)

    +{ profile: }+ with description, address, email, website, category

Raises:



296
297
298
299
# File 'lib/whatsapp/client.rb', line 296

def business_profile(phone)
  ensure_bridge!
  @bridge.post("/contacts/business-profile", {jid: normalize_number(phone)})
end

#connectself

Start the bridge and connect to WhatsApp.

Returns:

  • (self)


37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/whatsapp/client.rb', line 37

def connect
  Whatsapp.logger.info "Starting bridge..."
  @connection.start!
  @bridge = Bridge.new(@connection, config: @config)
  @bridge.open_websocket!

  @bridge.on_event do |event, data|
    handle_event(event, data)
  end

  Whatsapp.logger.info "Bridge started, waiting for WhatsApp connection"
  self
end

#connected?Boolean

Whether this client is connected to WhatsApp.

Returns:

  • (Boolean)


64
65
66
# File 'lib/whatsapp/client.rb', line 64

def connected?
  @connected
end

#create_group(subject:, participants:) ⇒ Group

Create a new group.

Parameters:

  • subject (String)

    group name

  • participants (Array<String>)

    phone numbers or JIDs to add

Returns:

  • (Group)

    the created group

Raises:



339
340
341
342
343
# File 'lib/whatsapp/client.rb', line 339

def create_group(subject:, participants:)
  ensure_bridge!
  response = @bridge.post("/groups", {subject: subject, participants: participants})
  Group.new(response[:group])
end

#disconnectvoid

This method returns an undefined value.

Disconnect from WhatsApp and stop the bridge.



54
55
56
57
58
59
# File 'lib/whatsapp/client.rb', line 54

def disconnect
  Whatsapp.logger.info "Disconnecting..."
  @bridge&.close!
  @connection.stop!
  @connected = false
end

#fetch_status(phone) ⇒ Hash

Get a contact's status/about text.

Parameters:

  • phone (String)

    phone number or JID

Returns:

  • (Hash)

    +{ status:, set_at: }+

Raises:



286
287
288
289
# File 'lib/whatsapp/client.rb', line 286

def fetch_status(phone)
  ensure_bridge!
  @bridge.post("/contacts/status", {jid: normalize_number(phone)})
end

#group(group_id) ⇒ Group

Get detailed info about a group.

Parameters:

Returns:

Raises:



327
328
329
330
331
# File 'lib/whatsapp/client.rb', line 327

def group(group_id)
  ensure_bridge!
  response = @bridge.get("/groups/#{group_id}")
  Group.new(response[:group])
end

#groupsArray<Group>

List all groups the account participates in.

Returns:

Raises:



316
317
318
319
320
# File 'lib/whatsapp/client.rb', line 316

def groups
  ensure_bridge!
  response = @bridge.get("/groups")
  Array(response[:groups]).map { |g| Group.new(g) }
end

#on(event_name) {|data| ... } ⇒ void

This method returns an undefined value.

Register a handler for any event.

Parameters:

  • event_name (String)

    event name

Yields:

  • (data)

    called when the event fires

Yield Parameters:

  • data (Hash, nil)

    event payload



116
117
118
# File 'lib/whatsapp/client.rb', line 116

def on(event_name, &block)
  events.on(event_name, &block)
end

#on_connected {|data| ... } ⇒ void

This method returns an undefined value.

Register a handler for successful connection.

Yields:

  • (data)

    called when WhatsApp connects



151
152
153
# File 'lib/whatsapp/client.rb', line 151

def on_connected(&block)
  on("connected", &block)
end

#on_contacts_update {|contacts| ... } ⇒ void

This method returns an undefined value.

Register a handler for contact list updates.

Yields:

  • (contacts)

    called when contacts change

Yield Parameters:

  • contacts (Array<Hash>)

    updated contact data



306
307
308
# File 'lib/whatsapp/client.rb', line 306

def on_contacts_update(&block)
  on("contacts.update", &block)
end

#on_message {|message| ... } ⇒ void

This method returns an undefined value.

Register a handler for new (live) incoming messages.

Yields:

  • (message)

    called for each new message

Yield Parameters:

  • message (Message)

    the received message



134
135
136
# File 'lib/whatsapp/client.rb', line 134

def on_message(&block)
  on("message.new") { |data| block.call(Message.new(data)) }
end

#on_message_any {|message| ... } ⇒ void

This method returns an undefined value.

Register a handler for all messages (live + history sync).

Yields:

  • (message)

    called for each message

Yield Parameters:

  • message (Message)

    the message



143
144
145
# File 'lib/whatsapp/client.rb', line 143

def on_message_any(&block)
  on("message") { |data| block.call(Message.new(data)) }
end

#on_message_delivered {|data| ... } ⇒ void

This method returns an undefined value.

Register a handler for "delivered" status only.

Yields:

  • (data)

    called when a message is marked as delivered

Yield Parameters:

  • data (Hash)

    status update info



# File 'lib/whatsapp/client.rb', line 170

#on_message_read {|data| ... } ⇒ void

This method returns an undefined value.

Register a handler for "read" status only.

Yields:

  • (data)

    called when a message is marked as read

Yield Parameters:

  • data (Hash)

    status update info



182
183
184
185
186
# File 'lib/whatsapp/client.rb', line 182

{on_message_sent: "sent", on_message_delivered: "delivered", on_message_read: "read"}.each do |method_name, status|
  define_method(method_name) do |&block|
    on("message.status") { |data| block.call(data) if data[:status] == status }
  end
end

#on_message_receipt {|data| ... } ⇒ void

This method returns an undefined value.

Register a handler for message receipt updates.

Yields:

  • (data)

    called with receipt update info

Yield Parameters:

  • data (Hash)

    receipt data



245
246
247
# File 'lib/whatsapp/client.rb', line 245

def on_message_receipt(&block)
  on("message-receipt.update", &block)
end

#on_message_sent {|data| ... } ⇒ void

This method returns an undefined value.

Register a handler for "sent" status only.

Yields:

  • (data)

    called when a message is marked as sent

Yield Parameters:

  • data (Hash)

    status update info



# File 'lib/whatsapp/client.rb', line 164

#on_message_status {|data| ... } ⇒ void

This method returns an undefined value.

Register a handler for all message status changes.

Yields:

  • (data)

    called with status update info

Yield Parameters:

  • data (Hash)

    +{ id:, to:, from_me:, status:, status_code: }+



160
161
162
# File 'lib/whatsapp/client.rb', line 160

def on_message_status(&block)
  on("message.status", &block)
end

#on_poll_vote {|data| ... } ⇒ void

This method returns an undefined value.

Register a handler for poll vote results.

Yields:

  • (data)

    called with aggregated poll votes

Yield Parameters:

  • data (Hash)

    +{ poll_message_id:, remote_jid:, votes: [{ name:, voters: [] }] }+



254
255
256
# File 'lib/whatsapp/client.rb', line 254

def on_poll_vote(&block)
  on("poll.vote", &block)
end

#on_presence_update {|data| ... } ⇒ void

This method returns an undefined value.

Register a handler for presence updates.

Yields:

  • (data)

    called with presence update info

Yield Parameters:

  • data (Hash)

    presence data



236
237
238
# File 'lib/whatsapp/client.rb', line 236

def on_presence_update(&block)
  on("presence.update", &block)
end

#on_qr {|qr| ... } ⇒ void

This method returns an undefined value.

Register a handler for QR code events.

Yields:

  • (qr)

    called with the base64-encoded QR code

Yield Parameters:

  • qr (String)

    base64 QR code data



125
126
127
# File 'lib/whatsapp/client.rb', line 125

def on_qr(&block)
  on("qr") { |data| block.call(data[:qr]) }
end

#on_whatsapp?(phone) ⇒ Hash

Check if a phone number is registered on WhatsApp.

Parameters:

  • phone (String)

    phone number (e.g. "+33612345678")

Returns:

  • (Hash)

    +{ exists:, jid: }+

Raises:



265
266
267
268
# File 'lib/whatsapp/client.rb', line 265

def on_whatsapp?(phone)
  ensure_bridge!
  @bridge.post("/contacts/check", {phone: normalize_number(phone)})
end

#profile_picture(phone, high_res: false) ⇒ Hash

Get a contact's profile picture URL.

Parameters:

  • phone (String)

    phone number or JID

  • high_res (Boolean) (defaults to: false)

    request full-resolution image (default: preview)

Returns:

  • (Hash)

    +{ url: }+ (nil if no picture set)

Raises:



276
277
278
279
# File 'lib/whatsapp/client.rb', line 276

def profile_picture(phone, high_res: false)
  ensure_bridge!
  @bridge.post("/contacts/profile-picture", {jid: normalize_number(phone), high_res: high_res})
end

#rate_limit_statsHash

Get rate limiting statistics.

Returns:

  • (Hash)

    current rate limit stats



396
397
398
# File 'lib/whatsapp/client.rb', line 396

def rate_limit_stats
  @rate_limiter.stats
end

#read_messages(keys) ⇒ Hash

Mark messages as read.

Parameters:

  • keys (Array<Hash>)

    message keys with +:remote_jid+, +:id+, and optional +:participant+

Returns:

  • (Hash)

    bridge response

Raises:



220
221
222
223
224
225
226
227
228
229
# File 'lib/whatsapp/client.rb', line 220

def read_messages(keys)
  ensure_bridge!
  camel_keys = Array(keys).map do |key|
    h = {remoteJid: key[:remote_jid] || key[:remoteJid], id: key[:id]}
    participant = key[:participant]
    h[:participant] = participant if participant
    h
  end
  @bridge.post("/messages/read", {keys: camel_keys})
end

#remove_group_participants(group_id, participants:) ⇒ Hash

Remove participants from a group.

Parameters:

  • group_id (String)

    group JID

  • participants (Array<String>)

    phone numbers or JIDs

Returns:

  • (Hash)

    bridge response

Raises:



377
378
379
380
# File 'lib/whatsapp/client.rb', line 377

def remove_group_participants(group_id, participants:)
  ensure_bridge!
  @bridge.post("/groups/#{group_id}/participants", {action: "remove", participants: participants})
end

#safety_statusHash

Get comprehensive safety status for monitoring.

Examples:

client.safety_status
# => {
#   risk: :low,
#   preset: :strict,
#   warmup_day: 3,
#   warmup_factor: 0.43,
#   limits: {
#     per_day: { used: 12, limit: 64, usage_pct: 18.8 },
#     per_hour: { used: 5, limit: 17, usage_pct: 29.4 },
#     new_chats: { used: 2, limit: 4, usage_pct: 50.0 }
#   },
#   features: {
#     typing_simulation: true,
#     auto_read_receipts: true,
#     ramp_up_enabled: true,
#     post_connect_delay: 60.0
#   }
# }

Returns:

  • (Hash)

    safety status with risk level, usage percentages, warmup progress, and active features



423
424
425
# File 'lib/whatsapp/client.rb', line 423

def safety_status
  @rate_limiter.safety_status
end

#send_message(to:, text: nil, media: nil, location: nil, contact: nil, react: nil, poll: nil, quoted_message_id: nil, unsafe: false) ⇒ Message

Send a message to a WhatsApp recipient.

Parameters:

  • to (String)

    recipient phone number (e.g. "+33612345678")

  • text (String, nil) (defaults to: nil)

    text content

  • media (Hash, nil) (defaults to: nil)

    media payload (+{ type:, file:, caption:, filename: }+)

  • location (Hash, nil) (defaults to: nil)

    location payload (+{ latitude:, longitude:, name:, address: }+)

  • contact (Hash, nil) (defaults to: nil)

    contact payload (+{ name:, phone: }+)

  • react (Hash, nil) (defaults to: nil)

    reaction payload (+{ emoji:, message_id: }+)

  • poll (Hash, nil) (defaults to: nil)

    poll payload (+{ question:, options:, selectable_count: }+)

  • quoted_message_id (String, nil) (defaults to: nil)

    ID of message to quote (reply)

  • unsafe (Boolean) (defaults to: false)

    skip all safety checks. Use with extreme caution — bypasses rate limiting, typing simulation, and all anti-ban protections.

Returns:

Raises:



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
# File 'lib/whatsapp/client.rb', line 83

def send_message(to:, text: nil, media: nil, location: nil, contact: nil, react: nil, poll: nil, quoted_message_id: nil, unsafe: false)
  ensure_bridge!

  recipient = normalize_number(to)

  if @config.rate_limiting_enabled && !unsafe
    @rate_limiter.acquire!(recipient)
    Whatsapp.logger.debug "Rate limiter OK", to: recipient
  end

  # Typing simulation: send "composing" presence before text messages
  if @config.typing_simulation && text && !react && !unsafe
    simulate_typing(recipient, text)
  end

  Whatsapp.logger.info "Sending message", to: recipient
  body = {
    to: recipient, text: text, media: media,
    location: location, contact: contact, react: react,
    poll: poll, quoted_message_id: quoted_message_id
  }
  response = @bridge.post("/messages/send", body)
  msg = Message.new(response[:message] || response)
  Whatsapp.logger.info "Message sent", id: msg.id, to: recipient
  msg
end

#send_presence(type, to: nil) ⇒ Hash

Send a presence update.

Parameters:

  • type (String)

    "composing", "recording", "paused", "available", "unavailable"

  • to (String, nil) (defaults to: nil)

    recipient JID (required for composing/recording/paused)

Returns:

  • (Hash)

    bridge response

Raises:



196
197
198
199
200
201
# File 'lib/whatsapp/client.rb', line 196

def send_presence(type, to: nil)
  ensure_bridge!
  body = {type: type}
  body[:to] = normalize_number(to) if to
  @bridge.post("/presence/update", body)
end

#statusHash

Get the bridge and WhatsApp connection status.

Returns:

  • (Hash)

    status info from the bridge

Raises:



388
389
390
391
# File 'lib/whatsapp/client.rb', line 388

def status
  ensure_bridge!
  @bridge.status
end

#subscribe_presence(jid) ⇒ Hash

Subscribe to presence updates for a JID.

Parameters:

  • jid (String)

    JID to subscribe to

Returns:

  • (Hash)

    bridge response

Raises:



208
209
210
211
# File 'lib/whatsapp/client.rb', line 208

def subscribe_presence(jid)
  ensure_bridge!
  @bridge.post("/presence/subscribe", {jid: normalize_number(jid)})
end

#update_group(group_id, subject: nil, description: nil) ⇒ Hash

Update a group's subject and/or description.

Parameters:

  • group_id (String)

    group JID

  • subject (String, nil) (defaults to: nil)

    new subject

  • description (String, nil) (defaults to: nil)

    new description

Returns:

  • (Hash)

    bridge response

Raises:



352
353
354
355
356
357
358
# File 'lib/whatsapp/client.rb', line 352

def update_group(group_id, subject: nil, description: nil)
  ensure_bridge!
  body = {}
  body[:subject] = subject if subject
  body[:description] = description unless description.nil?
  @bridge.put("/groups/#{group_id}", body)
end