VisualBasic Screenshot per Telegram versenden

7H0M45

Lt. Commander
Registriert
Jan. 2010
Beiträge
1.148
Hallo zusammen,

ich bin gerade bei einem Projekt mir mein eigenes SmartHome aufzubauen. Aktuell auf Basis VBA (steinigt mich nicht, das kann ich relativ gut. Später kommt evtl. was anderes)

Teil davon ist ein TelegramBot der mir von außerhalb die Kommunikation mit dem SmartHome ermöglicht. Der Bot Funktioniert soweit gut und läuft mittlerweile eigentlich rund.

Mein Problem jetzt:
Ich möchte einen Screenshot des Rechners, auf dem das Programm läuft, verschicken. Wie ich einen Screenshot speicher, hab ich mittlerweile raus. Ich hänge gerade an dem Code um das ganze bei Telegram hochzuladen und zu versenden.

Die Doku sagt folgendes:
Photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. More info on Sending Files »

bzw.

Sending files
There are three ways to send files (photos, stickers, audio, media, etc.):
  1. If the file is already stored somewhere on the Telegram servers, you don't need to reupload it: each file object has a file_id field, simply pass this file_id as a parameter instead of uploading. There are no limits for files sent this way.
  2. Provide Telegram with an HTTP URL for the file to be sent. Telegram will download and send the file. 5 MB max size for photos and 20 MB max for other types of content.
  3. Post the file using multipart/form-data in the usual way that files are uploaded via the browser. 10 MB max size for photos, 50 MB for other files.

Bei mir scheiterts jetzt am "multipart/form-data". Ich habe keine Ahnung, wie ich damit ein Bild zu Telegram schicken soll.
Kann mir da jemand auf die Sprünge helfen?

Die normalen Nachrichten versende ich zB. so:
Code:
Private Function mfHTTP_POST()
  mfHTTP_POST = vbNullString
  With CreateObject("MSXML2.XMLHTTP")
     pstrURL = "https://api.telegram.org/bot0000000000/sendMessage?"
     pstrPostData = "{""chat_id"": ""0000000000"", ""text"": ""Beispieltext""}"
    .Open "POST", pstrURL, False
    .setRequestHeader "Content-Type", "application/json"
    .Send (pstrPostData)
     mfHTTP_POST = .ResponseText
  End With
End Function
 
Wenn du VBA nutzt, hast du ein System mit Windows. Was ist wenn du mit dem stinknomralem Snipping Tool dein Screenshoot machst und diesen per Outlook an deine E-Mail versendest?
 
Da das ganze sogar in Outlook läuft, wäre das prinzipiell kein Problem, würde ich hinbekommen.

Ich wollte es aber eigentlich in Telegram, da ich ja von dort aus das ganze Steuer über die InlineKeyboards. Telegram bietet diese Funktionalität ja auch, daher muss es ja irgendwie gehen.

Das mit der Mail wäre eine Notlösung an die ich selbst garnicht gedacht habe. Deswegen, danke dafür.
 
Weiß nicht ob du das schon gesehen hast, aber hier wird im punkt 4 beschrieben wie Dateien per telegram über curl versendet werden. Wenn du das per VBA mit exec ansteuerst, könnte das dein Problem lösen.

P.S.
Übrigens, cooler Nickname :) Das Gehirn vervollständigt die fehlenden Teile von alleine :-D
 
Zuletzt bearbeitet:
Code:
With objRequest
        .Open "POST", "https://api.telegram.org/bot0000000000/sendPhoto?", False
        .setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
        .send (strPostData)
        strResponse = .responseText
    End With

Oder sowas Ähnliches
Ergänzung ()

Hier findest du ein komplettes Beispiel für multipart form data
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: new Account()
G-Red schrieb:
Weiß nicht ob du das schon gesehen hast, aber hier wird im punkt 4 beschrieben wie Dateien per telegram über curl versendet werden. Wenn du das per VBA mit exec ansteuerst, könnte das dein Problem lösen.


Hat mich ein bisschen Googlen gekostet, aber ich hab jetzt rausgefunden, dass ich einen cURL über die Shell und die cmd.exe abschicken kann.

Code:
Shell("cmd.exe /S /C curl -X  POST ""https://api.telegram.org/bot000000000000/sendPhoto"" -F chat_id=00000000000 -F photo=""@C:\Users\ABC\Screenshots\Screenshot.jpg""")

Nur bekomme ich jetzt hierdurch keinen Rückgabewert was die API mir Antwortet, ist der Weg über die Shell der falsche? Der Screenshot wird aber tatsächlich verschickt, also bin ich da schonmal einen kleinen Schritt weiter 👍



nerdalicious schrieb:

Das habe ich bereits gefunden, konnte damit aber leider nichts anfangen. Irgendwie verstehe ich da noch nicht so ganz wie ich das umsetzen muss. Aber ich häng mich nochmal rein.
 
@nerdalicious hat dir quasi schon die Antwort und ein Beispiel geschickt, wie es wohl in VB funktioniert. In dem Beispiel siehst du, wie du ein multipart/form-data body zusammen stellen kannst. Die Telegram Bot API erwartet nämlich ein multipart/form-data (oder eine URL von einem Webserver wo das Bild zufinden wäre).

Ansonsten mit curl einfach den Parameter -I (großes Ihh) nutzen und dann die erste Zeile auslesen.
 
ich habe jetzt mal versucht die Variante von @nerdalicious umzusetzen.

Code:
Public Function pvPostFile()
'Private Function pvPostFile(sUrl As String, sFileName As String, Optional ByVal bAsync As Boolean) As String
    Const STR_BOUNDARY  As String = "3fbd04f5-b1ed-4060-99b9-fca7ff59c113"
    Dim nFile           As Integer
    Dim baBuffer()      As Byte
    Dim sPostData       As String
 
    Dim sFileName As String
    Dim sURL As String
    Dim bAsync As Boolean
    sFileName = "C:\Users\...\Screenshots\Screenshot.jpg"
    sURL = "https://api.telegram.org/bot0000000000000000/sendPhoto"
    '--- read file
    nFile = FreeFile
    Open sFileName For Binary Access Read As nFile
    If LOF(nFile) > 0 Then
        ReDim baBuffer(0 To LOF(nFile) - 1) As Byte
        Get nFile, , baBuffer
        sPostData = StrConv(baBuffer, vbUnicode)
    End If
    Close nFile
    '--- prepare body
    sPostData = "--" & STR_BOUNDARY & vbCrLf & _
        "Content-Disposition: form-data; name=""chat_id""" & vbCrLf & _
        "000000000" & vbCrLf & _
        "--" & STR_BOUNDARY & vbCrLf & _
        "Content-Disposition: form-data; name=""photo""; filename=""" & Mid$(sFileName, InStrRev(sFileName, "\") + 1) & """" & vbCrLf & _
        "Content-Type: application/octet-stream" & vbCrLf & vbCrLf & _
        strPostData & vbCrLf & _
        "--" & STR_BOUNDARY & "--"

    Debug.Print sPostData

    '--- post
    With CreateObject("Microsoft.XMLHTTP")
        .Open "POST", sURL, bAsync
        .setRequestHeader "Content-Type", "multipart/form-data; boundary=" & STR_BOUNDARY
        .Send pvToByteArray(sPostData)
        If Not bAsync Then
            pvPostFile = .ResponseText
        End If
    End With

End Function

Private Function pvToByteArray(sText As String) As Byte()
    pvToByteArray = StrConv(sText, vbFromUnicode)
End Function

Es reagiert zumindest etwas, ich bekomme folgenden ResponseText:
{"ok":false,"error_code":400,"description":"Bad Request: there is no photo in the request"}

Der Debug.Print oben wirft folgendes aus:
--3fbd04f5-b1ed-4060-99b9-fca7ff59c113
Content-Disposition: form-data; name="chat_id"
675075064
--3fbd04f5-b1ed-4060-99b9-fca7ff59c113
Content-Disposition: form-data; name="photo"; filename="Screenshot.jpg"
Content-Type: application/octet-stream


--3fbd04f5-b1ed-4060-99b9-fca7ff59c113--

Wo liegt der Fehler? Ich denke das Format des POST müsste passen, da ich ja einen sinnvollen Response bekomme. Aber warum lädt er die .jpg nicht mit?
 
Benutz mal im Quelltext einen absolute Pfad für das Foto. Also ersetze /.../ und schau mal was passiert
 
\...\ ersetzt nur irgendwelche Unterordner, die ich hier ersparen wollte. Im Code stehen Sie natürlich drin.

Bsp.: C:\Users\ABC\Screenshots\Screenshot.jpg
 
Ok. Leider kenn ich mich mit VBA nicht aus. Aber wenn die Telegram REST API dir HTTP Status 400 zurück gibt mit der Meldung "kein Foto", so kannst du, wie du auch sagtest, davon ausgehen, dass der Auth Header funktioniert. Versuch, das hier nachzubauen. Ansonsten würde ich mit cURL oder Postman einen erfolgreichen Request bauen, den dann mit WireShark inspizieren und speichern, und dann Schritt für Schritt in VBA diesen Request nachbauen. Schau dir Content-Lenght und Content-Type Header an, schau wo das Foto liegt, wie es formatiert ist, etc.
 
Wireshark wäre etwas übertrieben. Sinnvoll wäre da ein einfacher http proxy den man dazwischen schaltet wie bspw. tcpmon.

Ich denke aber das Problem ist nicht zu wissen, wie der Datenstrom aussehen soll. Denn das ist wirklich ein einfacher multipart/form-data wo es genug Beispiele im Netz gibt. Und der Aufbau sieht jetzt auch nicht so verkehrt aus.

Leider bin ich gar nicht VB versiert...
Code:
  "Content-Disposition: form-data; name=""photo""; filename=""" & Mid$(sFileName, InStrRev(sFileName, "\") + 1) & """" & vbCrLf & _
        "Content-Type: application/octet-stream" & vbCrLf & vbCrLf & _
        strPostData & vbCrLf & _
        "--" & STR_BOUNDARY & "--"

was ist denn in strPostData? die Variable sehe ich nirgends. Normalerweise musst du die Datei als byte array einlesen und unter Content-Type: application/octet-stream ausgeben
Code:
--boundary string
Content-Disposition: form-data; name="photo" filename="bild.png"
Content-Type: application/octet-stream

byte array
 
das mit strPostData habe ich auch gerade gefunden, das ist ein Fehler, das muss sPostData heißen. Deswegen hat er gemeckert, dass es kein Foto gibt.

Jetzt bekomme ich als Rückgabe:
{"ok":false,"error_code":413,"description":"Request Entity Too Large"}

Die Datei ist ca. 5 MB groß, laut Doku sind 10MB ok. Eine kleinere Datei von ca 1MB bringt den gleichen Fehler. Außerdem hat die große Datei per cURL üüber die cmd.exe ja funktioniert.

wie genau soll ich das Byte Array in den String bekommen? Das ist ja erstmal nicht kompatibel
Konvertiert wird aktuell der gesamte String mit
.Send pvToByteArray(sPostData)
 
Zuletzt bearbeitet:
Wow, ich glaube ich habs geschafft! Mehr oder weniger durch Googlen und tausend mal das gleiche lesen und mal hier was getestet, mal da.

Ich glaube der Parameter für die chat_id muss oben in die URL mit rein, nicht unten in den PostData-String.
Ich vermute dass nur der Teil mit dem Bild in ein ByteArray konvertiert werden darf.

Ich werd das ganze im Laufe der Woche mal in mein Programm komplett mit einbauen und melde mich dann nochmal. Danke soweit schonmal für eure Hilfe!

Aktuell sieht es so aus:
Code:
Public Function pvPostFile()
'Private Function pvPostFile(sUrl As String, sFileName As String, Optional ByVal bAsync As Boolean) As String
    Const STR_BOUNDARY  As String = "3fbd04f5-b1ed-4060-99b9-fca7ff59c113"
    Dim nFile           As Integer
    Dim baBuffer()      As Byte
    Dim sPostData       As String
 
    Dim sFileName As String
    Dim sURL As String
    Dim bAsync As Boolean
    Dim lngI As Long
    sFileName = "C:\Users\ABC\Screenshot.jpg"
    sURL = "https://api.telegram.org/bot00000000/sendPhoto?chat_id=000000"
    '--- read file
    nFile = FreeFile
    Open sFileName For Binary Access Read As nFile
    If LOF(nFile) > 0 Then
        ReDim baBuffer(0 To LOF(nFile) - 1) As Byte
        Get nFile, , baBuffer
        sPostData = StrConv(baBuffer, vbUnicode)
    End If
    Close nFile
    
    '--- prepare body
    sPostData = "--" & STR_BOUNDARY & vbCrLf & _
        "Content-Disposition: form-data; name=""photo""; filename=""" & Mid$(sFileName, InStrRev(sFileName, "\") + 1) & """" & vbCrLf & _
        "Content-Type: application/octet-stream" & vbCrLf & vbCrLf & _
        sPostData & vbCrLf & _
        "--" & STR_BOUNDARY & "--"


    '--- post
    With CreateObject("Microsoft.XMLHTTP")
        .Open "POST", sURL, bAsync
        .setRequestHeader "Content-Type", "multipart/form-data; boundary=" & STR_BOUNDARY
        .Send pvToByteArray(sPostData)
        If Not bAsync Then
            pvPostFile = .ResponseText
        End If
    End With

End Function

Private Function pvToByteArray(sText As String) As Byte()
    pvToByteArray = StrConv(sText, vbFromUnicode)
End Function
 
  • Gefällt mir
Reaktionen: nerdalicious
So, hat ein bisschen länger gedauert, aber ja es funktioniert jetzt. Der Code von oben passt soweit. Generell muss man sagen, dass sämtliche Parameter mit in die URL kommen müssen. Das gilt auch für die Markups im JSON Format. Nur das Foto selbst wird über sPostData als Bytes übertragen
 
Zurück
Oben