Getting Started
A few days ago I decided to pick up a new programming language. For a while I’ve been a predominantly Python dev, with scatterings of C, Javascript, HTML, and CSS depending on the project at hand. Seeing as Golang has been become fairly popular in the DevOps realm, I decided it’d be worth while to pick it up.
Getting started wasn’t too bad, I made sure Go was installed, opened up my favorite editor and started getting to work. I decided my first project would be a simple program to take in the XKCD API, and post the latest comic to a discord webhook.
Please note that this just a braindump of my experience creating this project with Golang, so if I’m not 100% on best practices, or paradimes I apologize in advance!
Go-XKCD
The XKCD API only returns a few items:
Sample Return { "month": "4", "num": 2916, "link": "", "year": "2024", "news": "...trimmed...", "safe_title": "Machine", "transcript": "", "alt": "The Credible Machine" "img": "https://imgs.xkcd.com/comics/machine.png", "title": "Machine", "extra_parts": { "headerextra": "", "pre": "", "post": "\n <script src="//xkcd.com/2916/client/index.js"></script>\n", "imgAttr": "" }, "day": "5" }
In-order to craft our webhook with the comic we only really need a couple items:
title
img
alt
(this one is kind-of optional in my use case)
Since I know the keys and types from the API, I can easily create a Struct
to hold the data I need.
// Basic struct for grabbing only the items we want from the json type Comic struct { Title string Alt string Img string }
With this I was able to use json.Unmarshal
to decode the json from XKCD into a struct, the resulting output was:
{Machine The Credible Machine https://imgs.xkcd.com/comics/machine.png}
Now that I had the required elements from the input, I could move onto crafting my response. This led me down looking into how maps and interface{}
worked.
Here is an example JSON we want to create:
{ "content": "Daily XKCD", "embeds": [ { "title": "Machine", "image": { "url": "https://imgs.xkcd.com/comics/machine.png" } } ] }
Since I am coming in from Python, I immediately thought about how I would do it in Python, as a result I essentially wrote Pythonic Golang. Instead of treating JSON objects as actual objects, I instead looked at it all as key->value pairings. Meaning in Python I would’ve crafted my embed array, and added that to my final payload dictionary as a value for the image
key.
A few takeaways from that:
interface{}
is good for working on arbitrary types.- If you know the exact keys and types you should create a Struct
So I ended up writing Pythonic Go code? If that’s a phrase.
The code below does three things:
- We create the imageURL map, to map the
url
key to the actualimage_url
. - We then create an array containing the
title
andimage: imageURL
- We append this array to our
mapSlice
variable, to create the final embeds map.
type M map[string]interface{} func create_embed(comic Comic) []M { var mapSlice []M imageUrl := map[string]string{"url": comic.Img} image := M{"title": comic.Title, "image": imageUrl} mapSlice = append(mapSlice, image) return(mapSlice) }
At the end you essentially get something that looks like this:
{"image":{"url":"https://imgs.xkcd.com/comics/machine.png"}, "title":"Machine"}
Now, someone more experinced than me pointed out I shouldn’t use interface{}
in this case. The better method would have been to create an Embed
struct and Image
struct, as these are two JSON objects.
I intend to go back and make those changes but I wanted to stick with what I did/figured out in my first few days.
Now that we have our embed array ready we move on to the final payload. For this payload I created a struct:
type DiscWebhook struct { Content string `json:"content"` Embeds []M `json:"embeds"` }
I made use of the tags to make sure when the JSON was formed we
- Exposed the
Content
andEmbeds
fields to Go, so we could set the values correctly. - When encoding the JSON our objects where named
content
andembeds
.
My understanding of tags is they provide additional information about the field. Specifically with JSON it determines the JSON object we’re talking about.
Crafting the remaing response was fairly straightforward. I simply instantiate the DiscWebhook
struct, and then pass in the values
discordData := DiscWebhook{ Content: "Daily XKCD", Embeds: embeds, }
Using the json.Marshal
function, I was able to pass my struct in, and form the payload for our post request.
jsonData, err := json.Marshal(discordData) if err != nil { panic(err) } resp, err := http.Post("<discord_webhook>", "application/json", bytes.NewBuffer(jsonData)) if err != nil { panic(err) }
The result was a nice Discord post with todays XKCD image!
Future Plans
I do want to improve on this more, firstly by fixing the use of interfaces{}
and instead using Structs for forming the end JSON.
I also want to try an integrate this into a discord bot down the line and have it automatically post a daily XKCD comic, either the latest or just a random one.
You can view the code on my Github: Go-XKCD.