159 lines
4.3 KiB
Go
159 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"math/rand/v2"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
spinhttp "github.com/fermyon/spin/sdk/go/v2/http"
|
|
"github.com/julienschmidt/httprouter"
|
|
)
|
|
|
|
const (
|
|
contentTypeHeader = "Content-Type"
|
|
)
|
|
|
|
type MonkeyUserEntry struct {
|
|
Title string `json:"title"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type Comic struct {
|
|
Source string `json:"source"`
|
|
Title string `json:"title"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
func init() {
|
|
router := spinhttp.NewRouter()
|
|
|
|
router.GET("/comics/random", randomComic)
|
|
router.GET("/comics/monkeyuser", monkeyUserComic)
|
|
router.GET("/comics/xkcd", xkcdComic)
|
|
|
|
spinhttp.Handle(router.ServeHTTP)
|
|
}
|
|
|
|
func randomComic(w http.ResponseWriter, re *http.Request, params httprouter.Params) {
|
|
possibleHandlers := []httprouter.Handle{
|
|
xkcdComic,
|
|
monkeyUserComic,
|
|
}
|
|
|
|
possibleHandlers[rand.IntN(len(possibleHandlers))](w, re, params)
|
|
}
|
|
|
|
func xkcdComic(w http.ResponseWriter, re *http.Request, _ httprouter.Params) {
|
|
pageResponse, err := spinhttp.Get("https://c.xkcd.com/random/comic/")
|
|
if err != nil {
|
|
slog.Error("failed to fetch xkcd comic page", slog.String("err", err.Error()))
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
slog.Info("Fetched xkcd comic page", slog.Int("status", pageResponse.StatusCode))
|
|
|
|
defer pageResponse.Body.Close()
|
|
if pageResponse.StatusCode != http.StatusOK {
|
|
http.Error(w, pageResponse.Status, pageResponse.StatusCode)
|
|
return
|
|
}
|
|
|
|
comicDoc, err := goquery.NewDocumentFromReader(pageResponse.Body)
|
|
if err != nil {
|
|
slog.Error("failed to parse comic page", slog.String("err", err.Error()))
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
slog.Info("Extracting comic info")
|
|
|
|
ref := Comic{
|
|
Source: "xkcd",
|
|
Title: comicDoc.Find("#ctitle").Text(),
|
|
URL: comicDoc.Find("#comic > img").AttrOr("src", ""),
|
|
}
|
|
|
|
parsed, err := url.Parse(ref.URL)
|
|
if err != nil {
|
|
slog.Error("failed to parse comic URL", slog.String("err", err.Error()))
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if parsed.Scheme == "" {
|
|
parsed.Scheme = "https"
|
|
ref.URL = parsed.String()
|
|
}
|
|
|
|
w.Header().Add(contentTypeHeader, "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
if err := json.NewEncoder(w).Encode(ref); err != nil {
|
|
slog.Error("failed to encode image ref", slog.String("err", err.Error()))
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
func monkeyUserComic(w http.ResponseWriter, re *http.Request, _ httprouter.Params) {
|
|
indexResponse, err := spinhttp.Get("https://www.monkeyuser.com/index.json")
|
|
if err != nil {
|
|
slog.Error("failed to fetch MonkeyUser index", slog.String("err", err.Error()))
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
slog.Info("Fetched MonkeyUser index")
|
|
defer indexResponse.Body.Close()
|
|
if indexResponse.StatusCode != http.StatusOK {
|
|
w.WriteHeader(indexResponse.StatusCode)
|
|
return
|
|
}
|
|
|
|
var entries []MonkeyUserEntry
|
|
|
|
if err := json.NewDecoder(indexResponse.Body).Decode(&entries); err != nil {
|
|
slog.Error("failed to decode MonkeyUser index", slog.String("err", err.Error()))
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
slog.Info("Decoded MonkeyUser index")
|
|
entry := entries[rand.IntN(len(entries))]
|
|
|
|
pageResponse, err := spinhttp.Get(fmt.Sprintf("https://www.monkeyuser.com%s", entry.URL))
|
|
if err != nil {
|
|
slog.Error("failed to fetch monkeyuser comic page", slog.String("err", err.Error()))
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
defer pageResponse.Body.Close()
|
|
if pageResponse.StatusCode != http.StatusOK {
|
|
w.WriteHeader(pageResponse.StatusCode)
|
|
return
|
|
}
|
|
|
|
comicDoc, err := goquery.NewDocumentFromReader(pageResponse.Body)
|
|
if err != nil {
|
|
slog.Error("failed to parse comic page", slog.String("err", err.Error()))
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ref := Comic{
|
|
Source: "monkeyuser",
|
|
Title: entry.Title,
|
|
URL: fmt.Sprintf("https://www.monkeyuser.com%s", comicDoc.Find("div.content > p > img").AttrOr("src", "")),
|
|
}
|
|
|
|
w.Header().Add(contentTypeHeader, "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
if err := json.NewEncoder(w).Encode(ref); err != nil {
|
|
slog.Error("failed to encode image ref", slog.String("err", err.Error()))
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
}
|