From a6c1dce4e06207d256ecd112d5df06aef5adf559 Mon Sep 17 00:00:00 2001 From: Ian Byrd Date: Thu, 10 Nov 2016 19:52:45 +0200 Subject: [PATCH 1/3] Replace IQR*.MarshalJSON with InlineQueryResultBase, fixes #75. This commit gets rid of multiple redundant copies of MarhsalJSON() method present for all existing IQR. It does so by introducing a new struct called InlineQueryResultBase, which takes care of new Type (for JSON) and existing duplicated ID field. InlineQueryResults is a new helper wrapper for []IQR slices. It implements MarshalJSON, which makes sure IQR.ID and IQR.Type are set properly (by computing a hash and infering IQR type). --- inline.go | 67 +++++++-- inline_types.go | 387 ++++-------------------------------------------- 2 files changed, 88 insertions(+), 366 deletions(-) diff --git a/inline.go b/inline.go index d6b7900..ab75c52 100644 --- a/inline.go +++ b/inline.go @@ -1,6 +1,8 @@ package telebot import ( + "encoding/json" + "fmt" "hash/fnv" "strconv" @@ -41,7 +43,7 @@ type QueryResponse struct { QueryID string `json:"inline_query_id"` // The results for the inline query. - Results []InlineQueryResult `json:"results"` + Results InlineQueryResults `json:"results"` // (Optional) The maximum amount of time in seconds that the result // of the inline query may be cached on the server. @@ -70,18 +72,63 @@ type QueryResponse struct { // InlineQueryResult represents one result of an inline query. type InlineQueryResult interface { - MarshalJSON() ([]byte, error) - id() (string, error) + GetID() string + SetID(string) } -// hashInlineQueryResult calculates the 64-bit FNV-1 hash of an -// inline query result. -func hashInlineQueryResult(result InlineQueryResult) (string, error) { - hash, err := hashstructure.Hash(result, inlineQueryHashOptions) - if err != nil { - return "", err +// InlineQueryResults is a slice wrapper for convenient marshalling. +type InlineQueryResults []InlineQueryResult + +func (results *InlineQueryResults) MarshalJSON() ([]byte, error) { + for i, result := range *results { + if result.GetID() == "" { + hash, err := hashstructure.Hash(result, inlineQueryHashOptions) + if err != nil { + return nil, fmt.Errorf("telebot: can't hash IQR #%d: %s", + i, err) + } + + result.SetID(strconv.FormatUint(hash, 16)) + } + + if err := infereIQR(result); err != nil { + return nil, fmt.Errorf("telebot: can't infere type of IQR #%d: %s", + i, err) + } } - return strconv.FormatUint(hash, 16), nil + + return json.Marshal([]InlineQueryResult(*results)) +} + +func infereIQR(result InlineQueryResult) error { + switch r := result.(type) { + case *InlineQueryResultArticle: + r.Type = "article" + case *InlineQueryResultAudio: + r.Type = "audio" + case *InlineQueryResultContact: + r.Type = "contact" + case *InlineQueryResultDocument: + r.Type = "document" + case *InlineQueryResultGif: + r.Type = "gif" + case *InlineQueryResultLocation: + r.Type = "location" + case *InlineQueryResultMpeg4Gif: + r.Type = "mpeg4_gif" + case *InlineQueryResultPhoto: + r.Type = "photo" + case *InlineQueryResultVenue: + r.Type = "venue" + case *InlineQueryResultVideo: + r.Type = "video" + case *InlineQueryResultVoice: + r.Type = "voice" + default: + return fmt.Errorf("%T is not an IQR", result) + } + + return nil } // Result is a deprecated type, superseded by InlineQueryResult. diff --git a/inline_types.go b/inline_types.go index f1b8ac5..2b54872 100644 --- a/inline_types.go +++ b/inline_types.go @@ -1,17 +1,31 @@ package telebot -import ( - "encoding/json" -) - -// InlineQueryResultArticle represents a link to an article or web page. -// See also: https://core.telegram.org/bots/api#inlinequeryresultarticle -type InlineQueryResultArticle struct { +// InlineQueryResultBase must be embedded into all IQRs. +type InlineQueryResultBase struct { // Unique identifier for this result, 1-64 Bytes. // If left unspecified, a 64-bit FNV-1 hash will be calculated // from the other fields and used automatically. ID string `json:"id",hash:"ignore"` + // Ignore. This field gets set automatically. + Type string `json:"type",hash:"ignore"` +} + +// GetID is part of IQRBase's implementation of IQR interface. +func (result *InlineQueryResultBase) GetID() string { + return result.ID +} + +// GetID is part of IQRBase's implementation of IQR interface. +func (result *InlineQueryResultBase) SetID(id string) { + result.ID = id +} + +// InlineQueryResultArticle represents a link to an article or web page. +// See also: https://core.telegram.org/bots/api#inlinequeryresultarticle +type InlineQueryResultArticle struct { + InlineQueryResultBase + // Title of the result. Title string `json:"title"` @@ -44,39 +58,9 @@ type InlineQueryResultArticle struct { ThumbHeight int `json:"thumb_height,omitempty"` } -func (r *InlineQueryResultArticle) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultArticle - - id, err := r.id() - if err != nil { - return nil, err - } - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "article", - }) -} - -func (r *InlineQueryResultArticle) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultAudio represents a link to an mp3 audio file. type InlineQueryResultAudio struct { - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // A valid URL for the audio file. AudioURL string `json:"audio_url"` @@ -97,41 +81,10 @@ type InlineQueryResultAudio struct { InputMessageContent InputMessageContent `json:"input_message_content,omitempty"` } -func (r *InlineQueryResultAudio) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultAudio - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "audio", - }) -} - -func (r *InlineQueryResultAudio) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultContact represents a contact with a phone number. // See also: https://core.telegram.org/bots/api#inlinequeryresultcontact type InlineQueryResultContact struct { - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // Contact's phone number. PhoneNumber string `json:"phone_number"` @@ -158,41 +111,10 @@ type InlineQueryResultContact struct { ThumbHeight int `json:"thumb_height,omitempty"` } -func (r *InlineQueryResultContact) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultContact - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "contact", - }) -} - -func (r *InlineQueryResultContact) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultDocument represents a link to a file. // See also: https://core.telegram.org/bots/api#inlinequeryresultdocument type InlineQueryResultDocument struct { - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // Title for the result. Title string `json:"title"` @@ -226,41 +148,10 @@ type InlineQueryResultDocument struct { ThumbHeight int `json:"thumb_height,omitempty"` } -func (r *InlineQueryResultDocument) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultDocument - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "document", - }) -} - -func (r *InlineQueryResultDocument) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultGif represents a link to an animated GIF file. // See also: https://core.telegram.org/bots/api#inlinequeryresultgif type InlineQueryResultGif struct { - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // A valid URL for the GIF file. File size must not exceed 1MB. GifURL string `json:"gif_url"` @@ -287,42 +178,10 @@ type InlineQueryResultGif struct { InputMessageContent InputMessageContent `json:"input_message_content,omitempty"` } -func (r *InlineQueryResultGif) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultGif - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "gif", - }) -} - -func (r *InlineQueryResultGif) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultLocation represents a location on a map. // See also: https://core.telegram.org/bots/api#inlinequeryresultlocation type InlineQueryResultLocation struct { - - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // Latitude of the location in degrees. Latitude float32 `json:"latitude"` @@ -349,42 +208,11 @@ type InlineQueryResultLocation struct { ThumbHeight int `json:"thumb_height,omitempty"` } -func (r *InlineQueryResultLocation) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultLocation - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "location", - }) -} - -func (r *InlineQueryResultLocation) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultMpeg4Gif represents a link to a video animation // (H.264/MPEG-4 AVC video without sound). // See also: https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif type InlineQueryResultMpeg4Gif struct { - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // A valid URL for the MP4 file. URL string `json:"mpeg4_url"` @@ -411,41 +239,10 @@ type InlineQueryResultMpeg4Gif struct { InputMessageContent InputMessageContent `json:"input_message_content,omitempty"` } -func (r *InlineQueryResultMpeg4Gif) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultMpeg4Gif - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "mpeg4_gif", - }) -} - -func (r *InlineQueryResultMpeg4Gif) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultPhoto represents a link to a photo. // See also: https://core.telegram.org/bots/api#inlinequeryresultphoto type InlineQueryResultPhoto struct { - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // A valid URL of the photo. Photo must be in jpeg format. // Photo size must not exceed 5MB. @@ -476,41 +273,10 @@ type InlineQueryResultPhoto struct { InputMessageContent InputMessageContent `json:"input_message_content,omitempty"` } -func (r *InlineQueryResultPhoto) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultPhoto - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "photo", - }) -} - -func (r *InlineQueryResultPhoto) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultVenue represents a venue. // See also: https://core.telegram.org/bots/api#inlinequeryresultvenue type InlineQueryResultVenue struct { - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // Latitude of the venue location in degrees. Latitude float32 `json:"latitude"` @@ -543,42 +309,11 @@ type InlineQueryResultVenue struct { ThumbHeight int `json:"thumb_height,omitempty"` } -func (r *InlineQueryResultVenue) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultVenue - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "venue", - }) -} - -func (r *InlineQueryResultVenue) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultVideo represents a link to a page containing an embedded // video player or a video file. // See also: https://core.telegram.org/bots/api#inlinequeryresultvideo type InlineQueryResultVideo struct { - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // A valid URL for the embedded video player or video file. VideoURL string `json:"video_url"` @@ -614,43 +349,11 @@ type InlineQueryResultVideo struct { InputMessageContent InputMessageContent `json:"input_message_content,omitempty"` } -func (r *InlineQueryResultVideo) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultVideo - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "video", - }) -} - -func (r *InlineQueryResultVideo) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} - // InlineQueryResultVoice represents a link to a voice recording in a // .ogg container encoded with OPUS. // See also: https://core.telegram.org/bots/api#inlinequeryresultvoice type InlineQueryResultVoice struct { - - // Unique identifier for this result, 1-64 Bytes. - // If left unspecified, a 64-bit FNV-1 hash will be calculated - // from the other fields and used automatically. - ID string `json:"id",hash:"ignore"` + InlineQueryResultBase // A valid URL for the voice recording. VoiceURL string `json:"voice_url"` @@ -667,31 +370,3 @@ type InlineQueryResultVoice struct { // Optional. Content of the message to be sent instead of the audio. InputMessageContent InputMessageContent `json:"input_message_content,omitempty"` } - -func (r *InlineQueryResultVoice) MarshalJSON() ([]byte, error) { - // avoiding endless self-recursion - type wrapper InlineQueryResultVoice - - id, err := r.id() - if err != nil { - return nil, err - } - - return json.Marshal(struct { - wrapper - Type string `json:"type"` - ID string `json:"id",hash:"ignore"` - }{ - wrapper: wrapper(*r), - ID: id, - Type: "voice", - }) -} - -func (r *InlineQueryResultVoice) id() (string, error) { - if r.ID == "" { - return hashInlineQueryResult(r) - } - - return r.ID, nil -} From acbbb524dcaf294a2cab99c002b5c05b1b430a45 Mon Sep 17 00:00:00 2001 From: Ian Byrd Date: Thu, 10 Nov 2016 20:05:52 +0200 Subject: [PATCH 2/3] Documentation for newly introduced methods. --- inline.go | 4 ++++ inline_types.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/inline.go b/inline.go index ab75c52..6cee961 100644 --- a/inline.go +++ b/inline.go @@ -79,6 +79,10 @@ type InlineQueryResult interface { // InlineQueryResults is a slice wrapper for convenient marshalling. type InlineQueryResults []InlineQueryResult +// MarshalJSON makes sure IQRs have proper IDs and Type variables set. +// +// If ID of some result appears empty, it gets set to a new hash. +// JSON-specific Type gets infered from the actual (specific) IQR type. func (results *InlineQueryResults) MarshalJSON() ([]byte, error) { for i, result := range *results { if result.GetID() == "" { diff --git a/inline_types.go b/inline_types.go index 2b54872..9cf88ae 100644 --- a/inline_types.go +++ b/inline_types.go @@ -16,7 +16,7 @@ func (result *InlineQueryResultBase) GetID() string { return result.ID } -// GetID is part of IQRBase's implementation of IQR interface. +// SetID is part of IQRBase's implementation of IQR interface. func (result *InlineQueryResultBase) SetID(id string) { result.ID = id } From 7c002850b1bc0084590d94cf32b2b356bc206f26 Mon Sep 17 00:00:00 2001 From: Ian Byrd Date: Thu, 10 Nov 2016 22:10:37 +0200 Subject: [PATCH 3/3] Typo infereIQR -> inferIQR. --- inline.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inline.go b/inline.go index 6cee961..5318922 100644 --- a/inline.go +++ b/inline.go @@ -95,8 +95,8 @@ func (results *InlineQueryResults) MarshalJSON() ([]byte, error) { result.SetID(strconv.FormatUint(hash, 16)) } - if err := infereIQR(result); err != nil { - return nil, fmt.Errorf("telebot: can't infere type of IQR #%d: %s", + if err := inferIQR(result); err != nil { + return nil, fmt.Errorf("telebot: can't infer type of IQR #%d: %s", i, err) } } @@ -104,7 +104,7 @@ func (results *InlineQueryResults) MarshalJSON() ([]byte, error) { return json.Marshal([]InlineQueryResult(*results)) } -func infereIQR(result InlineQueryResult) error { +func inferIQR(result InlineQueryResult) error { switch r := result.(type) { case *InlineQueryResultArticle: r.Type = "article"