Skip to content

Instantly share code, notes, and snippets.

@adrianocr
Created August 25, 2020 02:04
Show Gist options
  • Save adrianocr/8afae928551ec3d165cf3831ec861004 to your computer and use it in GitHub Desktop.
Save adrianocr/8afae928551ec3d165cf3831ec861004 to your computer and use it in GitHub Desktop.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"strings"
)
const FILENAME = "./comic_index.json"
const XKCDURL = "https://xkcd.com/"
type Comic struct {
Month string `json:"month"`
Num int `json:"num"`
Link string `json:"link"`
Year string `json:"year"`
News string `json:"news"`
SafeTitle string `json:"safe_title"`
Transcript string `json:"transcript"`
Alt string `json:"alt"`
Img string `json:"img"`
Title string `json:"title"`
Day string `json:"day"`
}
// create the index the first time around if it doesn't already exist
func createIndex () {
file, err := os.Create(FILENAME)
if err != nil {
log.Fatal("Failed to create index file. ", err)
}
defer file.Close()
i := 1
comics := getComics(i)
indexData, err := json.MarshalIndent(comics, "", " ")
if err != nil {
fmt.Println("Failed to marshall comics.")
}
_, err = file.Write(indexData)
if err != nil {
fmt.Println("Failed to write index data.")
}
}
// hit each possible URL and get the Comic
func getComics (startingIndex int) (comics []Comic) {
for {
resp, err := http.Get(XKCDURL + strconv.Itoa(startingIndex) + "/info.0.json")
if err != nil {
fmt.Println("Failed to make request to XKCD. ", err)
break
}
if resp.StatusCode != http.StatusOK {
/*
XKCD are funny people so https://xkcd.com/404/info.0.json actually returns a 404 error which would
cause the original StatusOK check to fail. The below extra condition makes the loop check the next
possible item after the failed index to make sure there aren't any other comics after it. I have a
feeling the break continue statements below are inelegant and there is a better way to do this but
I haven't thought of it yet.
*/
resp, err = http.Get(XKCDURL + strconv.Itoa(startingIndex+1) + "/info.0.json")
if err != nil {
fmt.Println("Failed to make secondary request. ", err)
break
}
if resp.StatusCode != http.StatusOK {
break
} else {
startingIndex++
continue
}
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Failed to read Body into `data`. ", err)
}
comic := Comic{}
if err = json.Unmarshal(data, &comic); err != nil {
fmt.Println("Failed to unmarshall JSON data. ", err)
}
comics = append(comics, comic)
startingIndex++
}
return comics
}
// check to see if the index is up to date, if it is not update it
func updateIndex () {
file, err := os.Open(FILENAME)
if err != nil {
log.Fatal("Failed to open index file. ", err)
}
defer file.Close()
readData, err := ioutil.ReadAll(file)
var comicsList []Comic
if err := json.Unmarshal(readData, &comicsList); err != nil {
fmt.Println("Failed to read unmarshall json data. ", err)
}
i := comicsList[len(comicsList)-1].Num + 1
newComics := append(comicsList, getComics(i)...)
data, err := json.MarshalIndent(newComics, "", " ")
if err != nil {
fmt.Println("Failed to marshall `newComics`. ", err)
}
/*
I'm not quite sure if the below is the best way to "append" the new entries into the file.
Feels kind of hacky to write over the entire file and to not use the `file` variable that already exists
*/
err = ioutil.WriteFile(FILENAME, data, 0664)
if err != nil {
fmt.Println("Failed to write to file or append. ", err)
}
}
func searchComics (searchTerms string) (results []Comic) {
file, err := os.Open(FILENAME)
if err != nil {
log.Fatal("Failed to open index file. ", err)
}
data, err := ioutil.ReadAll(file)
var comics []Comic
if err = json.Unmarshal(data, &comics); err != nil {
fmt.Println("Failed to unmarshall data. ", err)
}
terms := strings.Fields(searchTerms)
var tmpResults = make(map[Comic]bool)
for _, item := range comics {
transcript := strings.Fields(item.Transcript)
for _, v := range transcript {
for _, term := range terms {
if v == term {
tmpResults[item] = true
}
}
}
}
for item, _ := range tmpResults {
results = append(results, item)
}
return results
}
func main () {
if _, err := os.Stat(FILENAME); err == nil {
updateIndex()
} else {
createIndex()
}
results := searchComics(os.Args[1])
for _, item := range results {
fmt.Print("\n\n//---------------------------------------COMIC---------------------------------------//\n\n")
fmt.Printf("Image: %-5s\nTranscript: %.95s...", item.Img, item.Transcript)
}
fmt.Println("\n\n\nResults: ", len(results))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment