B4J Code Snippet How to send file using Telegram bot API with multipart/form-data

I got it working finally. So, for sending files to Telegram bot without using curl, you need to do the following:
1. Download Erel's sample project of [B4X] Post multipart requests / file uploads with progress
2. Slightly modify it as shown below (pay attention to my comments in code):
B4X:
Private Sub Button1_Click
    Dim fd As MultipartFileData
    fd.Initialize
    fd.KeyName = "document"        'DOCUMENT KEY IS USED FOR SENDING GENERAL FILES
    fd.Dir = File.DirAssets        'SET YOUR FILE DIRECTORY
    fd.FileName = "1.txt"        'SET YOUR FILENAME
    Dim m As Map                'ADD A MAP FOR PARAMETERS
    m.Initialize                'INIT IT
    m.Put("chat_id", 12345678)    'PUT YOUR CHAT ID. ADD OTHER PARAMETERS TO THIS MAP IF NECESSARY
    Dim job As HttpJob = CreateMultipartJob("https://api.telegram.org/YOURBOTKEYHERE/sendDocument", m, Array(fd))    'PUT YOUR BOT KEY HERE. NOTE "sendDocument" METHOD IS USED WITH PARAMETERS MAP
    Wait For (job) JobDone (job As HttpJob)
    Log(job)
    File.Delete(xui.DefaultFolder, job.Tag)
    job.Release
End Sub

Public Sub CreateMultipartJob(Link As String, NameValues As Map, Files As List) As HttpJob
    Dim boundary As String = "---------------------------1461124740692"
    TempCounter = TempCounter + 1
    Dim TempFileName As String = "post-" & TempCounter
    Dim stream As OutputStream = File.OpenOutput(xui.DefaultFolder, TempFileName, False)
    Dim b() As Byte
    Dim eol As String = Chr(13) & Chr(10)
    Dim empty As Boolean = True
    If NameValues <> Null And NameValues.IsInitialized Then
        For Each key As String In NameValues.Keys
            Dim value As String = NameValues.Get(key)
            empty = MultipartStartSection (stream, empty)
            Dim s As String = _
$"--${boundary}
Content-Disposition: form-data; name="${key}"

${value}"$
            b = s.Replace(CRLF, eol).GetBytes("UTF8")
            stream.WriteBytes(b, 0, b.Length)
        Next
    End If
    If Files <> Null And Files.IsInitialized Then
        For Each fd As MultipartFileData In Files
            empty = MultipartStartSection (stream, empty)
            Dim s As String = _
$"--${boundary}
Content-Disposition: form-data; name="${fd.KeyName}"; filename="${fd.FileName}"
Content-Type: ${fd.ContentType}

"$
            b = s.Replace(CRLF, eol).GetBytes("UTF8")
            stream.WriteBytes(b, 0, b.Length)
            Dim in As InputStream = File.OpenInput(fd.Dir, fd.FileName)
            File.Copy2(in, stream)
        Next
    End If
    empty = MultipartStartSection (stream, empty)
    s = _
$"--${boundary}--
"$
    b = s.Replace(CRLF, eol).GetBytes("UTF8")
    stream.WriteBytes(b, 0, b.Length)
    Dim job As HttpJob
    job.Initialize("", Me)
    stream.Close
    Dim length As Int = File.Size(xui.DefaultFolder, TempFileName)
    Dim in As InputStream = File.OpenInput(xui.DefaultFolder, TempFileName)
    Dim cin As CountingInputStream
    cin.Initialize(in)
    Dim req As OkHttpRequest = job.GetRequest
    req.InitializePost(Link, cin, length)
    req.SetContentType("multipart/form-data; boundary=" & boundary)
    'req.SetContentEncoding("UTF8")                                        'COMMENT OR REMOVE THIS STRING - IT CAUSES ERROR RESPONSE FROM TELEGRAM
    'req.RemoveHeaders("Connection")                                    'IF YOU WANT YOUR REQUEST TO LOOK MORE CURL-LIKE, UNCOMMENT THIS
    'req.RemoveHeaders("Accept-Encoding")                                'AND THIS
    'req.SetHeader("Accept", "*/*")                                        'AND THIS
    TrackProgress(cin, length)
    job.Tag = TempFileName
    CallSubDelayed2(HttpUtils2Service, "SubmitJob", job)
    Return job
End Sub

3. All other subs leave unchanged.
4. Enjoy.
 

red30

Well-Known Member
Licensed User
Longtime User
Thank you a lot for this example. It also works in B4A but doesn't work in B4I because iRandomAccessFile doesn't support CountingInputStream.
In general, this example is necessary for uploading files with progress, but in my case, progress is not as important for me as the very ability to send files. Is there any way to send a file to telegram in B4I?
 

Gandalf

Member
Licensed User
Longtime User

If you just need to avoid using CountingInputStream, modify CreateMultipartJob sub as follows:
B4X:
    'Dim cin As CountingInputStream                'YOU CAN REMOVE THIS STRING IF YOU DON'T NEED TO TRACK PROGRESS
    'cin.Initialize(in)                            'AND THIS ONE
    Dim req As OkHttpRequest = job.GetRequest
    'req.InitializePost(Link, cin, length)        'REPLACE THIS STRING WITH
    req.InitializePost(Link, in, length)        'THIS ONE
    req.SetContentType("multipart/form-data; boundary=" & boundary)
    'TrackProgress(cin, length)                    'REMOVE THIS STRING, THE SUB IT CALLS, AND TrackerIndex VARIABLE

I don't have B4I so I can't test it there, but it works on B4J.
 

jinyistudio

Well-Known Member
Licensed User
Longtime User
May I ask what I need to modify when I want to send .png, .jpg and other image files
 

Gandalf

Member
Licensed User
Longtime User
May I ask what I need to modify when I want to send .png, .jpg and other image files
Add this sub to previous modifications:
B4X:
Sub SendPhotoFromFile (fileDir As String, fileName As String, caption As String, chatID As Long)
    Dim fd As MultipartFileData
    fd.Initialize
    fd.KeyName = "photo"
    fd.Dir = fileDir
    fd.FileName = fileName
    Dim m As Map
    m.Initialize
    m.Put("chat_id", chatID)
    If caption <> "" Then m.Put("caption", caption)
    Dim job As HttpJob = CreateMultipartJob("https://api.telegram.org/YOURBOTKEYHERE/sendPhoto", m, Array(fd))
    Wait For (job) JobDone (job As HttpJob)
    File.Delete(File.DirApp, job.Tag)
    If job.Success Then
        Dim resStr As String = job.GetString
        Dim fidIndex As Int = resStr.IndexOf("file_id")
        Dim fidStr As String = resStr.SubString2(fidIndex + 10, resStr.IndexOf2(QUOTE, fidIndex + 11))
        Log("File ID:" & fidStr)
    Else
        Log("ERROR:" & job.ErrorMessage)
    End If
    job.Release
End Sub
Note that it logs file ID on success. You can use this file ID later to send the same file without uploading it to Telegram servers. Same approach can be used with documents also.
 
Last edited:

jinyistudio

Well-Known Member
Licensed User
Longtime User
I use your program directly. I can only upload index.html successfully, other 1.txt, post1.png all have the following timeout message I use win7 x64 chinese version

B4X:
0%
0%
0%
68.03%
68.03%
68.03%
68.03%
68.03%
81.64%
100%
java.net.SocketTimeoutException: timeout
    at okhttp3.internal.http2.Http2Stream$StreamTimeout.newTimeoutException(Http2Stream.java:672)
    at okhttp3.internal.http2.Http2Stream$StreamTimeout.exitAndThrowIfTimedOut(Http2Stream.java:680)
    at okhttp3.internal.http2.Http2Stream.takeHeaders(Http2Stream.java:153)
    at okhttp3.internal.http2.Http2Codec.readResponseHeaders(Http2Codec.java:131)
    at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:88)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:45)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
    at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.java:93)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
    at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.java:93)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.java:127)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:147)
    at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:121)
    at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:257)
    at okhttp3.RealCall.execute(RealCall.java:93)
    at anywheresoftware.b4h.okhttp.OkHttpClientWrapper.executeWithTimeout(OkHttpClientWrapper.java:173)
    at anywheresoftware.b4h.okhttp.OkHttpClientWrapper.access$0(OkHttpClientWrapper.java:170)
    at anywheresoftware.b4h.okhttp.OkHttpClientWrapper$ExecuteHelper.run(OkHttpClientWrapper.java:218)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
ResponseError. Reason: java.net.SocketTimeoutException: timeout, Response:
TeleImge - - - java.net.SocketTimeoutException: timeout
(RuntimeException) java.lang.RuntimeException: JSON Object expected.
 

Attachments

  • 1.txt
    216 bytes · Views: 193
  • post1.png
    117.2 KB · Views: 168
  • index.html.txt
    25.8 KB · Views: 175

Gandalf

Member
Licensed User
Longtime User
I use your program directly. I can only upload index.html successfully, other 1.txt, post1.png all have the following timeout message I use win7 x64 chinese version
I have tested sending your files with my bot and all works correctly. Actually, I have no idea why you get this error for particular files only... Please make sure you use API method sendDocument for files and sendPhoto for images. Also check OkHttpUtils library version (I use jOkHttpUtils v3.00, B4J v9.80).
 

jinyistudio

Well-Known Member
Licensed User
Longtime User
my b4j v9.8 but my jOkHttpUtils2 is v2.96.
 
Cookies are required to use this site. You must accept them to continue using the site. Learn more…