@@ -278,7 +278,16 @@ <h2 class="text-lg font-semibold text-gray-800 mb-1">Send Media</h2>
278278 </ div >
279279 < div >
280280 < label class ="block text-sm font-medium text-gray-700 mb-1.5 "> Media URL < span class ="text-red-400 "> *</ span > </ label >
281- < input id ="media-url " type ="text " placeholder ="https://example.com/image.jpg " class ="w-full border border-gray-300 rounded-lg px-3.5 py-2.5 text-sm "/>
281+ < input id ="media-url " type ="text " placeholder ="https://example.com/image.jpg " class ="w-full border border-gray-300 rounded-lg px-3.5 py-2.5 text-sm " oninput ="mediaFileBase64=null;document.getElementById('media-file-badge').classList.add('hidden') "/>
282+ < div class ="flex items-center gap-2 mt-2 ">
283+ < span class ="text-xs text-gray-400 "> or</ span >
284+ < label class ="cursor-pointer inline-flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 font-medium ">
285+ < svg class ="w-3.5 h-3.5 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12 "/> </ svg >
286+ Choose file from computer
287+ < input type ="file " id ="media-file " class ="hidden " onchange ="onMediaFileSelected(this) "/>
288+ </ label >
289+ < span id ="media-file-badge " class ="hidden text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full "> </ span >
290+ </ div >
282291 </ div >
283292 < div >
284293 < label class ="block text-sm font-medium text-gray-700 mb-1.5 "> MIME Type < span class ="text-red-400 "> *</ span > </ label >
@@ -310,7 +319,16 @@ <h2 class="text-lg font-semibold text-gray-800 mb-1">Send Voice Note</h2>
310319 </ div >
311320 < div >
312321 < label class ="block text-sm font-medium text-gray-700 mb-1.5 "> Audio URL < span class ="text-red-400 "> *</ span > </ label >
313- < input id ="audio-url " type ="text " placeholder ="https://example.com/audio.mp3 " class ="w-full border border-gray-300 rounded-lg px-3.5 py-2.5 text-sm "/>
322+ < input id ="audio-url " type ="text " placeholder ="https://example.com/audio.mp3 " class ="w-full border border-gray-300 rounded-lg px-3.5 py-2.5 text-sm " oninput ="audioFileBase64=null;document.getElementById('audio-file-badge').classList.add('hidden') "/>
323+ < div class ="flex items-center gap-2 mt-2 ">
324+ < span class ="text-xs text-gray-400 "> or</ span >
325+ < label class ="cursor-pointer inline-flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 font-medium ">
326+ < svg class ="w-3.5 h-3.5 " fill ="none " stroke ="currentColor " viewBox ="0 0 24 24 "> < path stroke-linecap ="round " stroke-linejoin ="round " stroke-width ="2 " d ="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12 "/> </ svg >
327+ Choose file from computer
328+ < input type ="file " id ="audio-file " class ="hidden " accept ="audio/* " onchange ="onAudioFileSelected(this) "/>
329+ </ label >
330+ < span id ="audio-file-badge " class ="hidden text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full "> </ span >
331+ </ div >
314332 < p class ="text-xs text-gray-400 mt-1 "> Supported: mp3, ogg, m4a, wav</ p >
315333 </ div >
316334 < div class ="flex items-center gap-2 ">
@@ -1808,6 +1826,43 @@ <h3 class="text-sm font-semibold text-gray-800">Privacy Settings</h3>
18081826 sendBtn . disabled = false ;
18091827 }
18101828
1829+ // ── FILE → BASE64 HELPERS ─────────────────────────────────────────────────
1830+ let mediaFileBase64 = null ;
1831+ let audioFileBase64 = null ;
1832+
1833+ function readFileAsBase64 ( file ) {
1834+ return new Promise ( ( resolve , reject ) => {
1835+ const reader = new FileReader ( ) ;
1836+ reader . onload = e => resolve ( e . target . result . split ( ',' ) [ 1 ] ) ;
1837+ reader . onerror = reject ;
1838+ reader . readAsDataURL ( file ) ;
1839+ } ) ;
1840+ }
1841+
1842+ async function onMediaFileSelected ( input ) {
1843+ const file = input . files [ 0 ] ;
1844+ if ( ! file ) return ;
1845+ mediaFileBase64 = await readFileAsBase64 ( file ) ;
1846+ document . getElementById ( 'media-url' ) . value = '' ;
1847+ document . getElementById ( 'media-url' ) . placeholder = file . name ;
1848+ if ( file . type ) document . getElementById ( 'media-mimetype' ) . value = file . type ;
1849+ if ( ! document . getElementById ( 'media-filename' ) . value ) document . getElementById ( 'media-filename' ) . value = file . name ;
1850+ const badge = document . getElementById ( 'media-file-badge' ) ;
1851+ badge . textContent = file . name ;
1852+ badge . classList . remove ( 'hidden' ) ;
1853+ }
1854+
1855+ async function onAudioFileSelected ( input ) {
1856+ const file = input . files [ 0 ] ;
1857+ if ( ! file ) return ;
1858+ audioFileBase64 = await readFileAsBase64 ( file ) ;
1859+ document . getElementById ( 'audio-url' ) . value = '' ;
1860+ document . getElementById ( 'audio-url' ) . placeholder = file . name ;
1861+ const badge = document . getElementById ( 'audio-file-badge' ) ;
1862+ badge . textContent = file . name ;
1863+ badge . classList . remove ( 'hidden' ) ;
1864+ }
1865+
18111866 // ── SEND MEDIA ────────────────────────────────────────────────────────────
18121867 function updateMimetype ( ) {
18131868 const type = document . getElementById ( 'media-type' ) . value ;
@@ -1820,11 +1875,12 @@ <h3 class="text-sm font-semibold text-gray-800">Privacy Settings</h3>
18201875 const number = validatePhone ( rawNum ) ;
18211876 if ( ! number ) return showFieldError ( document . getElementById ( 'media-number' ) , rawNum ? `"${ rawNum } " is not a valid number` : 'Number is required' ) ;
18221877 const mediatype = document . getElementById ( 'media-type' ) . value ;
1823- const media = document . getElementById ( 'media-url' ) . value . trim ( ) ;
1878+ const media = mediaFileBase64 || document . getElementById ( 'media-url' ) . value . trim ( ) ;
18241879 const mimetype = document . getElementById ( 'media-mimetype' ) . value . trim ( ) ;
18251880 const fileName = document . getElementById ( 'media-filename' ) . value . trim ( ) ;
18261881 const caption = document . getElementById ( 'media-caption' ) . value . trim ( ) ;
1827- if ( ! media || ! mimetype ) return alert ( 'URL and MIME type are required.' ) ;
1882+ if ( ! media ) return alert ( 'Provide a URL or choose a file.' ) ;
1883+ if ( ! mimetype ) return alert ( 'MIME type is required.' ) ;
18281884 const body = { number, mediatype, media, mimetype } ;
18291885 if ( fileName ) body . fileName = fileName ;
18301886 if ( caption ) body . caption = caption ;
@@ -1836,9 +1892,9 @@ <h3 class="text-sm font-semibold text-gray-800">Privacy Settings</h3>
18361892 const rawNum = document . getElementById ( 'audio-number' ) . value . trim ( ) ;
18371893 const number = validatePhone ( rawNum ) ;
18381894 if ( ! number ) return showFieldError ( document . getElementById ( 'audio-number' ) , rawNum ? `"${ rawNum } " is not a valid number` : 'Number is required' ) ;
1839- const audio = document . getElementById ( 'audio-url' ) . value . trim ( ) ;
1895+ const audio = audioFileBase64 || document . getElementById ( 'audio-url' ) . value . trim ( ) ;
18401896 const encoding = document . getElementById ( 'audio-encoding' ) . checked ;
1841- if ( ! audio ) return alert ( 'Audio URL is required .' ) ;
1897+ if ( ! audio ) return alert ( 'Provide a URL or choose an audio file .' ) ;
18421898 await apiCall ( `/message/sendWhatsAppAudio/${ getCfg ( ) . instance } ` , { number, audio, encoding } ) ;
18431899 }
18441900
0 commit comments