whatsapp_notifier 0.9.1 → 0.9.3

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.
@@ -34,7 +34,12 @@ module WhatsAppNotifier
34
34
  media_size: %w[mediaSize media_size],
35
35
  sender_name: %w[senderName sender_name],
36
36
  to: %w[to],
37
- from_me: %w[fromMe from_me]
37
+ from_me: %w[fromMe from_me],
38
+ # 0.9.3: the `<digits>@lid` privacy id the counterparty above was
39
+ # RESOLVED FROM, present only for a privacy-keyed chat. The phone in
40
+ # from/to is always a real phone — this says where it came from, so a
41
+ # host can audit a thread's origin. Never a value to thread on.
42
+ sender_lid: %w[senderLid sender_lid]
38
43
  }.freeze
39
44
 
40
45
  def self.default_base_url
@@ -183,6 +188,27 @@ module WhatsAppNotifier
183
188
  Array(response["messages"]).map { |m| map_inbound_message(m) }
184
189
  end
185
190
 
191
+ # Resolves ONE WhatsApp privacy id (`@lid`) to the phone number behind it,
192
+ # returning the bare digits or nil when nothing is recoverable.
193
+ #
194
+ # Hosts need this to repair conversations they keyed on an unresolved
195
+ # privacy id — rows written when the service still fabricated a "phone"
196
+ # out of the LID's own digits (fixed in 0.9.3). Live capture resolves on
197
+ # its own; this is the backward-looking half.
198
+ #
199
+ # nil is a real answer ("no phone behind this id"), not an error: the
200
+ # service replies 200 with pn: null, and only a transport/gate failure
201
+ # raises.
202
+ def resolve_lid(lid:, metadata: {})
203
+ user_id = user_id_from(metadata)
204
+ digits = lid.to_s.split("@").first.to_s.gsub(/\D/, "")
205
+ return nil if digits.empty?
206
+
207
+ response = request(:get, "/contacts/lid/#{user_id}?lid=#{digits}")
208
+ resolved = response["pn"].to_s.split("@").first.to_s.gsub(/\D/, "")
209
+ resolved.empty? ? nil : resolved
210
+ end
211
+
186
212
  # Logs the user out of WhatsApp and clears their saved session on the service.
187
213
  def logout(metadata: {})
188
214
  user_id = user_id_from(metadata)
@@ -97,6 +97,10 @@ module WhatsAppNotifier
97
97
  client.fetch_history(chat_id: chat_id, limit: limit, provider: provider, metadata: metadata)
98
98
  end
99
99
 
100
+ def resolve_lid(lid:, provider: nil, metadata: {})
101
+ client.resolve_lid(lid: lid, provider: provider, metadata: metadata)
102
+ end
103
+
100
104
  def logout(provider: nil, metadata: {})
101
105
  client.logout(provider: provider, metadata: metadata)
102
106
  end
data/spec/client_spec.rb CHANGED
@@ -91,7 +91,7 @@ RSpec.describe WhatsAppNotifier::Client do
91
91
  end
92
92
  end
93
93
 
94
- it "delegates list_chats and fetch_history to the provider" do
94
+ it "delegates list_chats, fetch_history and resolve_lid to the provider" do
95
95
  Dir.mktmpdir do |dir|
96
96
  config.provider = :web_automation
97
97
  config.web_automation_enabled = true
@@ -101,7 +101,8 @@ RSpec.describe WhatsAppNotifier::Client do
101
101
  fetch_qr_code: "qr",
102
102
  connection_status: { state: "AUTHENTICATED", authenticated: true },
103
103
  list_chats: [{ id: "919@c.us", name: "Asha", last_message_at: 9 }],
104
- fetch_history: [{ from: "919@c.us", body: "old", message_id: "h1" }]
104
+ fetch_history: [{ from: "919@c.us", body: "old", message_id: "h1" }],
105
+ resolve_lid: "919882536803"
105
106
  )
106
107
  config.web_adapter = adapter
107
108
  client = described_class.new(configuration: config)
@@ -110,6 +111,8 @@ RSpec.describe WhatsAppNotifier::Client do
110
111
  .to eq([{ id: "919@c.us", name: "Asha", last_message_at: 9 }])
111
112
  expect(client.fetch_history(chat_id: "919@c.us", provider: :web_automation, metadata: { user_id: 1 }))
112
113
  .to eq([{ from: "919@c.us", body: "old", message_id: "h1" }])
114
+ expect(client.resolve_lid(lid: "125417440686124@lid", provider: :web_automation, metadata: { user_id: 1 }))
115
+ .to eq("919882536803")
113
116
  # The default page size survives the delegation chain untouched.
114
117
  expect(adapter).to have_received(:fetch_history).with(chat_id: "919@c.us", limit: 50, metadata: { user_id: 1 })
115
118
  end
@@ -249,11 +249,13 @@ RSpec.describe WhatsAppNotifier::Providers::WebAutomation do
249
249
  adapter = double(
250
250
  fetch_qr_code: "qr", connection_status: {},
251
251
  list_chats: [{ id: "919@c.us", name: "Asha", last_message_at: 9 }],
252
- fetch_history: [{ from: "919@c.us", body: "old", message_id: "h1" }]
252
+ fetch_history: [{ from: "919@c.us", body: "old", message_id: "h1" }],
253
+ resolve_lid: "919882536803"
253
254
  )
254
255
  config = build_config(path: File.join(dir, "session.json"), adapter: adapter)
255
256
  provider = described_class.new(configuration: config)
256
257
 
258
+ expect(provider.resolve_lid(lid: "125417440686124@lid", metadata: { user_id: 1 })).to eq("919882536803")
257
259
  expect(provider.list_chats(metadata: { user_id: 1 })).to eq([{ id: "919@c.us", name: "Asha", last_message_at: 9 }])
258
260
  expect(provider.fetch_history(chat_id: "919@c.us", limit: 20, metadata: { user_id: 1 }))
259
261
  .to eq([{ from: "919@c.us", body: "old", message_id: "h1" }])
@@ -282,6 +284,7 @@ RSpec.describe WhatsAppNotifier::Providers::WebAutomation do
282
284
 
283
285
  expect { provider.list_chats }.to raise_error(WhatsAppNotifier::ConfigurationError, /disabled/)
284
286
  expect { provider.fetch_history(chat_id: "919@c.us") }.to raise_error(WhatsAppNotifier::ConfigurationError, /disabled/)
287
+ expect { provider.resolve_lid(lid: "1@lid") }.to raise_error(WhatsAppNotifier::ConfigurationError, /disabled/)
285
288
  end
286
289
  end
287
290
 
@@ -293,6 +296,7 @@ RSpec.describe WhatsAppNotifier::Providers::WebAutomation do
293
296
 
294
297
  expect { provider.list_chats }.to raise_error(WhatsAppNotifier::ConfigurationError, /chat listing/)
295
298
  expect { provider.fetch_history(chat_id: "919@c.us") }.to raise_error(WhatsAppNotifier::ConfigurationError, /history replay/)
299
+ expect { provider.resolve_lid(lid: "1@lid") }.to raise_error(WhatsAppNotifier::ConfigurationError, /@lid resolution/)
296
300
  end
297
301
  end
298
302
 
@@ -454,6 +454,37 @@ RSpec.describe WhatsAppNotifier::WebAdapter do
454
454
  .to raise_error(/service request failed \(401\): User not authenticated/)
455
455
  end
456
456
 
457
+ # Privacy-id resolution (0.9.3): the half a host needs to repair threads it
458
+ # already keyed on an unresolved @lid.
459
+ it "resolves a privacy id to bare phone digits" do
460
+ allow(ENV).to receive(:[]).and_call_original
461
+ allow(ENV).to receive(:[]).with("WHATSAPP_WEBHOOK_TOKEN").and_return("sekrit")
462
+ response = http_success(body: { "success" => true, "lid" => "125417440686124@lid", "pn" => "919882536803@c.us" })
463
+ captured = nil
464
+ http = double("http")
465
+ allow(http).to receive(:request) { |req| captured = req; response }
466
+ allow(Net::HTTP).to receive(:start) { |*_args, **_kwargs, &blk| blk.call(http) }
467
+
468
+ expect(adapter.resolve_lid(lid: "125417440686124@lid", metadata: { user_id: "u-1" }))
469
+ .to eq("919882536803")
470
+ expect(captured).to be_a(Net::HTTP::Get)
471
+ expect(captured.path).to eq("/contacts/lid/u-1?lid=125417440686124")
472
+ expect(captured["X-WA-Token"]).to eq("sekrit")
473
+ end
474
+
475
+ it "returns nil when the privacy id has no phone behind it" do
476
+ allow(Net::HTTP).to receive(:start)
477
+ .and_return(http_success(body: { "success" => true, "lid" => "1@lid", "pn" => nil }))
478
+
479
+ expect(adapter.resolve_lid(lid: "125417440686124", metadata: {})).to be_nil
480
+ end
481
+
482
+ it "does not call the service for a lid with no digits" do
483
+ expect(Net::HTTP).not_to receive(:start)
484
+
485
+ expect(adapter.resolve_lid(lid: "@lid", metadata: {})).to be_nil
486
+ end
487
+
457
488
  it "fetches history with the token, posting the chat id and clamped limit" do
458
489
  allow(ENV).to receive(:[]).and_call_original
459
490
  allow(ENV).to receive(:[]).with("WHATSAPP_WEBHOOK_TOKEN").and_return("sekrit")
@@ -99,6 +99,7 @@ RSpec.describe WhatsAppNotifier do
99
99
  allow(fake_client).to receive(:refetch_media).and_return(mime: "image/jpeg", filename: nil, size: 5, status: "available")
100
100
  allow(fake_client).to receive(:list_chats).and_return([{ id: "919@c.us", name: "Asha", last_message_at: 9 }])
101
101
  allow(fake_client).to receive(:fetch_history).and_return([{ from: "919@c.us", body: "old", message_id: "h1" }])
102
+ allow(fake_client).to receive(:resolve_lid).and_return("919882536803")
102
103
  allow(fake_client).to receive(:logout).and_return(success: true)
103
104
  described_class.instance_variable_set(:@client, fake_client)
104
105
 
@@ -111,6 +112,7 @@ RSpec.describe WhatsAppNotifier do
111
112
  expect(described_class.refetch_media(message_id: "m1", chat_id: "919@c.us", provider: :web_automation, metadata: { user_id: 1 })).to include(status: "available")
112
113
  expect(described_class.list_chats(provider: :web_automation, metadata: { user_id: 1 })).to eq([{ id: "919@c.us", name: "Asha", last_message_at: 9 }])
113
114
  expect(described_class.fetch_history(chat_id: "919@c.us", limit: 20, provider: :web_automation, metadata: { user_id: 1 })).to eq([{ from: "919@c.us", body: "old", message_id: "h1" }])
115
+ expect(described_class.resolve_lid(lid: "125417440686124@lid", provider: :web_automation, metadata: { user_id: 1 })).to eq("919882536803")
114
116
  expect(described_class.logout(provider: :web_automation, metadata: { user_id: 1 })).to eq(success: true)
115
117
  expect(fake_client).to have_received(:fetch_media).with(message_id: "m1", provider: :web_automation, metadata: { user_id: 1 })
116
118
  expect(fake_client).to have_received(:delete_media).with(message_id: "m1", provider: :web_automation, metadata: { user_id: 1 })
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whatsapp_notifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kshitiz Sinha