flow_chat 0.4.1 → 0.4.2

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.
@@ -1529,6 +1529,7 @@
1529
1529
  // Extract message content based on type
1530
1530
  let messageText = ''
1531
1531
  let interactive = null
1532
+ let mediaContent = null
1532
1533
 
1533
1534
  switch (messagePayload.type) {
1534
1535
  case 'text':
@@ -1570,25 +1571,48 @@
1570
1571
  }
1571
1572
  break
1572
1573
  case 'image':
1573
- messageText = messagePayload.image.caption || 'Image message'
1574
- // In a real implementation, you might want to show the image
1575
- messageText = `📷 ${messageText}`
1574
+ messageText = messagePayload.image.caption || ''
1575
+ mediaContent = {
1576
+ type: 'image',
1577
+ url: messagePayload.image.link || `https://via.placeholder.com/300x200/25d366/white?text=Image+ID:+${messagePayload.image.id || 'media_123'}`,
1578
+ caption: messagePayload.image.caption
1579
+ }
1576
1580
  break
1577
1581
  case 'document':
1578
- messageText = messagePayload.document.caption || messagePayload.document.filename || 'Document'
1579
- messageText = `📄 ${messageText}`
1582
+ messageText = messagePayload.document.caption || ''
1583
+ mediaContent = {
1584
+ type: 'document',
1585
+ url: messagePayload.document.link,
1586
+ filename: messagePayload.document.filename || 'document.pdf',
1587
+ caption: messagePayload.document.caption
1588
+ }
1580
1589
  break
1581
1590
  case 'audio':
1582
- messageText = '🎵 Audio message'
1591
+ messageText = ''
1592
+ mediaContent = {
1593
+ type: 'audio',
1594
+ url: messagePayload.audio.link || `data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmkcBzuU2e+saDUGOWOJ6+2SV0gYKT2L5zJAGyJH4f2GHz+7zJMxtx9R5Dsl`
1595
+ }
1583
1596
  break
1584
1597
  case 'video':
1585
- messageText = messagePayload.video.caption || 'Video message'
1586
- messageText = `🎥 ${messageText}`
1598
+ messageText = messagePayload.video.caption || ''
1599
+ mediaContent = {
1600
+ type: 'video',
1601
+ url: messagePayload.video.link || `https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4`,
1602
+ caption: messagePayload.video.caption
1603
+ }
1587
1604
  break
1588
1605
  case 'location':
1589
1606
  const loc = messagePayload.location
1590
- messageText = `📍 Location: ${loc.name || 'Shared location'}`
1607
+ messageText = loc.name || 'Shared location'
1591
1608
  if (loc.address) messageText += `\n${loc.address}`
1609
+ mediaContent = {
1610
+ type: 'location',
1611
+ latitude: loc.latitude || 0,
1612
+ longitude: loc.longitude || 0,
1613
+ name: loc.name,
1614
+ address: loc.address
1615
+ }
1592
1616
  break
1593
1617
  default:
1594
1618
  messageText = `Unsupported message type: ${messagePayload.type}`
@@ -1596,16 +1620,235 @@
1596
1620
  }
1597
1621
 
1598
1622
  // Add the simulated message to the chat
1599
- addMessage(messageText, false, messagePayload.type, interactive)
1623
+ addMessage(messageText, false, messagePayload.type, interactive, mediaContent)
1600
1624
  }
1601
1625
 
1602
- function addMessage(content, isOutgoing = false, type = 'text', interactive = null) {
1626
+ function addMessage(content, isOutgoing = false, type = 'text', interactive = null, mediaContent = null) {
1603
1627
  const messageDiv = document.createElement('div')
1604
1628
  messageDiv.className = `message ${isOutgoing ? 'outgoing' : 'incoming'}`
1605
1629
 
1606
1630
  const bubbleDiv = document.createElement('div')
1607
1631
  bubbleDiv.className = 'message-bubble'
1608
- bubbleDiv.textContent = content
1632
+
1633
+ // Handle media content first
1634
+ if (mediaContent) {
1635
+ const mediaContainer = document.createElement('div')
1636
+ mediaContainer.className = 'media-container'
1637
+ mediaContainer.style.marginBottom = content ? '8px' : '0'
1638
+
1639
+ switch (mediaContent.type) {
1640
+ case 'image':
1641
+ const img = document.createElement('img')
1642
+ img.src = mediaContent.url
1643
+ img.style.maxWidth = '100%'
1644
+ img.style.height = 'auto'
1645
+ img.style.borderRadius = '8px'
1646
+ img.style.display = 'block'
1647
+ img.alt = mediaContent.caption || 'Image'
1648
+ img.onerror = function() {
1649
+ this.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjBmMGYwIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPvCfk7cgSW1hZ2U8L3RleHQ+PC9zdmc+'
1650
+ this.style.border = '1px solid #e0e0e0'
1651
+ }
1652
+ mediaContainer.appendChild(img)
1653
+ break
1654
+
1655
+ case 'video':
1656
+ const video = document.createElement('video')
1657
+ video.src = mediaContent.url
1658
+ video.controls = true
1659
+ video.style.maxWidth = '100%'
1660
+ video.style.height = 'auto'
1661
+ video.style.borderRadius = '8px'
1662
+ video.style.display = 'block'
1663
+ video.preload = 'metadata'
1664
+ video.onerror = function() {
1665
+ const placeholder = document.createElement('div')
1666
+ placeholder.style.width = '300px'
1667
+ placeholder.style.height = '200px'
1668
+ placeholder.style.backgroundColor = '#f0f0f0'
1669
+ placeholder.style.border = '1px solid #e0e0e0'
1670
+ placeholder.style.borderRadius = '8px'
1671
+ placeholder.style.display = 'flex'
1672
+ placeholder.style.alignItems = 'center'
1673
+ placeholder.style.justifyContent = 'center'
1674
+ placeholder.style.color = '#999'
1675
+ placeholder.style.fontSize = '14px'
1676
+ placeholder.textContent = '🎥 Video'
1677
+ mediaContainer.replaceChild(placeholder, video)
1678
+ }
1679
+ mediaContainer.appendChild(video)
1680
+ break
1681
+
1682
+ case 'audio':
1683
+ const audio = document.createElement('audio')
1684
+ audio.src = mediaContent.url
1685
+ audio.controls = true
1686
+ audio.style.width = '100%'
1687
+ audio.style.maxWidth = '300px'
1688
+ audio.preload = 'metadata'
1689
+ audio.onerror = function() {
1690
+ const placeholder = document.createElement('div')
1691
+ placeholder.style.padding = '12px 16px'
1692
+ placeholder.style.backgroundColor = '#f0f0f0'
1693
+ placeholder.style.border = '1px solid #e0e0e0'
1694
+ placeholder.style.borderRadius = '8px'
1695
+ placeholder.style.display = 'flex'
1696
+ placeholder.style.alignItems = 'center'
1697
+ placeholder.style.gap = '8px'
1698
+ placeholder.style.color = '#666'
1699
+ placeholder.style.fontSize = '14px'
1700
+ placeholder.innerHTML = '🎵 <span>Audio message</span>'
1701
+ mediaContainer.replaceChild(placeholder, audio)
1702
+ }
1703
+ mediaContainer.appendChild(audio)
1704
+ break
1705
+
1706
+ case 'document':
1707
+ const docContainer = document.createElement('div')
1708
+ docContainer.style.padding = '12px 16px'
1709
+ docContainer.style.backgroundColor = '#f8f9fa'
1710
+ docContainer.style.border = '1px solid #e0e0e0'
1711
+ docContainer.style.borderRadius = '8px'
1712
+ docContainer.style.display = 'flex'
1713
+ docContainer.style.alignItems = 'center'
1714
+ docContainer.style.gap = '12px'
1715
+ docContainer.style.cursor = 'pointer'
1716
+ docContainer.style.transition = 'background-color 0.2s'
1717
+
1718
+ const docIcon = document.createElement('div')
1719
+ docIcon.style.fontSize = '24px'
1720
+ docIcon.textContent = getDocumentIcon(mediaContent.filename)
1721
+
1722
+ const docInfo = document.createElement('div')
1723
+ docInfo.style.flex = '1'
1724
+ docInfo.style.minWidth = '0'
1725
+
1726
+ const docName = document.createElement('div')
1727
+ docName.style.fontWeight = '600'
1728
+ docName.style.fontSize = '14px'
1729
+ docName.style.color = '#333'
1730
+ docName.style.overflow = 'hidden'
1731
+ docName.style.textOverflow = 'ellipsis'
1732
+ docName.style.whiteSpace = 'nowrap'
1733
+ docName.textContent = mediaContent.filename
1734
+
1735
+ const docType = document.createElement('div')
1736
+ docType.style.fontSize = '12px'
1737
+ docType.style.color = '#666'
1738
+ docType.textContent = 'Document'
1739
+
1740
+ docInfo.appendChild(docName)
1741
+ docInfo.appendChild(docType)
1742
+ docContainer.appendChild(docIcon)
1743
+ docContainer.appendChild(docInfo)
1744
+
1745
+ // Add click handler if URL is available
1746
+ if (mediaContent.url) {
1747
+ const downloadIcon = document.createElement('div')
1748
+ downloadIcon.style.fontSize = '18px'
1749
+ downloadIcon.style.color = '#666'
1750
+ downloadIcon.textContent = '⬇️'
1751
+ docContainer.appendChild(downloadIcon)
1752
+
1753
+ docContainer.onclick = () => {
1754
+ if (mediaContent.url.startsWith('http')) {
1755
+ window.open(mediaContent.url, '_blank')
1756
+ }
1757
+ }
1758
+
1759
+ docContainer.onmouseover = () => {
1760
+ docContainer.style.backgroundColor = '#e8f5e8'
1761
+ }
1762
+ docContainer.onmouseout = () => {
1763
+ docContainer.style.backgroundColor = '#f8f9fa'
1764
+ }
1765
+ }
1766
+
1767
+ mediaContainer.appendChild(docContainer)
1768
+ break
1769
+
1770
+ case 'location':
1771
+ const locationContainer = document.createElement('div')
1772
+ locationContainer.style.border = '1px solid #e0e0e0'
1773
+ locationContainer.style.borderRadius = '8px'
1774
+ locationContainer.style.overflow = 'hidden'
1775
+ locationContainer.style.maxWidth = '300px'
1776
+
1777
+ // Create a simple map-like visualization
1778
+ const mapDiv = document.createElement('div')
1779
+ mapDiv.style.height = '150px'
1780
+ mapDiv.style.background = 'linear-gradient(135deg, #a8e6cf 0%, #dcedc1 100%)'
1781
+ mapDiv.style.position = 'relative'
1782
+ mapDiv.style.display = 'flex'
1783
+ mapDiv.style.alignItems = 'center'
1784
+ mapDiv.style.justifyContent = 'center'
1785
+ mapDiv.style.fontSize = '32px'
1786
+ mapDiv.textContent = '📍'
1787
+
1788
+ // Add coordinate text
1789
+ const coordDiv = document.createElement('div')
1790
+ coordDiv.style.position = 'absolute'
1791
+ coordDiv.style.bottom = '8px'
1792
+ coordDiv.style.right = '8px'
1793
+ coordDiv.style.background = 'rgba(255,255,255,0.9)'
1794
+ coordDiv.style.padding = '4px 8px'
1795
+ coordDiv.style.borderRadius = '4px'
1796
+ coordDiv.style.fontSize = '10px'
1797
+ coordDiv.style.color = '#666'
1798
+ coordDiv.style.fontFamily = 'monospace'
1799
+ coordDiv.textContent = `${mediaContent.latitude.toFixed(4)}, ${mediaContent.longitude.toFixed(4)}`
1800
+ mapDiv.appendChild(coordDiv)
1801
+
1802
+ const locationInfo = document.createElement('div')
1803
+ locationInfo.style.padding = '12px'
1804
+ locationInfo.style.backgroundColor = 'white'
1805
+
1806
+ if (mediaContent.name) {
1807
+ const nameDiv = document.createElement('div')
1808
+ nameDiv.style.fontWeight = '600'
1809
+ nameDiv.style.fontSize = '14px'
1810
+ nameDiv.style.color = '#333'
1811
+ nameDiv.style.marginBottom = '4px'
1812
+ nameDiv.textContent = mediaContent.name
1813
+ locationInfo.appendChild(nameDiv)
1814
+ }
1815
+
1816
+ if (mediaContent.address) {
1817
+ const addressDiv = document.createElement('div')
1818
+ addressDiv.style.fontSize = '12px'
1819
+ addressDiv.style.color = '#666'
1820
+ addressDiv.textContent = mediaContent.address
1821
+ locationInfo.appendChild(addressDiv)
1822
+ }
1823
+
1824
+ // Add link to open in maps
1825
+ const mapsLink = document.createElement('div')
1826
+ mapsLink.style.fontSize = '12px'
1827
+ mapsLink.style.color = '#25d366'
1828
+ mapsLink.style.cursor = 'pointer'
1829
+ mapsLink.style.marginTop = '8px'
1830
+ mapsLink.textContent = '🗺️ View in Maps'
1831
+ mapsLink.onclick = () => {
1832
+ const url = `https://www.google.com/maps?q=${mediaContent.latitude},${mediaContent.longitude}`
1833
+ window.open(url, '_blank')
1834
+ }
1835
+ locationInfo.appendChild(mapsLink)
1836
+
1837
+ locationContainer.appendChild(mapDiv)
1838
+ locationContainer.appendChild(locationInfo)
1839
+ mediaContainer.appendChild(locationContainer)
1840
+ break
1841
+ }
1842
+
1843
+ bubbleDiv.appendChild(mediaContainer)
1844
+ }
1845
+
1846
+ // Add text content if present
1847
+ if (content) {
1848
+ const textDiv = document.createElement('div')
1849
+ textDiv.textContent = content
1850
+ bubbleDiv.appendChild(textDiv)
1851
+ }
1609
1852
 
1610
1853
  messageDiv.appendChild(bubbleDiv)
1611
1854
 
@@ -1640,10 +1883,42 @@
1640
1883
  bubbleDiv.appendChild(buttonsDiv)
1641
1884
  }
1642
1885
 
1886
+ messageDiv.appendChild(bubbleDiv)
1887
+
1643
1888
  elements.messagesArea.appendChild(messageDiv)
1644
1889
  elements.messagesArea.scrollTop = elements.messagesArea.scrollHeight
1645
1890
  }
1646
1891
 
1892
+ function getDocumentIcon(filename) {
1893
+ if (!filename) return '📄'
1894
+
1895
+ const ext = filename.split('.').pop().toLowerCase()
1896
+
1897
+ const iconMap = {
1898
+ 'pdf': '📕',
1899
+ 'doc': '📘',
1900
+ 'docx': '📘',
1901
+ 'xls': '📗',
1902
+ 'xlsx': '📗',
1903
+ 'ppt': '📙',
1904
+ 'pptx': '📙',
1905
+ 'txt': '📝',
1906
+ 'zip': '🗜️',
1907
+ 'rar': '🗜️',
1908
+ '7z': '🗜️',
1909
+ 'mp3': '🎵',
1910
+ 'wav': '🎵',
1911
+ 'mp4': '🎥',
1912
+ 'avi': '🎥',
1913
+ 'jpg': '🖼️',
1914
+ 'jpeg': '🖼️',
1915
+ 'png': '🖼️',
1916
+ 'gif': '🖼️'
1917
+ }
1918
+
1919
+ return iconMap[ext] || '📄'
1920
+ }
1921
+
1647
1922
  function addInfoMessage(content) {
1648
1923
  const messageDiv = document.createElement('div')
1649
1924
  messageDiv.className = 'message incoming'
@@ -14,7 +14,7 @@ module FlowChat
14
14
 
15
15
  # Add timestamp for all requests
16
16
  context["request.timestamp"] = Time.current.iso8601
17
-
17
+
18
18
  # Set a basic message_id (can be enhanced based on actual Nsano implementation)
19
19
  context["request.message_id"] = SecureRandom.uuid
20
20
 
@@ -24,4 +24,4 @@ module FlowChat
24
24
  end
25
25
  end
26
26
  end
27
- end
27
+ end
@@ -59,19 +59,19 @@ module FlowChat
59
59
 
60
60
  # For USSD, we append the media URL to the message
61
61
  media_text = case media_type.to_sym
62
- when :image
63
- "📷 Image: #{media_url}"
64
- when :document
65
- "📄 Document: #{media_url}"
66
- when :audio
67
- "🎵 Audio: #{media_url}"
68
- when :video
69
- "🎥 Video: #{media_url}"
70
- when :sticker
71
- "😊 Sticker: #{media_url}"
72
- else
73
- "📎 Media: #{media_url}"
74
- end
62
+ when :image
63
+ "📷 Image: #{media_url}"
64
+ when :document
65
+ "📄 Document: #{media_url}"
66
+ when :audio
67
+ "🎵 Audio: #{media_url}"
68
+ when :video
69
+ "🎥 Video: #{media_url}"
70
+ when :sticker
71
+ "😊 Sticker: #{media_url}"
72
+ else
73
+ "📎 Media: #{media_url}"
74
+ end
75
75
 
76
76
  # Combine message with media information
77
77
  "#{message}\n\n#{media_text}"
@@ -1,3 +1,3 @@
1
1
  module FlowChat
2
- VERSION = "0.4.1"
2
+ VERSION = "0.4.2"
3
3
  end
@@ -61,4 +61,4 @@ module FlowChat
61
61
  end
62
62
  end
63
63
  end
64
- end
64
+ end
@@ -23,7 +23,7 @@ module FlowChat
23
23
  end
24
24
 
25
25
  # Send a text message
26
- # @param to [String] Phone number in E.164 format
26
+ # @param to [String] Phone number in E.164 format
27
27
  # @param text [String] Message text
28
28
  # @return [Hash] API response or nil on error
29
29
  def send_text(to, text)
@@ -36,7 +36,7 @@ module FlowChat
36
36
  # @param buttons [Array] Array of button hashes with :id and :title
37
37
  # @return [Hash] API response or nil on error
38
38
  def send_buttons(to, text, buttons)
39
- send_message(to, [:interactive_buttons, text, { buttons: buttons }])
39
+ send_message(to, [:interactive_buttons, text, {buttons: buttons}])
40
40
  end
41
41
 
42
42
  # Send interactive list
@@ -46,7 +46,7 @@ module FlowChat
46
46
  # @param button_text [String] Button text (default: "Choose")
47
47
  # @return [Hash] API response or nil on error
48
48
  def send_list(to, text, sections, button_text = "Choose")
49
- send_message(to, [:interactive_list, text, { sections: sections, button_text: button_text }])
49
+ send_message(to, [:interactive_list, text, {sections: sections, button_text: button_text}])
50
50
  end
51
51
 
52
52
  # Send a template message
@@ -56,10 +56,10 @@ module FlowChat
56
56
  # @param language [String] Language code (default: "en_US")
57
57
  # @return [Hash] API response or nil on error
58
58
  def send_template(to, template_name, components = [], language = "en_US")
59
- send_message(to, [:template, "", {
60
- template_name: template_name,
61
- components: components,
62
- language: language
59
+ send_message(to, [:template, "", {
60
+ template_name: template_name,
61
+ components: components,
62
+ language: language
63
63
  }])
64
64
  end
65
65
 
@@ -121,12 +121,12 @@ module FlowChat
121
121
  # @raise [StandardError] If upload fails
122
122
  def upload_media(file_path_or_io, mime_type, filename = nil)
123
123
  raise ArgumentError, "mime_type is required" if mime_type.nil? || mime_type.empty?
124
-
124
+
125
125
  if file_path_or_io.is_a?(String)
126
126
  # File path
127
127
  raise ArgumentError, "File not found: #{file_path_or_io}" unless File.exist?(file_path_or_io)
128
128
  filename ||= File.basename(file_path_or_io)
129
- file = File.open(file_path_or_io, 'rb')
129
+ file = File.open(file_path_or_io, "rb")
130
130
  else
131
131
  # IO object
132
132
  file = file_path_or_io
@@ -140,24 +140,24 @@ module FlowChat
140
140
 
141
141
  # Prepare multipart form data
142
142
  boundary = "----WebKitFormBoundary#{SecureRandom.hex(16)}"
143
-
143
+
144
144
  form_data = []
145
145
  form_data << "--#{boundary}"
146
146
  form_data << 'Content-Disposition: form-data; name="messaging_product"'
147
147
  form_data << ""
148
148
  form_data << "whatsapp"
149
-
149
+
150
150
  form_data << "--#{boundary}"
151
151
  form_data << "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\""
152
152
  form_data << "Content-Type: #{mime_type}"
153
153
  form_data << ""
154
154
  form_data << file.read
155
-
155
+
156
156
  form_data << "--#{boundary}"
157
157
  form_data << 'Content-Disposition: form-data; name="type"'
158
158
  form_data << ""
159
159
  form_data << mime_type
160
-
160
+
161
161
  form_data << "--#{boundary}--"
162
162
 
163
163
  body = form_data.join("\r\n")
@@ -168,14 +168,10 @@ module FlowChat
168
168
  request.body = body
169
169
 
170
170
  response = http.request(request)
171
-
171
+
172
172
  if response.is_a?(Net::HTTPSuccess)
173
173
  data = JSON.parse(response.body)
174
- if data['id']
175
- data['id']
176
- else
177
- raise StandardError, "Failed to upload media: #{data}"
178
- end
174
+ data["id"] || raise(StandardError, "Failed to upload media: #{data}")
179
175
  else
180
176
  Rails.logger.error "WhatsApp Media Upload error: #{response.body}"
181
177
  raise StandardError, "Media upload failed: #{response.body}"
@@ -195,7 +191,7 @@ module FlowChat
195
191
  messaging_product: "whatsapp",
196
192
  to: to,
197
193
  type: "text",
198
- text: { body: content }
194
+ text: {body: content}
199
195
  }
200
196
  when :interactive_buttons
201
197
  {
@@ -204,7 +200,7 @@ module FlowChat
204
200
  type: "interactive",
205
201
  interactive: {
206
202
  type: "button",
207
- body: { text: content },
203
+ body: {text: content},
208
204
  action: {
209
205
  buttons: options[:buttons].map.with_index do |button, index|
210
206
  {
@@ -225,7 +221,7 @@ module FlowChat
225
221
  type: "interactive",
226
222
  interactive: {
227
223
  type: "list",
228
- body: { text: content },
224
+ body: {text: content},
229
225
  action: {
230
226
  button: options[:button_text] || "Choose",
231
227
  sections: options[:sections]
@@ -239,7 +235,7 @@ module FlowChat
239
235
  type: "template",
240
236
  template: {
241
237
  name: options[:template_name],
242
- language: { code: options[:language] || "en_US" },
238
+ language: {code: options[:language] || "en_US"},
243
239
  components: options[:components] || []
244
240
  }
245
241
  }
@@ -284,7 +280,7 @@ module FlowChat
284
280
  messaging_product: "whatsapp",
285
281
  to: to,
286
282
  type: "text",
287
- text: { body: content.to_s }
283
+ text: {body: content.to_s}
288
284
  }
289
285
  end
290
286
  end
@@ -301,7 +297,7 @@ module FlowChat
301
297
  request["Authorization"] = "Bearer #{@config.access_token}"
302
298
 
303
299
  response = http.request(request)
304
-
300
+
305
301
  if response.is_a?(Net::HTTPSuccess)
306
302
  data = JSON.parse(response.body)
307
303
  data["url"]
@@ -326,7 +322,7 @@ module FlowChat
326
322
  request["Authorization"] = "Bearer #{@config.access_token}"
327
323
 
328
324
  response = http.request(request)
329
-
325
+
330
326
  if response.is_a?(Net::HTTPSuccess)
331
327
  response.body
332
328
  else
@@ -337,15 +333,15 @@ module FlowChat
337
333
 
338
334
  # Get MIME type from URL without downloading (HEAD request)
339
335
  def get_media_mime_type(url)
340
- require 'net/http'
341
-
336
+ require "net/http"
337
+
342
338
  uri = URI(url)
343
339
  http = Net::HTTP.new(uri.host, uri.port)
344
- http.use_ssl = (uri.scheme == 'https')
345
-
340
+ http.use_ssl = (uri.scheme == "https")
341
+
346
342
  # Use HEAD request to get headers without downloading content
347
343
  response = http.head(uri.path)
348
- response['content-type']
344
+ response["content-type"]
349
345
  rescue => e
350
346
  Rails.logger.warn "Could not detect MIME type for #{url}: #{e.message}"
351
347
  nil
@@ -358,7 +354,7 @@ module FlowChat
358
354
  # @return [Hash] Media object for WhatsApp API
359
355
  def build_media_object(options)
360
356
  media_obj = {}
361
-
357
+
362
358
  # Handle URL or ID
363
359
  if options[:url]
364
360
  # Use URL directly
@@ -367,17 +363,17 @@ module FlowChat
367
363
  # Use provided media ID directly
368
364
  media_obj[:id] = options[:id]
369
365
  end
370
-
366
+
371
367
  # Add optional fields
372
368
  media_obj[:caption] = options[:caption] if options[:caption]
373
369
  media_obj[:filename] = options[:filename] if options[:filename]
374
-
370
+
375
371
  media_obj
376
372
  end
377
373
 
378
374
  # Check if input is a URL or file path/media ID
379
375
  def url?(input)
380
- input.to_s.start_with?('http://', 'https://')
376
+ input.to_s.start_with?("http://", "https://")
381
377
  end
382
378
 
383
379
  # Extract filename from URL
@@ -403,7 +399,7 @@ module FlowChat
403
399
  request.body = message_data.to_json
404
400
 
405
401
  response = http.request(request)
406
-
402
+
407
403
  if response.is_a?(Net::HTTPSuccess)
408
404
  JSON.parse(response.body)
409
405
  else
@@ -414,21 +410,21 @@ module FlowChat
414
410
 
415
411
  def send_media_message(to, media_type, url_or_id, caption: nil, filename: nil, mime_type: nil)
416
412
  media_object = if url?(url_or_id)
417
- { link: url_or_id }
418
- else
419
- { id: url_or_id }
420
- end
413
+ {link: url_or_id}
414
+ else
415
+ {id: url_or_id}
416
+ end
421
417
 
422
418
  # Add caption if provided (stickers don't support captions)
423
419
  media_object[:caption] = caption if caption && media_type != :sticker
424
-
420
+
425
421
  # Add filename for documents
426
422
  media_object[:filename] = filename if filename && media_type == :document
427
423
 
428
424
  message = {
429
- messaging_product: "whatsapp",
430
- to: to,
431
- type: media_type.to_s,
425
+ :messaging_product => "whatsapp",
426
+ :to => to,
427
+ :type => media_type.to_s,
432
428
  media_type.to_s => media_object
433
429
  }
434
430
 
@@ -436,4 +432,4 @@ module FlowChat
436
432
  end
437
433
  end
438
434
  end
439
- end
435
+ end