package telegoutil import ( "bytes" "crypto/hmac" "crypto/sha256" "encoding/hex" "errors" "io" "net/url" "strings" "github.com/mymmrac/telego" ta "github.com/mymmrac/telego/telegoapi" ) // namedReaderImpl represents the simplest implementation of [ta.NamedReader] type namedReaderImpl struct { reader io.Reader name string } func (r namedReaderImpl) Read(p []byte) (n int, err error) { return r.reader.Read(p) } func (r namedReaderImpl) Name() string { return r.name } // NameReader "names" [io.Reader] and returns valid [ta.NamedReader] func NameReader(reader io.Reader, name string) ta.NamedReader { return namedReaderImpl{ reader: reader, name: name, } } // NameBytes "names" slice of bytes and returns valid [ta.NamedReader] func NameBytes(data []byte, name string) ta.NamedReader { return namedReaderImpl{ reader: bytes.NewReader(data), name: name, } } // UpdateProcessor allows you to process updates and still use updates chan. // New updates chan will be closed when the original chan is closed. // Warning: Deep copy of update is passed, [telego.Update.Clone] method can panic, please read its comment. func UpdateProcessor(updates <-chan telego.Update, buffer uint, processor func(update telego.Update) telego.Update, ) <-chan telego.Update { processedUpdates := make(chan telego.Update, buffer) go func() { defer close(processedUpdates) for update := range updates { processedUpdates <- processor(update.Clone()) } }() return processedUpdates } // WebAppSecret represents secret used to hash web app data const WebAppSecret = "WebAppData" // Web app data query names const ( WebAppQueryID = "query_id" WebAppUser = "user" WebAppReceiver = "receiver" WebAppChat = "chat" WebAppStartParam = "start_param" WebAppCanSendAfter = "can_send_after" WebAppAuthDate = "auth_date" WebAppHash = "hash" ) // ValidateWebAppData validates the integrity of value provided by `window.Telegram.WebApp.initData` from web app and // returns [url.Values] containing all fields that were provided // More info: https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app func ValidateWebAppData(botToken string, data string) (url.Values, error) { appData, err := url.ParseQuery(data) if err != nil { return nil, errors.New("telego: parse query: bad data") } hash := appData.Get(WebAppHash) if hash == "" { return nil, errors.New("telego: no hash found") } appData.Del(WebAppHash) // Can't return error because [url.Values.Encode] method always inescapable //nolint:errcheck appDataToCheck, _ := url.QueryUnescape(strings.ReplaceAll(appData.Encode(), "&", "\n")) secretKey := hmacHash([]byte(botToken), []byte(WebAppSecret)) if hex.EncodeToString(hmacHash([]byte(appDataToCheck), secretKey)) != hash { return nil, errors.New("telego: invalid hash") } appData.Add(WebAppHash, hash) return appData, nil } // Login widget data query names const ( LoginWidgetID = "id" LoginWidgetFirstName = "first_name" LoginWidgetLastName = "last_name" LoginWidgetUsername = "username" LoginWidgetPhotoURL = "photo_url" LoginWidgetAuthDate = "auth_date" LoginWidgetHash = "hash" ) // ValidateLoginWidgetData validates the integrity of value provided by Telegram Login Widget and // returns [url.Values] containing all fields that were provided // More info: https://core.telegram.org/widgets/login#checking-authorization func ValidateLoginWidgetData(botToken string, data string) (url.Values, error) { appData, err := url.ParseQuery(data) if err != nil { return nil, errors.New("telego: parse query: bad data") } hash := appData.Get(LoginWidgetHash) if hash == "" { return nil, errors.New("telego: no hash found") } appData.Del(LoginWidgetHash) // Can't return error because [url.Values.Encode] method always inescapable //nolint:errcheck appDataToCheck, _ := url.QueryUnescape(strings.ReplaceAll(appData.Encode(), "&", "\n")) secretKey := sha256.Sum256([]byte(botToken)) if hex.EncodeToString(hmacHash([]byte(appDataToCheck), secretKey[:])) != hash { return nil, errors.New("telego: invalid hash") } appData.Add(LoginWidgetHash, hash) return appData, nil } // hmacHash hashes data with a provided key using HMAC and SHA256 func hmacHash(data, key []byte) []byte { h := hmac.New(sha256.New, key) _, _ = h.Write(data) return h.Sum(nil) }