/* * Echotron * Copyright (C) 2018 The Echotron Contributors * * Echotron is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Echotron is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package echotron import ( "errors" "fmt" "net/url" "os" "path/filepath" "strconv" ) // content contains a file's name, its type and its data. type content struct { fname string ftype string fdata []byte } func check(r APIResponse) error { if b := r.Base(); !b.Ok { return &APIError{code: b.ErrorCode, desc: b.Description} } return nil } func processMedia(media, thumbnail InputFile) (im mediaEnvelope, cnt []content, err error) { switch { case media.ref() != "": im.media = media.ref() case media.path != "" && len(media.content) == 0: if media.content, media.path, err = readFile(media); err != nil { return } fallthrough case media.path != "" && len(media.content) > 0: cnt = append(cnt, content{media.path, media.path, media.content}) im.media = fmt.Sprintf("attach://%s", media.path) } switch { // Needed by live photos, whose static photo is passed as the thumbnail. case thumbnail.ref() != "": im.thumbnail = thumbnail.ref() case thumbnail.path != "" && len(thumbnail.content) == 0: if thumbnail.content, thumbnail.path, err = readFile(thumbnail); err != nil { return } fallthrough case thumbnail.path != "" && len(thumbnail.content) > 0: cnt = append(cnt, content{thumbnail.path, thumbnail.path, thumbnail.content}) im.thumbnail = fmt.Sprintf("attach://%s", thumbnail.path) } return } func processSticker(sticker InputFile) (se stickerEnvelope, cnt []content, err error) { switch { case sticker.id != "": se.Sticker = sticker.id case sticker.url != "": se.Sticker = sticker.url case sticker.path != "" && len(sticker.content) == 0: if sticker.content, sticker.path, err = readFile(sticker); err != nil { return } fallthrough case sticker.path != "" && len(sticker.content) > 0: cnt = append(cnt, content{sticker.path, sticker.path, sticker.content}) se.Sticker = fmt.Sprintf("attach://%s", sticker.path) } return } func readFile(im InputFile) (content []byte, path string, err error) { content, err = os.ReadFile(im.path) if err != nil { return } path = filepath.Base(im.path) return } func toContent(ftype string, f InputFile) (content, error) { if f.path != "" && len(f.content) == 0 { var err error if f.content, f.path, err = readFile(f); err != nil { return content{}, err } } return content{f.path, ftype, f.content}, nil } // errNestedUpload is returned when a local file is used as media nested in another parameter. var errNestedUpload = errors.New("echotron: media nested in polls and rich messages must be a file ID or a URL") // inlineMedia prepares a media to be serialised inside another JSON parameter. // It returns nil if m is nil, so that the media can be omitted. // Nested media only accept file IDs and URLs, uploading local files // would need multipart plumbing through every parameter that embeds media. func inlineMedia(m InputMedia) (*mediaEnvelope, error) { if m == nil { return nil, nil } media, thumbnail := m.media(), m.thumbnail() if (media.ref() == "" && media.path != "") || (thumbnail.ref() == "" && thumbnail.path != "") { return nil, errNestedUpload } return &mediaEnvelope{ InputMedia: m, media: media.ref(), thumbnail: thumbnail.ref(), }, nil } func toInputMedia(media []GroupableInputMedia) (ret []InputMedia) { ret = make([]InputMedia, len(media)) for i, v := range media { ret[i] = v } return ret } func joinURL(base, endpoint string, vals url.Values) (addr string, err error) { addr, err = url.JoinPath(base, endpoint) if err != nil { return } if vals != nil { if queries := vals.Encode(); queries != "" { addr = fmt.Sprintf("%s?%s", addr, queries) } } return } func processProfilePhoto(photo InputProfilePhoto) (env profilePhotoEnvelope, cnt []content, err error) { env.content = photo file := photo.profilePhotoFile() switch { case file.id != "": env.ref = file.id case file.url != "": env.ref = file.url case file.path != "" && len(file.content) == 0: if file.content, file.path, err = readFile(file); err != nil { return } fallthrough case file.path != "" && len(file.content) > 0: cnt = append(cnt, content{file.path, file.path, file.content}) env.ref = fmt.Sprintf("attach://%s", file.path) } return } func processStoryContent(sc InputStoryContent) (env storyContentEnvelope, cnt []content, err error) { env.content = sc file := sc.storyContentFile() switch { case file.id != "": env.ref = file.id case file.url != "": env.ref = file.url case file.path != "" && len(file.content) == 0: if file.content, file.path, err = readFile(file); err != nil { return } fallthrough case file.path != "" && len(file.content) > 0: cnt = append(cnt, content{file.path, file.path, file.content}) env.ref = fmt.Sprintf("attach://%s", file.path) } return } // withParam appends an escaped query parameter to a URL that already has a query string. func withParam(u, key, val string) string { return u + "&" + key + "=" + url.QueryEscape(val) } func itoa(i int64) string { return strconv.FormatInt(i, 10) } func ftoa(f float64) string { return strconv.FormatFloat(f, 'f', -1, 64) } func btoa(b bool) string { return strconv.FormatBool(b) }