<|start_filename|>routes/picture.go<|end_filename|>
package routes

import (
	"github.com/disintegration/imaging"
	"github.com/fogleman/gg"
	"github.com/pollen5/img-api/utils"
	"image"
	"net/http"
)

var pictureTemplate image.Image

func ImagePicture(w http.ResponseWriter, r *http.Request) {
	file := r.FormValue("avatar")

	if file == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'avatar' query string.\"}"))
		return
	}

	img, err := utils.GetImage(file)

	if err != nil {
		utils.JSON(w, 400, map[string]interface{}{
			"message": err.Error(),
		})

		return
	}

	img = imaging.Resize(img, 800, 650, imaging.Box)

	ctx := gg.NewContext(909, 1024)

	ctx.Rotate(-0.055)
	// backup: 27x10
	ctx.DrawImage(img, 27, 0)
	ctx.Rotate(0.055)                    // Undo rotation
	ctx.DrawImage(pictureTemplate, 0, 0) // Draw template.

	// Signal the response type.
	w.Header().Set("Content-Type", "image/png")
	// Send
	ctx.EncodePNG(w)
}

func init() {
	img, err := gg.LoadPNG("assets/picture.png")

	if err != nil {
		panic(err)
	}

	pictureTemplate = img
}


<|start_filename|>routes/bed.go<|end_filename|>
package routes

import (
	"github.com/disintegration/imaging"
	"github.com/fogleman/gg"
	"github.com/pollen5/img-api/utils"
	"image"
	"net/http"
)

var bedTemplate image.Image

func ImageBed(w http.ResponseWriter, r *http.Request) {
	file := r.FormValue("avatar")

	if file == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'avatar' query string.\"}"))
		return
	}

	img, err := utils.GetImage(file)

	if err != nil {
		utils.JSON(w, 400, map[string]interface{}{
			"message": err.Error(),
		})

		return
	}

	file = r.FormValue("target")

	if file == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'target' query string.\"}"))
		return
	}

	img2, err := utils.GetImage(file)

	if err != nil {
		utils.JSON(w, 400, map[string]interface{}{
			"message": err.Error(),
		})

		return
	}

	img = imaging.Resize(img, 100, 100, imaging.Box)
	img2 = imaging.Resize(img2, 70, 70, imaging.Box)

	ctx := gg.NewContextForImage(bedTemplate)

	ctx.DrawImage(img, 25, 100) // Image 1

	img = imaging.Resize(img, 104, 100, imaging.Box)
	ctx.DrawImage(img, 25, 300) // Image 2

	img = imaging.Resize(img, 70, 71, imaging.Box)
	ctx.DrawImage(img, 53, 450) // Image 3

	ctx.DrawImage(img2, 53, 575) // Image 4

	// Signal the response type.
	w.Header().Set("Content-Type", "image/png")
	// Send
	ctx.EncodePNG(w)
}

func init() {
	img, err := gg.LoadPNG("assets/bed.png")

	if err != nil {
		panic(err)
	}

	bedTemplate = img
}


<|start_filename|>routes/father.go<|end_filename|>
package routes

import (
	"github.com/disintegration/imaging"
	"github.com/fogleman/gg"
	"github.com/pollen5/img-api/utils"
	"image"
	"net/http"
)

var fatherTemplate image.Image

func ImageFather(w http.ResponseWriter, r *http.Request) {
	file := r.FormValue("avatar")

	if file == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'avatar' query string.\"}"))
		return
	}

	img, err := utils.GetImage(file)

	if err != nil {
		utils.JSON(w, 400, map[string]interface{}{
			"message": err.Error(),
		})

		return
	}

	text := r.FormValue("text")

	if text == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'text' query string.\"}"))
		return
	}

	if len(text) > 42 {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Text must not be longer than 42 characters\"}"))
		return
	}

	img = imaging.Resize(img, 180, 180, imaging.Box)
	img2 := imaging.Resize(img, 120, 120, imaging.Box)

	ctx := gg.NewContextForImage(fatherTemplate)

	err = ctx.LoadFontFace("assets/Roboto-Regular.ttf", 30)

	if err != nil {
		panic(err)
	}

	ctx.DrawCircle(370+60, 240+60, 60)
	ctx.Clip()
	ctx.DrawImage(img2, 370, 240)
	ctx.ResetClip()

	ctx.DrawCircle(20+90, 500+90, 90)
	ctx.Clip()
	ctx.DrawImage(img, 20, 500)

	ctx.ResetClip()
	ctx.SetRGB(0, 0, 0)

	y := float64(450)
	for _, s := range utils.WordWrap(text, 21) {
		ctx.DrawString(s, 5, y)
		y += 30
	}

	// Signal the response type.
	w.Header().Set("Content-Type", "image/png")
	// Send
	ctx.EncodePNG(w)
}

func init() {
	img, err := gg.LoadPNG("assets/father.png")

	if err != nil {
		panic(err)
	}

	fatherTemplate = img
}


<|start_filename|>utils/fetch.go<|end_filename|>
package utils

import (
	"net/http"
	"image"
	_ "image/png"
	_ "image/jpeg"
	"errors"
)

func GetImage(url string) (image.Image, error) {
	res, err := http.Get(url)

	if err != nil {
		return nil, err
	}

	// Add some limit to avoid security problems.
	// Since this is mainly for Discord Bots, 8 MB is a sane limit.
	// Since normal users without Nitro can only upload maximum of 8 MB.
	// Although most people would use this for user avatars which is even smaller.
	// Not really a big deal but still.
	if res.ContentLength > 1048 * 1048 * 8 {
		res.Body.Close()
		return nil, errors.New("File cannot be larger than 8 MB")
	}

	img, _, err := image.Decode(res.Body)
	return img, err
}


<|start_filename|>Dockerfile<|end_filename|>
FROM golang:1.14-alpine AS builder
RUN apk add git
WORKDIR /app
COPY . .
RUN go get
RUN go build

FROM alpine:3.12
WORKDIR /app
COPY --from=builder /app/assets /app/assets
COPY --from=builder /app/img-api /app/
ENTRYPOINT ["./img-api", "-h", "0.0.0.0"]

<|start_filename|>routes/tweet.go<|end_filename|>
package routes

import (
	"github.com/fogleman/gg"
	"github.com/pollen5/img-api/utils"
	"image"
	"net/http"
)

var tweetTemplate image.Image

func ImageTweet(w http.ResponseWriter, r *http.Request) {
	text := r.FormValue("text")

	if text == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'text' query string.\"}"))
		return
	}

	if len(text) > 165 {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Text must not be longer than 165 characters\"}"))
		return
	}

	ctx := gg.NewContextForImage(tweetTemplate)

	err := ctx.LoadFontFace("assets/Roboto-Regular.ttf", 50)

	if err != nil {
		panic(err)
	}

	ctx.SetRGB(0, 0, 0)

	y := float64(170)

	for _, s := range utils.WordWrap(text, 40) {
		ctx.DrawString(s, 35, y)
		y += 50
	}

	// Signal the response type.
	w.Header().Set("Content-Type", "image/png")
	// Send
	ctx.EncodePNG(w)
}

func init() {
	img, err := gg.LoadPNG("assets/tweet.png")

	if err != nil {
		panic(err)
	}

	tweetTemplate = img
}


<|start_filename|>routes/mask.go<|end_filename|>
package routes

import (
	"github.com/disintegration/imaging"
	"github.com/fogleman/gg"
	"github.com/pollen5/img-api/utils"
	"image"
	"net/http"
)

var maskTemplate image.Image

// Idk if this was a good idea but it's corona time so why not.
func ImageMask(w http.ResponseWriter, r *http.Request) {
	file := r.FormValue("avatar")

	if file == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'avatar' query string.\"}"))
		return
	}

	img, err := utils.GetImage(file)

	if err != nil {
		utils.JSON(w, 400, map[string]interface{}{
			"message": err.Error(),
		})

		return
	}

	img = imaging.Resize(img, 512, 512, imaging.Box)

	ctx := gg.NewContextForImage(img)

	ctx.DrawImage(maskTemplate, 100, 256)

	// Signal the response type.
	w.Header().Set("Content-Type", "image/png")
	// Send
	ctx.EncodePNG(w)
}

func init() {
	img, err := gg.LoadPNG("assets/mask.png")

	if err != nil {
		panic(err)
	}

	maskTemplate = imaging.Resize(img, 300, 250, imaging.Box)
}


<|start_filename|>routes/dipshit.go<|end_filename|>
package routes

import (
	"github.com/fogleman/gg"
	"image"
	"net/http"
)

var dipshitTemplate image.Image

func ImageDipshit(w http.ResponseWriter, r *http.Request) {
	text := r.FormValue("text")

	if text == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'text' query string.\"}"))
		return
	}

	if len(text) > 33 {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Text must not be longer than 33 characters\"}"))
		return
	}

	ctx := gg.NewContextForImage(dipshitTemplate)

	err := ctx.LoadFontFace("assets/Roboto-Regular.ttf", 20)

	if err != nil {
		panic(err)
	}

	ctx.SetRGB(0, 0, 0)
	ctx.DrawString(text, 140, 76)

	// Signal the response type.
	w.Header().Set("Content-Type", "image/png")
	// Send
	ctx.EncodePNG(w)
}

func init() {
	img, err := gg.LoadPNG("assets/dipshit.png")

	if err != nil {
		panic(err)
	}

	dipshitTemplate = img
}


<|start_filename|>routes/ping.go<|end_filename|>
package routes

import (
	"net/http"
)

// Ping is used to check if the server is alive.
func Ping(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("{\"message\": \"Pong!\"}"))
}


<|start_filename|>routes/achievement.go<|end_filename|>
package routes

import (
	"github.com/disintegration/imaging"
	"github.com/fogleman/gg"
	"github.com/pollen5/img-api/utils"
	"image"
	"net/http"
)

var achievementTemplate image.Image

func ImageAchievement(w http.ResponseWriter, r *http.Request) {
	file := r.FormValue("avatar")

	if file == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'avatar' query string.\"}"))
		return
	}

	img, err := utils.GetImage(file)

	if err != nil {
		utils.JSON(w, 400, map[string]interface{}{
			"message": err.Error(),
		})

		return
	}

	text := r.FormValue("text")

	if text == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'text' query string.\"}"))
		return
	}

	if len(text) > 21 {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Text must not be longer than 21 characters\"}"))
		return
	}

	img = imaging.Resize(img, 40, 40, imaging.Box)

	ctx := gg.NewContextForImage(achievementTemplate)

	ctx.DrawCircle(10+20, 10+20, 20)
	ctx.Clip()
	ctx.DrawImage(img, 10, 10)

	ctx.ResetClip()

	err = ctx.LoadFontFace("assets/Mojangles.ttf", 20)

	if err != nil {
		panic(err)
	}

	ctx.SetRGB(1, 1, 1)
	ctx.DrawString(text, 59, 50)

	// Signal the response type.
	w.Header().Set("Content-Type", "image/png")
	// Send
	ctx.EncodePNG(w)
}

func init() {
	img, err := gg.LoadPNG("assets/achievement.png")

	if err != nil {
		panic(err)
	}

	achievementTemplate = img
}


<|start_filename|>main.go<|end_filename|>
package main

import (
	"flag"
	"fmt"
	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
	"github.com/pollen5/img-api/routes"
	"log"
	"net/http"
)

var port = flag.Int("p", 3030, "Change the port to listen to.")
var addr = flag.String("h", "127.0.0.1", "")
var secret = flag.String("s", "", "Set a password")
var dev = flag.Bool("d", false, "Start in development mode (disable browser cache)")

var (
	version = "dev"
	commit = "none"
)

func main() {
	flag.Parse()

	router := chi.NewRouter()

	// Setup middlewares
	router.Use(middleware.Logger)
	router.Use(middleware.Recoverer)

	if *secret != "" {
		router.Use(func(next http.Handler) http.Handler {
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				// Localhost requests are immune to Authorization.
				if r.Host == fmt.Sprintf("127.0.0.1:%d", *port) || r.Host == fmt.Sprintf("localhost:%d", *port) {
					next.ServeHTTP(w, r)
					return
				}

				auth := r.Header.Get("Authorization")

				if auth != *secret {
					w.WriteHeader(401)
					w.Header().Set("Content-Type", "application/json")
					w.Write([]byte("{\"message\": \"Unauthorized\"}"))
					return
				}

				next.ServeHTTP(w, r)
			})
		})
	}

	if *dev {
		router.Use(middleware.NoCache)
		router.Mount("/debug", middleware.Profiler())
		log.Print("Starting in development mode. (Browser cache will be disabled and profiler will be mounted.)")
	}

	// Initialize all routes.
	router.Get("/ping", routes.Ping)
	router.Get("/stats", routes.Stats)

	// Image routes.
	router.Get("/religion", routes.ImageReligion)
	router.Get("/beautiful", routes.ImageBeautiful)
	router.Get("/fear", routes.ImageFear)
	router.Get("/sacred", routes.ImageSacred)
	router.Get("/painting", routes.ImagePainting)
	router.Get("/color", routes.ImageColor)
	router.Get("/delete", routes.ImageDelete)
	router.Get("/garbage", routes.ImageGarbage)
	router.Get("/tom", routes.ImageTom)
	router.Get("/bed", routes.ImageBed)
	router.Get("/crush", routes.ImageCrush)
	router.Get("/patrick", routes.ImagePatrick)
	router.Get("/respect", routes.ImageRespect)
	router.Get("/dipshit", routes.ImageDipshit)
	router.Get("/picture", routes.ImagePicture)
	router.Get("/tweet", routes.ImageTweet)
	router.Get("/truth", routes.ImageTruth)
	router.Get("/bobross", routes.ImageBobross)
	router.Get("/mask", routes.ImageMask)
	router.Get("/father", routes.ImageFather)
	router.Get("/achievement", routes.ImageAchievement)
	router.Get("/kaguya", routes.ImageKaguya)

	// Non-Image routes.
	router.Get("/dominantColor", routes.DominantColor)

	fmt.Printf("Img API Version %s (Commit: %s)\n", version, commit)
	fmt.Printf("Running on http://%s:%d\n", *addr, *port)

	// Start the server.
	log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *addr, *port), router))
}


<|start_filename|>utils/wrap.go<|end_filename|>
package utils

import (
	"regexp"
	"strconv"
)

// Literally all the existing word wrap implementations suck.
// They split by spaces so "aaa aaa" works but "aaaaaa" wouldn't get word wrapped.
// That is not how real word wrapping works. It should still put them on a newline.
// But we should also respect words so as to not cut words.
// E.g if "hello world" exceeded then "world" should be in a newline but not trimmed like "wo\nrld"
func WordWrap(s string, limit int) []string {
	if len(s) < limit {
		return []string{s}
	}

	// Ok maybe regex is not the best solution but I can't think right now.
	// Plus I already did this in JavaScript before so I know it works.
	regex := regexp.MustCompile(".{1," + strconv.Itoa(limit) + "}(\\s|$)" + "|.{" + strconv.Itoa(limit) + "}|.+$")
	return regex.FindAllString(s, -1)
}


<|start_filename|>routes/stats.go<|end_filename|>
package routes

import (
	"encoding/json"
	"net/http"
	"runtime"
	"time"
)

var uptime int64

// XXX: Keep this version updated.
const VERSION = "1.2.4"

// Stats returns some information about the API Server process, like how much memory it uses.
func Stats(w http.ResponseWriter, r *http.Request) {
	stats := &runtime.MemStats{}

	if r.FormValue("noStats") == "true" {
		stats = nil
	} else {
		runtime.ReadMemStats(stats)
	}

	bytes, err := json.Marshal(map[string]interface{}{
		"version":    VERSION,
		"stats":      stats,
		"uptime":     time.Now().Unix() - uptime,
		"goroutines": runtime.NumGoroutine(),
	})

	if err != nil {
		panic(err)
	}

	w.Write(bytes)
}

func init() {
	uptime = time.Now().Unix()
}


<|start_filename|>utils/json.go<|end_filename|>
package utils

import (
	"net/http"
	"encoding/json"
)

func JSON(w http.ResponseWriter, status int, data map[string]interface{}) {
	bytes, err := json.Marshal(data)

	if err != nil {
		panic(err)
	}

	w.WriteHeader(status)
	w.Write(bytes)
}


<|start_filename|>routes/dominantColor.go<|end_filename|>
package routes

import (
	"github.com/pollen5/img-api/utils"
	"github.com/cenkalti/dominantcolor"
	"net/http"
)

func DominantColor(w http.ResponseWriter, r *http.Request) {
	file := r.FormValue("avatar")

	if file == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'avatar' query string.\"}"))
		return
	}

	img, err := utils.GetImage(file)

	if err != nil {
		utils.JSON(w, 400, map[string]interface{}{
			"message": err.Error(),
		})

		return
	}

	color := dominantcolor.Find(img)
	hex := dominantcolor.Hex(color)

	utils.JSON(w, 200, map[string]interface{}{
		"hex": hex,
		"rgb": map[string]uint8{ "r": color.R, "g": color.G, "b": color.B },
	})
}


<|start_filename|>routes/truth.go<|end_filename|>
package routes

import (
	"github.com/disintegration/imaging"
	"github.com/fogleman/gg"
	"github.com/pollen5/img-api/utils"
	"image"
	"net/http"
)

var truthTemplate image.Image

func ImageTruth(w http.ResponseWriter, r *http.Request) {
	file := r.FormValue("avatar")

	if file == "" {
		w.WriteHeader(http.StatusBadRequest)
		w.Header().Set("Content-Type", "application/json")
		w.Write([]byte("{\"message\": \"Missing 'avatar' query string.\"}"))
		return
	}

	img, err := utils.GetImage(file)

	if err != nil {
		utils.JSON(w, 400, map[string]interface{}{
			"message": err.Error(),
		})

		return
	}

	img = imaging.Resize(img, 200, 250, imaging.Box)

	ctx := gg.NewContext(539, 1024)

	ctx.Rotate(-0.01)
	ctx.DrawImage(img, 20, 600)
	ctx.Rotate(0.01) // Undo rotation

	ctx.DrawImage(truthTemplate, 0, 0)

	// Signal the response type.
	w.Header().Set("Content-Type", "image/png")
	// Send
	ctx.EncodePNG(w)
}

func init() {
	img, err := gg.LoadPNG("assets/truth.png")

	if err != nil {
		panic(err)
	}

	truthTemplate = img
}
