Do you think your Twitter profile banner is boring? Do you wish there was something that could rotate your Twitter banner images? Maybe what you want is to brag about your Twitter account statistics on your profile banner to make a unique Twitter profile banner. This article showcases a Go program that does just that.
Retrieve User Statistics from Twitter
We will authenticate with Twitter API using OAuth 2.0 to retrieve the statistics for the given Twitter profile. To achieve this, we need the Go oauth2 package.
You can find the code for user data in user_data.go.
Get OAuth 2.0 Client
Using the Go oauth2 package you can make an HTTP Client. OAuth 2.0 requires your consumer key and consumer secret which has access to Twitter public API.
import (
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
)
func getTwitterOauth2Client(consumerKey string, consumerSecret string) *http.Client {
config := &clientcredentials.Config{
ClientID: consumerKey,
ClientSecret: consumerSecret,
TokenURL: "https://api.twitter.com/oauth2/token",
}
return config.Client(oauth2.NoContext)
}
Fetch the user ID for the given Twitter user name
For convenience, we want to allow people to specify user names to get the user statistics. This function uses the OAuth 2.0 client created above with the user name to get the ID of the user.
import (
"encoding/json"
"net/http"
)
type userLookup struct {
Id string `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
}
func fetchIdForUsername(client *http.Client, username string) (string, error) {
req, err := http.NewRequest("GET", "https://api.twitter.com/2/users/by/username/"+username, nil)
if err != nil {
return "", err
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
dec := json.NewDecoder(resp.Body)
var data = make(map[string]userLookup)
err = dec.Decode(&data)
if err != nil {
return "", err
}
return data["data"].Id, nil
}
Fetch user data for the given user ID
Using the user ID we obtained above we need to make a request to Twitter public API to fetch the user data.
import (
"encoding/json"
"net/http"
)
type publicMetrics struct {
FollowerCount int `json:"followers_count"`
FollowingCount int `json:"following_count"`
TweetCount int `json:"tweet_count"`
ListedCount int `json:"listed_count"`
}
type user struct {
PublicMetrics publicMetrics `json:"public_metrics"`
Location string
}
func fetchUserData(client *http.Client, userId string) (map[string]user, error) {
req, err := http.NewRequest("GET", "https://api.twitter.com/2/users/"+userId+"?user.fields=public_metrics,location", nil)
if err != nil {
return make(map[string]user), err
}
resp, err := client.Do(req)
if err != nil {
return make(map[string]user), err
}
dec := json.NewDecoder(resp.Body)
userData := make(map[string]user)
err = dec.Decode(&userData)
if err != nil {
return make(map[string]user), err
}
return userData, nil
}
Get Twitter user data function
We will have a separate function that simplifies the user data retrieving functionality so it’s easier to use externally.
import (
"fmt"
)
func GetTwitterUserData(consumerKey string, consumerSecret string, username string) (map[string]string, error) {
client := getTwitterOauth2Client(consumerKey, consumerSecret)
userId, err := fetchIdForUsername(client, username)
if err != nil {
return make(map[string]string), err
}
userData, err := fetchUserData(client, userId)
if err != nil {
return make(map[string]string), err
}
return map[string]string{
"followers_count": fmt.Sprintf("%d", userData["data"].PublicMetrics.FollowerCount),
"following_count": fmt.Sprintf("%d", userData["data"].PublicMetrics.FollowingCount),
"tweet_count": fmt.Sprintf("%d", userData["data"].PublicMetrics.TweetCount),
"listed_count": fmt.Sprintf("%d", userData["data"].PublicMetrics.ListedCount),
"location": userData["data"].Location,
}, nil
}
Get text lines function
This function will combine all the text we’re going to add to the image.
import (
"fmt"
)
func GetTwitterUserData(consumerKey string, consumerSecret string, username string) (map[string]string, error) {
client := getTwitterOauth2Client(consumerKey, consumerSecret)
userId, err := fetchIdForUsername(client, username)
if err != nil {
return make(map[string]string), err
}
userData, err := fetchUserData(client, userId)
if err != nil {
return make(map[string]string), err
}
return map[string]string{
"followers_count": fmt.Sprintf("%d", userData["data"].PublicMetrics.FollowerCount),
"following_count": fmt.Sprintf("%d", userData["data"].PublicMetrics.FollowingCount),
"tweet_count": fmt.Sprintf("%d", userData["data"].PublicMetrics.TweetCount),
"listed_count": fmt.Sprintf("%d", userData["data"].PublicMetrics.ListedCount),
"location": userData["data"].Location,
}, nil
}
Choosing a base image
To make the banner image more exciting, we can switch between different images. The program finds a random jpeg in the “images/” directory and uses it as the base image.
You can find the source for this in io.go.
import (
"errors"
"fmt"
"math/rand"
"os"
"path/filepath"
"time"
)
func getJpegFilesInDirectory(directory string) []string {
var files []string
filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
return nil
}
fileExt := filepath.Ext(path)
if !info.IsDir() && (fileExt == ".jpg" || fileExt == ".jpeg") {
files = append(files, path)
}
return nil
})
return files
}
func getRandIndexInArray(array []string) (int, error) {
if len(array) == 0 {
return -1, errors.New("Empty list")
}
rand.Seed(time.Now().Unix())
return rand.Intn(len(array)), nil
}
func GetJpegPathInDirectory(directory string) (string, error) {
files := getJpegFilesInDirectory(directory)
rInd, err := getRandIndexInArray(files)
if err != nil {
return "", errEmptyDir
}
return files[rInd], nil
}
Image Manipulation
We’ll draw a rectangle onto the base JPEG file. After that, we’ll add the text to the rectangle.
You can see the source code for this section in image.go.
Convert JPEG to drawable
To allow manipulation, we need to create a draw object from the selected JPEG file.
import (
"bufio"
"image"
_ "image/jpeg"
"image/draw"
"os"
)
func GetDrawableFromImagePath(imagePath string) *image.RGBA {
file, err := os.Open(imagePath)
if err != nil {
panic(err)
}
reader := bufio.NewReader(file)
decodedImage, _, err := image.Decode(reader)
if err != nil {
panic(err)
}
decodedImageBounds := decodedImage.Bounds()
drawable := image.NewRGBA(image.Rect(0, 0, decodedImageBounds.Dx(), decodedImageBounds.Dy()))
draw.Draw(drawable, drawable.Bounds(), decodedImage, decodedImageBounds.Min, draw.Src)
return drawable
}
Load font to use
To print text on the draw object, we need to select a font. The source code contains the Open Sans font for ease of use.
import (
"io/ioutil"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
)
func LoadFontFromPath(fontPath string) *truetype.Font {
fontBytes, err := ioutil.ReadFile(fontPath)
if err != nil {
panic(err)
}
font, err := freetype.ParseFont(fontBytes)
if err != nil {
panic(err)
}
return font
}
Draw overlay rectangle over the image
Before we draw the text we will draw the text background. This is to make the text more readable.
import (
"image"
"image/color"
)
func AddOverlayOnDrawable(drawable *image.RGBA, rectangle image.Rectangle, colour *color.RGBA, opacity *color.Alpha) {
draw.DrawMask(drawable, rectangle, &image.Uniform{colour}, image.ZP, &image.Uniform{opacity}, image.ZP, draw.Over)
}
Obtain freetype context for writing
To draw text onto the image, we need a freetype context. We obtain this by providing the text options such as the font and the font size.
import (
"image"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
)
func GetFreetypeContext(font *truetype.Font, dpi float64, fontSize float64, drawable *image.RGBA) *freetype.Context {
context := freetype.NewContext()
context.SetDPI(dpi)
context.SetFont(font)
context.SetFontSize(fontSize)
context.SetClip(drawable.Bounds())
context.SetDst(drawable)
context.SetSrc(image.Black)
return context
}
Write text lines on the overlay
We want the text to be on the overlay rectangle. Therefore we’ll use the overlay rectangle coordinates when writing the text.
import (
"image"
"github.com/golang/freetype"
)
func WriteLinesOnRectangle(rectangle image.Rectangle, context *freetype.Context, lines []string, fontSize int, padding int) {
pointX := rectangle.Min.X + padding
pointY := rectangle.Min.Y + fontSize
for _, text := range lines {
labelPoint := freetype.Pt(pointX, pointY)
_, err := context.DrawString(text, labelPoint)
if err != nil {
panic(err)
}
pointY += fontSize + padding
}
}
Save to PNG file
When debugging it’s useful to see the banner image before it’s uploaded to Twitter. For that reason, we have an option to save the banner as a PNG.
import (
"image/png"
"os"
)
func WriteToPngFile(filename string, drawable *image.RGBA) {
outFile, err := os.Create(filename)
if err != nil {
panic(err)
}
png.Encode(outFile, drawable)
}
Upload Banner Image to Twitter
Finally, we’ll need to upload the generated banner image to Twitter.
Source code is available in banner_update.go.
Get Twitter OAuth 1.0 client
You can only do banner image uploads using Twitter v1 API. Twitter v1 API uses OAuth 1.0, so we’ll need an OAuth 1.0 client in our program.
import (
"net/http"
"github.com/dghubble/oauth1"
)
func getTwitterOauth1Client(consumerKey string, consumerSecret string, accessToken string, accessSecret string) *http.Client {
config := oauth1.NewConfig(consumerKey, consumerSecret)
token := oauth1.NewToken(accessToken, accessSecret)
return config.Client(oauth1.NoContext, token)
}
Encoding banner form
Twitter update_profile_banner API requires us to encode the banner image in a particular way. In the function below we’re reading the image and writing into a buffer.
import (
"bytes"
"image"
"image/png"
"mime/multipart"
)
func writeBannerForm(body *bytes.Buffer, drawable *image.RGBA) (*multipart.Writer, error) {
multipartWriter := multipart.NewWriter(body)
fwriter, err := multipartWriter.CreateFormField("banner")
if err != nil {
return multipartWriter, err
}
err = png.Encode(fwriter, drawable)
if err != nil {
return multipartWriter, err
}
multipartWriter.Close()
return multipartWriter, nil
}
Do upload request
When updating the banner, we need to ensure the content type is correctly set. The update_profile_banner API returns HTTP 201 for successful uploads.
import (
"bytes"
"fmt"
"mime/multipart"
"net/http"
)
func doTwitterUploadRequest(client *http.Client, multipartWriter *multipart.Writer, body *bytes.Buffer, debug bool) error {
const apiUrl = "https://api.twitter.com/1.1/account/update_profile_banner.json"
req, err := http.NewRequest("POST", apiUrl, body)
if err != nil {
return err
}
req.Header.Set("Content-Type", multipartWriter.FormDataContentType())
res, err := client.Do(req)
if err != nil {
return err
}
if res.StatusCode != 201 {
return errors.New(fmt.Sprintf("Failed upload request. Status: %s", res.Status))
}
if debug {
fmt.Printf("Upload request returned: %s\n", res.Status)
}
return nil
}
Update Twitter banner
For convenience, the upload process can be triggered from a single public function.
import (
"bytes"
"image"
"net/http"
)
func UpdateTwitterBanner(consumerKey string, consumerSecret string, accessToken string, accessSecret string, drawable *image.RGBA, debug bool) error {
var body bytes.Buffer
client := getTwitterOauth1Client(consumerKey, consumerSecret, accessToken, accessSecret)
multipartWriter, err := writeBannerForm(&body, drawable)
if err != nil {
return err
}
err = doTwitterUploadRequest(client, multipartWriter, &body, debug)
if err != nil {
return err
}
return nil
}
Usage
Download Twitter data and replace profile banner
The main use case for the program is to fetch Twitter profile statistics, place them on a base image and upload it as a profile banner. You can achieve this by running the program in the following way:
./verbose-twit-banner -access-secret=x -access-token=x \
-consumer-key=x -consumer-secret=x \
-username=oliverradwell
Generate an image without any changes to your profile
You can run the program for any Twitter profile to see what image it would generate. To achieve this, the program needs to run with the following parameters:
./verbose-twit-banner -consumer-key=x -consumer-secret=x \
-username=oliverradwell -dry-run
Your unique Twitter profile banner
Hope you found this article showcasing the verbose-twit-banner Go program useful. Now, you can also have a unique Twitter profile banner. For more details, please visit the verbose-twit-banner GitHub repository.