197 lines
5.9 KiB
Go
197 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"html"
|
|
"io"
|
|
"log"
|
|
"mime"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/textproto"
|
|
"strings"
|
|
"time"
|
|
|
|
telegram "github.com/go-telegram-bot-api/telegram-bot-api"
|
|
filetype "gopkg.in/h2non/filetype.v1"
|
|
)
|
|
|
|
func (this *NTFServer) ContentedEnabled() bool {
|
|
return this.contentedMaxBytes > 0
|
|
}
|
|
|
|
func (this *NTFServer) uploadAsyncComplete(userID int64, typeName string, conUrl string, err error, textPrefix string) {
|
|
if err != nil {
|
|
log.Printf("Upload failed for %s: %s", typeName, err.Error())
|
|
this.callOnMainThread <- func() {
|
|
this.GroupChatSayHTML("<i>Can't upload " + typeName + " for native users: " + html.EscapeString(err.Error()) + "</i>")
|
|
}
|
|
return
|
|
}
|
|
|
|
// Upload success
|
|
if len(textPrefix) > 0 {
|
|
textPrefix = strings.Trim(textPrefix, ` `) + " "
|
|
}
|
|
|
|
this.callOnMainThread <- func() {
|
|
// n.b. this will fail if the user has disconnected by the time the upload completed
|
|
this.HubSay(userID, textPrefix+conUrl)
|
|
}
|
|
|
|
}
|
|
|
|
func (this *NTFServer) ContentedUploadFallbackSync(FileID string, FileSize int64, thumb *telegram.PhotoSize) (string, error) {
|
|
if FileSize < this.contentedMaxBytes {
|
|
return this.ContentedUploadSync(FileID, FileSize)
|
|
|
|
} else if thumb != nil && int64(thumb.FileSize) < this.contentedMaxBytes {
|
|
return this.ContentedUploadSync(thumb.FileID, int64(thumb.FileSize))
|
|
|
|
} else {
|
|
return "", errors.New("File too big and/or bad thumbnail")
|
|
|
|
}
|
|
}
|
|
|
|
func (this *NTFServer) ContentedUploadBestSync(thumbs []telegram.PhotoSize) (string, error) {
|
|
|
|
bestKnownIdx := -1
|
|
bestKnownMpx := int64(-1)
|
|
for idx, img := range thumbs {
|
|
if int64(img.FileSize) > this.contentedMaxBytes {
|
|
continue // no good
|
|
}
|
|
|
|
// Highest total pixel count
|
|
mpx := int64(img.Width) * int64(img.Height)
|
|
if mpx > bestKnownMpx {
|
|
bestKnownIdx = idx
|
|
bestKnownMpx = mpx
|
|
}
|
|
}
|
|
|
|
if bestKnownIdx == -1 {
|
|
return "", errors.New("The file was too large for the image host server")
|
|
}
|
|
|
|
return this.ContentedUploadSync(thumbs[bestKnownIdx].FileID, int64(thumbs[bestKnownIdx].FileSize))
|
|
}
|
|
|
|
func (this *NTFServer) ContentedUploadSync(fileId string, expectSizeBytes int64) (string, error) {
|
|
|
|
// If file fits under size limit, take it
|
|
if expectSizeBytes > this.contentedMaxBytes {
|
|
return "", errors.New("The file was too large for the image host server (and no smaller thumbnail is available)")
|
|
}
|
|
|
|
// Prepare to download the file from telegram
|
|
url, err := this.bot.GetFileDirectURL(fileId)
|
|
if err != nil {
|
|
return "", fmt.Errorf("TelegramGetFileDirectURL: %s", err.Error())
|
|
}
|
|
|
|
if this.verbose {
|
|
log.Printf("Downloading telegram file %s", url)
|
|
}
|
|
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return "", fmt.Errorf("HttpGet: %s", err.Error())
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if this.verbose {
|
|
log.Printf("Downloaded telegram file response headers: %#v\n", resp.Header)
|
|
}
|
|
|
|
// Download
|
|
fileBuff := bytes.Buffer{}
|
|
_, err = io.CopyN(&fileBuff, resp.Body, expectSizeBytes) // CopyN asserts either err!=nil, or copiedBytes == expectSizeBytes. So we're safe
|
|
if err != nil {
|
|
return "", fmt.Errorf("CopyN: %s", err.Error())
|
|
}
|
|
|
|
// Determine a suitable pseudo-filename from the telegram content-type
|
|
// Telegram seems to always supply application/octet-stream
|
|
// If Telegram gives us something else, trust it; but otherwise, we can probably do better
|
|
|
|
fileName := fmt.Sprintf("telegram-upload-%d", time.Now().Unix())
|
|
fileContentType := resp.Header.Get("Content-Type") // is it blank? who knows?
|
|
|
|
if fileContentType == `application/octet-stream` || fileContentType == `` {
|
|
|
|
// Autodetect something better, if possible
|
|
if contentTypeMatches, err := filetype.Match(fileBuff.Bytes()); err == nil {
|
|
fileContentType = contentTypeMatches.MIME.Value
|
|
fileName += `.` + contentTypeMatches.Extension // no leading period
|
|
}
|
|
|
|
} else {
|
|
|
|
// We were supplied a good mime type, just need to find a good file extension
|
|
if fileExts, err := mime.ExtensionsByType(fileContentType); err == nil && len(fileExts) > 0 {
|
|
fileName += fileExts[0] // Just pick the first one. They all contain a leading period
|
|
}
|
|
|
|
}
|
|
|
|
// Contented uploads are in multipart mime format
|
|
uploadBody := bytes.Buffer{}
|
|
formValues := multipart.NewWriter(&uploadBody)
|
|
|
|
// Adapted from (mime/multipart)Writer.CreateFormFile to support a custom interior content type
|
|
multipartFileHeader := make(textproto.MIMEHeader)
|
|
multipartFileHeader.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "f", fileName))
|
|
multipartFileHeader.Set("Content-Type", fileContentType)
|
|
filePart, err := formValues.CreatePart(multipartFileHeader)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
_, err = io.CopyN(filePart, bytes.NewReader(fileBuff.Bytes()), expectSizeBytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Upload
|
|
err = formValues.Close()
|
|
if err != nil {
|
|
return "", fmt.Errorf("Close: %s", err.Error())
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", this.config.ContentedURL+"upload", &uploadBody)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
req.Header.Set("Content-Type", formValues.FormDataContentType()) // it's "multipart/form-data; boundary=xxxx" with a magic string
|
|
uploadResp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Upload failed: %s", err.Error())
|
|
}
|
|
|
|
defer uploadResp.Body.Close()
|
|
|
|
// Read back the response from contented
|
|
// Should be a 200 error code, JSON content type, array of strings of shortcodes to the uploaded file(s)
|
|
if uploadResp.StatusCode != 200 {
|
|
return "", fmt.Errorf("Unexpected HTTP %d response back from file hosting server", uploadResp.StatusCode)
|
|
}
|
|
|
|
shortCodes := make([]string, 0, 1)
|
|
err = json.NewDecoder(uploadResp.Body).Decode(&shortCodes)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Decoding response from file hosting server: %s", err.Error())
|
|
}
|
|
|
|
if len(shortCodes) != 1 || len(shortCodes[0]) == 0 {
|
|
return "", errors.New("Unexpected blank response back from file hosting server")
|
|
}
|
|
|
|
// DONE
|
|
return this.config.ContentedURL + "p/" + shortCodes[0], nil
|
|
}
|