package main import ( "bytes" "embed" "encoding/base64" "fmt" "html/template" "log" "math" "net/http" "os" "regexp" "strconv" "strings" "time" "github.com/ChimeraCoder/anaconda" strip "github.com/grokify/html-strip-tags-go" "github.com/joho/godotenv" ) //go:embed index.html tweet.svg.tmpl var content embed.FS var ( api *anaconda.TwitterApi tweetDateLayout = "Mon Jan 2 15:04:05 -0700 2006" port string accessToken string accessTokenSecret string consumerKey string consumerSecret string ) func init() { godotenv.Load(os.Getenv("DOTENV_DIR") + ".env") port = os.Getenv("PORT") accessToken = os.Getenv("ACCESS_TOKEN") accessTokenSecret = os.Getenv("ACCESS_TOKEN_SECRET") consumerKey = os.Getenv("CONSUMER_KEY") consumerSecret = os.Getenv("CONSUMER_SECRET") } func main() { if accessToken == "" || accessTokenSecret == "" || consumerKey == "" || consumerSecret == "" { panic("Missing Twitter credentials") } api = anaconda.NewTwitterApiWithCredentials(accessToken, accessTokenSecret, consumerKey, consumerSecret) if port == "" { port = "8080" } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if len(r.URL.Path) > 1 { getTweet(w, r) } else { body, _ := content.ReadFile("index.html") w.Write(body) } }) fmt.Println("Server started at port " + port) log.Fatal(http.ListenAndServe(":"+port, nil)) } func getTweet(w http.ResponseWriter, r *http.Request) { id := strings.ReplaceAll(r.URL.Path, "/", "") i, err := strconv.ParseInt(id, 10, 64) if err != nil { w.WriteHeader(400) return } tweet, err := api.GetTweet(i, nil) if err != nil { w.WriteHeader(404) return } re := regexp.MustCompile(`[\x{1F300}-\x{1F6FF}]`) emojis := re.FindAllString(tweet.FullText, -1) emojiCount := 0 for _, emoji := range emojis { emojiCount += len([]byte(emoji)) - 1 } tweet.FullText = tweet.FullText[tweet.DisplayTextRange[0] : tweet.DisplayTextRange[1]+emojiCount] for _, user := range tweet.Entities.User_mentions { tweet.FullText = strings.ReplaceAll(tweet.FullText, "@"+user.Screen_name, fmt.Sprintf("@%s", user.Screen_name, user.Screen_name)) } for _, url := range tweet.Entities.Urls { tweet.FullText = strings.ReplaceAll(tweet.FullText, url.Url, fmt.Sprintf("%s", url.Expanded_url, url.Display_url)) } for _, hashtag := range tweet.Entities.Hashtags { tweet.FullText = strings.ReplaceAll(tweet.FullText, "#"+hashtag.Text, fmt.Sprintf("#%s", hashtag.Text, hashtag.Text)) } tweet.FullText = strings.ReplaceAll(tweet.FullText, "\n", "
") templateFuncs := template.FuncMap{ "base64": func(url string) string { res, err := http.Get(url) if err != nil { return "" } buf := new(bytes.Buffer) buf.ReadFrom(res.Body) return base64.StdEncoding.EncodeToString(buf.Bytes()) }, "isoDate": func(date string) string { t, _ := time.Parse(tweetDateLayout, date) return t.Format(time.RFC3339) }, "humanDate": func(date string) string { t, _ := time.Parse(tweetDateLayout, date) return t.Format("3:04 PM ยท Jan 2, 2006") }, "html": func(in string) template.HTML { return template.HTML(in) }, "calculateHeight": func(tweet anaconda.Tweet) string { height := 205.0 lines := math.Floor(float64(len(strip.StripTags(tweet.FullText))) / 40) height += lines * 20 if tweet.InReplyToScreenName != "" { height += 45 } height += float64(strings.Count(tweet.FullText, "
") * 20) for _, pic := range tweet.ExtendedEntities.Media { ratio := float64(pic.Sizes.Small.W) / 464 height += float64(pic.Sizes.Small.H) / ratio } return fmt.Sprintf("%dpx", int64(height)) }, } t := template.Must( template.New("tweet.svg.tmpl"). Funcs(templateFuncs). ParseFS(content, "tweet.svg.tmpl")) w.Header().Set("Content-type", "image/svg+xml") err = t.Execute(w, tweet) if err != nil { fmt.Println(err) w.WriteHeader(500) return } }