From 9692da68521c44f11be8cf2e79242f2db86e9d4b Mon Sep 17 00:00:00 2001 From: Julien Riou Date: Tue, 2 Mar 2021 08:56:47 +0100 Subject: [PATCH] Truncate too long product names for Twitter Signed-off-by: Julien Riou --- twitter.go | 25 +++++++++++++++++++++---- twitter_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/twitter.go b/twitter.go index e8dfc62..14f346d 100644 --- a/twitter.go +++ b/twitter.go @@ -5,6 +5,7 @@ import ( "regexp" "strings" "time" + "unicode/utf8" "github.com/dghubble/go-twitter/twitter" "github.com/dghubble/oauth1" @@ -12,6 +13,9 @@ import ( "gorm.io/gorm" ) +// maximum number of characters a tweet can support +const tweetMaxSize = 280 + // Tweet to store relationship between a Product and a Twitter notification type Tweet struct { gorm.Model @@ -107,11 +111,8 @@ func formatPrice(value float64, currency string) string { // NotifyWhenAvailable create a Twitter status for announcing that a product is available // implements the Notifier interface func (c *TwitterNotifier) NotifyWhenAvailable(shopName string, productName string, productPrice float64, productCurrency string, productURL string) error { - // format message - formattedPrice := formatPrice(productPrice, productCurrency) hashtags := c.buildHashtags(productName) - message := fmt.Sprintf("%s: %s for %s is available at %s %s", shopName, productName, formattedPrice, productURL, hashtags) - + message := formatAvailableTweet(shopName, productName, productPrice, productCurrency, productURL, hashtags) // create thread tweetID, err := c.createTweet(message) if err != nil { @@ -129,6 +130,22 @@ func (c *TwitterNotifier) NotifyWhenAvailable(shopName string, productName strin return nil } +func formatAvailableTweet(shopName string, productName string, productPrice float64, productCurrency string, productURL string, hashtags string) string { + // format message + formattedPrice := formatPrice(productPrice, productCurrency) + message := fmt.Sprintf("%s: %s for %s is available at %s %s", shopName, productName, formattedPrice, productURL, hashtags) + + // truncate tweet if too big + if utf8.RuneCountInString(message) > tweetMaxSize { + // maximum tweet size - other characters - additional "…" to say product name has been truncated + productNameSize := tweetMaxSize - utf8.RuneCountInString(fmt.Sprintf("%s: for %s is available at %s %s", shopName, formattedPrice, productURL, hashtags)) - 1 + format := fmt.Sprintf("%%s: %%.%ds… for %%s is available at %%s %%s", productNameSize) + message = fmt.Sprintf(format, shopName, productName, formattedPrice, productURL, hashtags) + } + + return message +} + // NotifyWhenNotAvailable create a Twitter status replying to the NotifyWhenAvailable status to say it's over // implements the Notifier interface func (c *TwitterNotifier) NotifyWhenNotAvailable(productURL string, duration time.Duration) error { diff --git a/twitter_test.go b/twitter_test.go index db3af41..a2a985b 100644 --- a/twitter_test.go +++ b/twitter_test.go @@ -79,3 +79,46 @@ func TestFormatPrice(t *testing.T) { }) } } + +/* +func formatAvailableTweet(shopName string, productName string, productPrice float64, productCurrency string, productURL string, hashtags string) string { + // format message + formattedPrice := formatPrice(productPrice, productCurrency) + message := fmt.Sprintf("%s: %s for %s is available at %s %s", shopName, productName, formattedPrice, productURL, hashtags) + + // truncate tweet if too big + if utf8.RuneCountInString(message) > tweetMaxSize { + // maximum tweet size - other characters - additional "…" to say product name has been truncated + productNameSize := tweetMaxSize - utf8.RuneCountInString(fmt.Sprintf("%s: for %s is available at %s %s", shopName, formattedPrice, productURL, hashtags)) - 1 + format := fmt.Sprintf("%%s: %%.%ds… for %%s is available at %%s %%s", productNameSize) + message = fmt.Sprintf(format, shopName, productName, formattedPrice, productURL, hashtags) + } + + return message +} +*/ + +func TestFormatAvailableTweet(t *testing.T) { + tests := []struct { + shopName string + productName string + productPrice float64 + productCurrency string + productURL string + hashtags string + expected string + }{ + {"shop.com", "my awesome product", 999.99, "USD", "https://shop.com/awesome", "#awesome #product", "shop.com: my awesome product for $999.99 is available at https://shop.com/awesome #awesome #product"}, + {"shop.com", "my awesome product with very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long name", 999.99, "USD", "https://shop.com/awesome", "#awesome #product", "shop.com: my awesome product with very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very… for $999.99 is available at https://shop.com/awesome #awesome #product"}, + } + for i, tc := range tests { + t.Run(fmt.Sprintf("TestFormatAvailableTweet#%d", i), func(t *testing.T) { + got := formatAvailableTweet(tc.shopName, tc.productName, tc.productPrice, tc.productCurrency, tc.productURL, tc.hashtags) + if got != tc.expected { + t.Errorf("for %s, got '%s', want '%s'", tc.productName, got, tc.expected) + } else { + t.Logf("for %s, got '%s', want '%s'", tc.productName, got, tc.expected) + } + }) + } +}