Repository: mr-linch/go-tg
Pull request #144 (2024-03-12 15:09:37)

add reactions enum

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/mr-linch/go-tg/pull/144?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sasha+Savchuk) Report
Attention: Patch coverage is `60.00000%` with `2 lines` in your changes are missing coverage. Please review.
> Project coverage is 94.53%. Comparing base [(`5cf133d`)](https://app.codecov.io/gh/mr-linch/go-tg/commit/5cf133deaca3a4c39a5fb28e1fe85f25f6638cc5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sasha+Savchuk) to head [(`aa53e36`)](https://app.codecov.io/gh/mr-linch/go-tg/pull/144?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sasha+Savchuk).

| [Files](https://app.codecov.io/gh/mr-linch/go-tg/pull/144?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sasha+Savchuk) | Patch % | Lines |
|---|---|---|
| [types\_gen\_ext.go](https://app.codecov.io/gh/mr-linch/go-tg/pull/144?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sasha+Savchuk#diff-dHlwZXNfZ2VuX2V4dC5nbw==) | 50.00% | [2 Missing :warning: ](https://app.codecov.io/gh/mr-linch/go-tg/pull/144?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sasha+Savchuk) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #144      +/-   ##
==========================================
- Coverage   94.61%   94.53%   -0.08%     
==========================================
  Files          21       21              
  Lines        2320     2324       +4     
==========================================
+ Hits         2195     2197       +2     
- Misses         85       87       +2     
  Partials       40       40              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/mr-linch/go-tg/pull/144?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sasha+Savchuk).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Sasha+Savchuk).


Patch:
diff --git a/examples/echo-bot/main.go b/examples/echo-bot/main.go
index fcfb275..bf65780 100644
--- a/examples/echo-bot/main.go
+++ b/examples/echo-bot/main.go
@@ -4,6 +4,7 @@ package main
 import (
 	"context"
 	"fmt"
+	"math/rand"
 	"regexp"
 	"time"
 
@@ -52,14 +53,35 @@ func main() {
 			)).DoVoid(ctx)
 
 		}, tgb.Regexp(regexp.MustCompile(`(?mi)(go|golang|gopher)[$\s+]?`))).
+		Message(func(ctx context.Context, mu *tgb.MessageUpdate) error {
+			// react to replied message with random reaction
+
+			msg := mu.ReplyToMessage
+
+			if msg == nil {
+				return mu.Update.Reply(ctx, mu.Answer("Reply to a message to get a reaction."))
+			}
+
+			reaction := tg.ReactionTypeEmojiAll[rand.Int()%len(tg.ReactionTypeEmojiAll)]
+
+			return mu.Update.Reply(ctx, mu.React(reaction).IsBig(true))
+		}, tgb.Command("react")).
 		Message(func(ctx context.Context, msg *tgb.MessageUpdate) error {
 			// handle other messages
 			return msg.Update.Reply(ctx, msg.Copy(msg.Chat))
 		}).
 		MessageReaction(func(ctx context.Context, reaction *tgb.MessageReactionUpdate) error {
 			// sets same reaction to the message
-			answer := tg.NewSetMessageReactionCall(reaction.Chat, reaction.MessageID).Reaction(reaction.NewReaction)
+			answer := tg.NewSetMessageReactionCall(reaction.Chat, reaction.MessageID).
+				Reaction(reaction.NewReaction)
 			return reaction.Update.Reply(ctx, answer)
 		}),
+
+		tg.WithClientInterceptors(
+			tg.NewInterceptorMethodFilter(
+				tg.NewInterceptorDefaultParseMethod(tg.HTML),
+				"sendMessage",
+			),
+		),
 	)
 }
diff --git a/interceptors.go b/interceptors.go
index e4e3ef2..2082193 100644
--- a/interceptors.go
+++ b/interceptors.go
@@ -202,6 +202,6 @@ func NewInterceptorMethodFilter(interceptor Interceptor, methods ...string) Inte
 			return interceptor(ctx, req, dst, invoker)
 		}
 
-		return nil
+		return invoker(ctx, req, dst)
 	}
 }
diff --git a/reactions.go b/reactions.go
new file mode 100644
index 0000000..31fa214
--- /dev/null
+++ b/reactions.go
@@ -0,0 +1,229 @@
+package tg
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// ReactionType it's type for describe content of Reaction.
+// It can be one of:
+//   - [ReactionTypeEmoji]
+//   - [ReactionTypeCustomEmoji]
+type ReactionType struct {
+	Emoji       *ReactionTypeEmoji
+	CustomEmoji *ReactionTypeCustomEmoji
+}
+
+// NewReactionTypeEmoji returns ReactionType with emoji subtype.
+func NewReactionTypeEmoji(emoji string) ReactionType {
+	return ReactionType{Emoji: &ReactionTypeEmoji{Emoji: emoji}}
+}
+
+// NewReactionTypeCustomEmoji returns ReactionType with custom emoji subtype.
+func NewReactionTypeCustomEmoji(id string) ReactionType {
+	return ReactionType{CustomEmoji: &ReactionTypeCustomEmoji{CustomEmojiID: id}}
+}
+
+func (reaction ReactionType) MarshalJSON() ([]byte, error) {
+	switch {
+	case reaction.Emoji != nil:
+		reaction.Emoji.Type = "emoji"
+		return json.Marshal(reaction.Emoji)
+	case reaction.CustomEmoji != nil:
+		reaction.CustomEmoji.Type = "custom_emoji"
+		return json.Marshal(reaction.CustomEmoji)
+	default:
+		return nil, fmt.Errorf("unknown ReactionType type")
+	}
+}
+
+func (reaction *ReactionType) UnmarshalJSON(v []byte) error {
+	var partial struct {
+		Type string `json:"type"`
+	}
+
+	if err := json.Unmarshal(v, &partial); err != nil {
+		return fmt.Errorf("unmarshal ReactionType partial: %w", err)
+	}
+
+	switch partial.Type {
+	case "emoji":
+		reaction.Emoji = &ReactionTypeEmoji{}
+		return json.Unmarshal(v, reaction.Emoji)
+	case "custom_emoji":
+		reaction.CustomEmoji = &ReactionTypeCustomEmoji{}
+		return json.Unmarshal(v, reaction.CustomEmoji)
+	default:
+		return fmt.Errorf("unknown ReactionType type: %s", partial.Type)
+	}
+}
+
+func (reaction *ReactionType) Type() string {
+	switch {
+	case reaction.Emoji != nil:
+		return "emoji"
+	case reaction.CustomEmoji != nil:
+		return "custom_emoji"
+	default:
+		return "unknown"
+	}
+}
+
+// Ported from https://github.com/grammyjs/emoji/blob/main/src/reactiondata.ts
+
+// Define all available reactions that can be used in the bot.
+var (
+	ReactionTypeEmojiThumbsUp                   = NewReactionTypeEmoji("👍")
+	ReactionTypeEmojiThumbsDown                 = NewReactionTypeEmoji("👎")
+	ReactionTypeEmojiRedHeart                   = NewReactionTypeEmoji("❤")
+	ReactionTypeEmojiFire                       = NewReactionTypeEmoji("🔥")
+	ReactionTypeEmojiSmilingFaceWithHearts      = NewReactionTypeEmoji("🥰")
+	ReactionTypeEmojiClappingHands              = NewReactionTypeEmoji("👏")
+	ReactionTypeEmojiBeamingFaceWithSmilingEyes = NewReactionTypeEmoji("😁")
+	ReactionTypeEmojiThinkingFace               = NewReactionTypeEmoji("🤔")
+	ReactionTypeEmojiExplodingHead              = NewReactionTypeEmoji("🤯")
+	ReactionTypeEmojiFaceScreamingInFear        = NewReactionTypeEmoji("😱")
+	ReactionTypeEmojiFaceWithSymbolsOnMouth     = NewReactionTypeEmoji("🤬")
+	ReactionTypeEmojiCryingFace                 = NewReactionTypeEmoji("😢")
+	ReactionTypeEmojiPartyPopper                = NewReactionTypeEmoji("🎉")
+	ReactionTypeEmojiStarStruck                 = NewReactionTypeEmoji("🤩")
+	ReactionTypeEmojiFaceVomiting               = NewReactionTypeEmoji("🤮")
+	ReactionTypeEmojiPileOfPoo                  = NewReactionTypeEmoji("💩")
+	ReactionTypeEmojiFoldedHands                = NewReactionTypeEmoji("🙏")
+	ReactionTypeEmojiOkHand                     = NewReactionTypeEmoji("👌")
+	ReactionTypeEmojiDove                       = NewReactionTypeEmoji("🕊")
+	ReactionTypeEmojiClownFace                  = NewReactionTypeEmoji("🤡")
+	ReactionTypeEmojiYawningFace                = NewReactionTypeEmoji("🥱")
+	ReactionTypeEmojiWoozyFace                  = NewReactionTypeEmoji("🥴")
+	ReactionTypeEmojiSmilingFaceWithHeartEyes   = NewReactionTypeEmoji("😍")
+	ReactionTypeEmojiSpoutingWhale              = NewReactionTypeEmoji("🐳")
+	ReactionTypeEmojiHeartOnFire                = NewReactionTypeEmoji("❤‍🔥")
+	ReactionTypeEmojiNewMoonFace                = NewReactionTypeEmoji("🌚")
+	ReactionTypeEmojiHotDog                     = NewReactionTypeEmoji("🌭")
+	ReactionTypeEmojiHundredPoints              = NewReactionTypeEmoji("💯")
+	ReactionTypeEmojiRollingOnTheFloorLaughing  = NewReactionTypeEmoji("🤣")
+	ReactionTypeEmojiHighVoltage                = NewReactionTypeEmoji("⚡")
+	ReactionTypeEmojiBanana                     = NewReactionTypeEmoji("🍌")
+	ReactionTypeEmojiTrophy                     = NewReactionTypeEmoji("🏆")
+	ReactionTypeEmojiBrokenHeart                = NewReactionTypeEmoji("💔")
+	ReactionTypeEmojiFaceWithRaisedEyebrow      = NewReactionTypeEmoji("🤨")
+	ReactionTypeEmojiNeutralFace                = NewReactionTypeEmoji("😐")
+	ReactionTypeEmojiStrawberry                 = NewReactionTypeEmoji("🍓")
+	ReactionTypeEmojiBottleWithPoppingCork      = NewReactionTypeEmoji("🍾")
+	ReactionTypeEmojiKissMark                   = NewReactionTypeEmoji("💋")
+	ReactionTypeEmojiMiddleFinger               = NewReactionTypeEmoji("🖕")
+	ReactionTypeEmojiSmilingFaceWithHorns       = NewReactionTypeEmoji("😈")
+	ReactionTypeEmojiSleepingFace               = NewReactionTypeEmoji("😴")
+	ReactionTypeEmojiLoudlyCryingFace           = NewReactionTypeEmoji("😭")
+	ReactionTypeEmojiNerdFace                   = NewReactionTypeEmoji("🤓")
+	ReactionTypeEmojiGhost                      = NewReactionTypeEmoji("👻")
+	ReactionTypeEmojiManTechnologist            = NewReactionTypeEmoji("👨‍💻")
+	ReactionTypeEmojiEyes                       = NewReactionTypeEmoji("👀")
+	ReactionTypeEmojiJackOLantern               = NewReactionTypeEmoji("🎃")
+	ReactionTypeEmojiSeeNoEvilMonkey            = NewReactionTypeEmoji("🙈")
+	ReactionTypeEmojiSmilingFaceWithHalo        = NewReactionTypeEmoji("😇")
+	ReactionTypeEmojiFearfulFace                = NewReactionTypeEmoji("😨")
+	ReactionTypeEmojiHandshake                  = NewReactionTypeEmoji("🤝")
+	ReactionTypeEmojiWritingHand                = NewReactionTypeEmoji("✍")
+	ReactionTypeEmojiSmilingFaceWithOpenHands   = NewReactionTypeEmoji("🤗")
+	ReactionTypeEmojiSalutingFace               = NewReactionTypeEmoji("🫡")
+	ReactionTypeEmojiSantaClaus                 = NewReactionTypeEmoji("🎅")
+	ReactionTypeEmojiChristmasTree              = NewReactionTypeEmoji("🎄")
+	ReactionTypeEmojiSnowman                    = NewReactionTypeEmoji("☃")
+	ReactionTypeEmojiNailPolish                 = NewReactionTypeEmoji("💅")
+	ReactionTypeEmojiZanyFace                   = NewReactionTypeEmoji("🤪")
+	ReactionTypeEmojiMoai                       = NewReactionTypeEmoji("🗿")
+	ReactionTypeEmojiCoolButton                 = NewReactionTypeEmoji("🆒")
+	ReactionTypeEmojiHeartWithArrow             = NewReactionTypeEmoji("💘")
+	ReactionTypeEmojiHearNoEvilMonkey           = NewReactionTypeEmoji("🙉")
+	ReactionTypeEmojiUnicorn                    = NewReactionTypeEmoji("🦄")
+	ReactionTypeEmojiFaceBlowingAKiss           = NewReactionTypeEmoji("😘")
+	ReactionTypeEmojiPill                       = NewReactionTypeEmoji("💊")
+	ReactionTypeEmojiSpeakNoEvilMonkey          = NewReactionTypeEmoji("🙊")
+	ReactionTypeEmojiSmilingFaceWithSunglasses  = NewReactionTypeEmoji("😎")
+	ReactionTypeEmojiAlienMonster               = NewReactionTypeEmoji("👾")
+	ReactionTypeEmojiManShrugging               = NewReactionTypeEmoji("🤷‍♂")
+	ReactionTypeEmojiPersonShrugging            = NewReactionTypeEmoji("🤷")
+	ReactionTypeEmojiWomanShrugging             = NewReactionTypeEmoji("🤷‍♀")
+	ReactionTypeEmojiEnragedFace                = NewReactionTypeEmoji("😡")
+)
+
+var (
+	// ReactionTypeEmojiAll is a list of all available emoji reactions
+	// that can be used in the bot as ReactionType.
+	ReactionTypeEmojiAll = []ReactionType{
+		ReactionTypeEmojiThumbsUp,
+		ReactionTypeEmojiThumbsDown,
+		ReactionTypeEmojiRedHeart,
+		ReactionTypeEmojiFire,
+		ReactionTypeEmojiSmilingFaceWithHearts,
+		ReactionTypeEmojiClappingHands,
+		ReactionTypeEmojiBeamingFaceWithSmilingEyes,
+		ReactionTypeEmojiThinkingFace,
+		ReactionTypeEmojiExplodingHead,
+		ReactionTypeEmojiFaceScreamingInFear,
+		ReactionTypeEmojiFaceWithSymbolsOnMouth,
+		ReactionTypeEmojiCryingFace,
+		ReactionTypeEmojiPartyPopper,
+		ReactionTypeEmojiStarStruck,
+		ReactionTypeEmojiFaceVomiting,
+		ReactionTypeEmojiPileOfPoo,
+		ReactionTypeEmojiFoldedHands,
+		ReactionTypeEmojiOkHand,
+		ReactionTypeEmojiDove,
+		ReactionTypeEmojiClownFace,
+		ReactionTypeEmojiYawningFace,
+		ReactionTypeEmojiWoozyFace,
+		ReactionTypeEmojiSmilingFaceWithHeartEyes,
+		ReactionTypeEmojiSpoutingWhale,
+		ReactionTypeEmojiHeartOnFire,
+		ReactionTypeEmojiNewMoonFace,
+		ReactionTypeEmojiHotDog,
+		ReactionTypeEmojiHundredPoints,
+		ReactionTypeEmojiRollingOnTheFloorLaughing,
+		ReactionTypeEmojiHighVoltage,
+		ReactionTypeEmojiBanana,
+		ReactionTypeEmojiTrophy,
+		ReactionTypeEmojiBrokenHeart,
+		ReactionTypeEmojiFaceWithRaisedEyebrow,
+		ReactionTypeEmojiNeutralFace,
+		ReactionTypeEmojiStrawberry,
+		ReactionTypeEmojiBottleWithPoppingCork,
+		ReactionTypeEmojiKissMark,
+		ReactionTypeEmojiMiddleFinger,
+		ReactionTypeEmojiSmilingFaceWithHorns,
+		ReactionTypeEmojiSleepingFace,
+		ReactionTypeEmojiLoudlyCryingFace,
+		ReactionTypeEmojiNerdFace,
+		ReactionTypeEmojiGhost,
+		ReactionTypeEmojiManTechnologist,
+		ReactionTypeEmojiEyes,
+		ReactionTypeEmojiJackOLantern,
+		ReactionTypeEmojiSeeNoEvilMonkey,
+		ReactionTypeEmojiSmilingFaceWithHalo,
+		ReactionTypeEmojiFearfulFace,
+		ReactionTypeEmojiHandshake,
+		ReactionTypeEmojiWritingHand,
+		ReactionTypeEmojiSmilingFaceWithOpenHands,
+		ReactionTypeEmojiSalutingFace,
+		ReactionTypeEmojiSantaClaus,
+		ReactionTypeEmojiChristmasTree,
+		ReactionTypeEmojiSnowman,
+		ReactionTypeEmojiNailPolish,
+		ReactionTypeEmojiZanyFace,
+		ReactionTypeEmojiMoai,
+		ReactionTypeEmojiCoolButton,
+		ReactionTypeEmojiHeartWithArrow,
+		ReactionTypeEmojiHearNoEvilMonkey,
+		ReactionTypeEmojiUnicorn,
+		ReactionTypeEmojiFaceBlowingAKiss,
+		ReactionTypeEmojiPill,
+		ReactionTypeEmojiSpeakNoEvilMonkey,
+		ReactionTypeEmojiSmilingFaceWithSunglasses,
+		ReactionTypeEmojiAlienMonster,
+		ReactionTypeEmojiManShrugging,
+		ReactionTypeEmojiPersonShrugging,
+		ReactionTypeEmojiWomanShrugging,
+		ReactionTypeEmojiEnragedFace,
+	}
+)
diff --git a/tgb/update.go b/tgb/update.go
index b32f520..1052326 100644
--- a/tgb/update.go
+++ b/tgb/update.go
@@ -177,6 +177,13 @@ func (msg *MessageUpdate) EditReplyMarkup(markup tg.InlineKeyboardMarkup) *tg.Ed
 	return msg.Client.EditMessageReplyMarkup(msg.Chat, msg.ID).ReplyMarkup(markup)
 }
 
+// React to incoming message.
+// No arguments means remove all reactions.
+func (msg *MessageUpdate) React(reactions ...tg.ReactionType) *tg.SetMessageReactionCall {
+	return msg.Client.SetMessageReaction(msg.Chat, msg.ID).
+		Reaction(reactions)
+}
+
 type CallbackQueryUpdate struct {
 	*tg.CallbackQuery
 
diff --git a/types_gen_ext.go b/types_gen_ext.go
index 79ba730..b6fbd21 100644
--- a/types_gen_ext.go
+++ b/types_gen_ext.go
@@ -1517,60 +1517,6 @@ func (origin *MessageOrigin) Type() string {
 	}
 }
 
-// ReactionType it's type for describe content of Reaction.
-// It can be one of:
-//   - [ReactionTypeEmoji]
-//   - [ReactionTypeCustomEmoji]
-type ReactionType struct {
-	Emoji       *ReactionTypeEmoji
-	CustomEmoji *ReactionTypeCustomEmoji
-}
-
-func (reaction ReactionType) MarshalJSON() ([]byte, error) {
-	switch {
-	case reaction.Emoji != nil:
-		reaction.Emoji.Type = "emoji"
-		return json.Marshal(reaction.Emoji)
-	case reaction.CustomEmoji != nil:
-		reaction.CustomEmoji.Type = "custom_emoji"
-		return json.Marshal(reaction.CustomEmoji)
-	default:
-		return nil, fmt.Errorf("unknown ReactionType type")
-	}
-}
-
-func (reaction *ReactionType) UnmarshalJSON(v []byte) error {
-	var partial struct {
-		Type string `json:"type"`
-	}
-
-	if err := json.Unmarshal(v, &partial); err != nil {
-		return fmt.Errorf("unmarshal ReactionType partial: %w", err)
-	}
-
-	switch partial.Type {
-	case "emoji":
-		reaction.Emoji = &ReactionTypeEmoji{}
-		return json.Unmarshal(v, reaction.Emoji)
-	case "custom_emoji":
-		reaction.CustomEmoji = &ReactionTypeCustomEmoji{}
-		return json.Unmarshal(v, reaction.CustomEmoji)
-	default:
-		return fmt.Errorf("unknown ReactionType type: %s", partial.Type)
-	}
-}
-
-func (reaction *ReactionType) Type() string {
-	switch {
-	case reaction.Emoji != nil:
-		return "emoji"
-	case reaction.CustomEmoji != nil:
-		return "custom_emoji"
-	default:
-		return "unknown"
-	}
-}
-
 // This object describes a message that can be inaccessible to the bot.
 // It can be one of:
 //   - [Message]


Test patch:
diff --git a/reactions_test.go b/reactions_test.go
new file mode 100644
index 0000000..f791ae3
--- /dev/null
+++ b/reactions_test.go
@@ -0,0 +1,80 @@
+package tg
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestReactionType(t *testing.T) {
+	t.Run("Emoji", func(t *testing.T) {
+		var r ReactionType
+
+		err := r.UnmarshalJSON([]byte(`{"type": "emoji", "emoji": "😀"}`))
+		require.NoError(t, err)
+
+		assert.Equal(t, "emoji", r.Type())
+		require.NotNil(t, r.Emoji)
+		assert.Equal(t, "😀", r.Emoji.Emoji)
+	})
+
+	t.Run("CustomEmoji", func(t *testing.T) {
+		var r ReactionType
+
+		err := r.UnmarshalJSON([]byte(`{"type": "custom_emoji", "custom_emoji_id": "12345"}`))
+		require.NoError(t, err)
+
+		assert.Equal(t, "custom_emoji", r.Type())
+		require.NotNil(t, r.CustomEmoji)
+		assert.Equal(t, "12345", r.CustomEmoji.CustomEmojiID)
+	})
+
+	t.Run("Unknown", func(t *testing.T) {
+		var r ReactionType
+
+		err := r.UnmarshalJSON([]byte(`{"type": "unknown"}`))
+		require.Error(t, err)
+	})
+
+	t.Run("InvalidFieldType", func(t *testing.T) {
+		var r ReactionType
+
+		err := r.UnmarshalJSON([]byte(`{"type": 123}`))
+		require.Error(t, err)
+	})
+}
+
+func TestReactionType_MarshalJSON(t *testing.T) {
+	t.Run("Emoji", func(t *testing.T) {
+		r := NewReactionTypeEmoji("😀")
+
+		assert.Equal(t, "emoji", r.Type())
+
+		b, err := json.Marshal(r)
+		require.NoError(t, err)
+
+		assert.Equal(t, `{"type":"emoji","emoji":"😀"}`, string(b))
+	})
+
+	t.Run("CustomEmoji", func(t *testing.T) {
+		r := NewReactionTypeCustomEmoji("12345")
+
+		assert.Equal(t, "custom_emoji", r.Type())
+
+		b, err := json.Marshal(r)
+		require.NoError(t, err)
+
+		assert.Equal(t, `{"type":"custom_emoji","custom_emoji_id":"12345"}`, string(b))
+	})
+
+	t.Run("Unknown", func(t *testing.T) {
+		r := ReactionType{}
+
+		assert.Equal(t, "unknown", r.Type())
+
+		_, err := json.Marshal(r)
+		require.Error(t, err)
+	})
+}
diff --git a/tgb/update_test.go b/tgb/update_test.go
index b68bff6..d22e370 100644
--- a/tgb/update_test.go
+++ b/tgb/update_test.go
@@ -333,6 +333,16 @@ func TestMessageUpdateHelpers(t *testing.T) {
 				"reply_markup": "{\"inline_keyboard\":[[{\"text\":\"1\",\"callback_data\":\"1\"}]]}",
 			},
 		},
+		{
+			Name:           "React",
+			Request:        msg.React(tg.ReactionTypeEmojiThumbsUp).Request(),
+			ExceptedMethod: "setMessageReaction",
+			ExpectedArgs: map[string]string{
+				"chat_id":    "123",
+				"message_id": "1",
+				"reaction":   `[{"type":"emoji","emoji":"👍"}]`,
+			},
+		},
 		{
 			Name: "AnswerMediaGroup",
 			Request: msg.AnswerMediaGroup([]tg.InputMedia{
diff --git a/types_gen_ext_test.go b/types_gen_ext_test.go
index c6f08bb..5c731ca 100644
--- a/types_gen_ext_test.go
+++ b/types_gen_ext_test.go
@@ -1283,74 +1283,6 @@ func TestMessageOrigin_UnmarshalJSON(t *testing.T) {
 	})
 }
 
-func TestReactionType(t *testing.T) {
-	t.Run("Emoji", func(t *testing.T) {
-		var r ReactionType
-
-		err := r.UnmarshalJSON([]byte(`{"type": "emoji", "emoji": "😀"}`))
-		require.NoError(t, err)
-
-		assert.Equal(t, "emoji", r.Type())
-		require.NotNil(t, r.Emoji)
-		assert.Equal(t, "😀", r.Emoji.Emoji)
-	})
-
-	t.Run("CustomEmoji", func(t *testing.T) {
-		var r ReactionType
-
-		err := r.UnmarshalJSON([]byte(`{"type": "custom_emoji", "custom_emoji_id": "12345"}`))
-		require.NoError(t, err)
-
-		assert.Equal(t, "custom_emoji", r.Type())
-		require.NotNil(t, r.CustomEmoji)
-		assert.Equal(t, "12345", r.CustomEmoji.CustomEmojiID)
-	})
-
-	t.Run("Unknown", func(t *testing.T) {
-		var r ReactionType
-
-		err := r.UnmarshalJSON([]byte(`{"type": "unknown"}`))
-		require.Error(t, err)
-	})
-}
-
-func TestReactionType_MarshalJSON(t *testing.T) {
-	t.Run("Emoji", func(t *testing.T) {
-		r := ReactionType{
-			Emoji: &ReactionTypeEmoji{Emoji: "😀"},
-		}
-
-		assert.Equal(t, "emoji", r.Type())
-
-		b, err := json.Marshal(r)
-		require.NoError(t, err)
-
-		assert.Equal(t, `{"type":"emoji","emoji":"😀"}`, string(b))
-	})
-
-	t.Run("CustomEmoji", func(t *testing.T) {
-		r := &ReactionType{
-			CustomEmoji: &ReactionTypeCustomEmoji{CustomEmojiID: "12345"},
-		}
-
-		assert.Equal(t, "custom_emoji", r.Type())
-
-		b, err := json.Marshal(r)
-		require.NoError(t, err)
-
-		assert.Equal(t, `{"type":"custom_emoji","custom_emoji_id":"12345"}`, string(b))
-	})
-
-	t.Run("Unknown", func(t *testing.T) {
-		r := ReactionType{}
-
-		assert.Equal(t, "unknown", r.Type())
-
-		_, err := json.Marshal(r)
-		require.Error(t, err)
-	})
-}
-
 func TestMaybeInaccessibleMessage(t *testing.T) {
 	t.Run("InaccessibleMessage", func(t *testing.T) {
 		var m MaybeInaccessibleMessage