mirror of
https://codeberg.org/pluja/kycnot.me
synced 2025-03-30 12:57:58 -04:00
177 lines
4.5 KiB
Go
177 lines
4.5 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"image/png"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/golang/freetype"
|
|
"github.com/golang/freetype/truetype"
|
|
"github.com/kataras/iris/v12"
|
|
"github.com/rs/zerolog/log"
|
|
"golang.org/x/image/font"
|
|
"golang.org/x/image/font/gofont/gomonobold"
|
|
|
|
"pluja.dev/kycnot.me/database"
|
|
)
|
|
|
|
func (s *Server) handleVerifyPow(c iris.Context) {
|
|
// Get id, nonce and proof from the request
|
|
id := c.Params().Get("id")
|
|
nonce := c.Params().Get("nonce")
|
|
|
|
// Verify the proof of work
|
|
valid := s.PowChallenger.PowVerifyProof(id, nonce)
|
|
|
|
// Return the result
|
|
c.JSON(iris.Map{
|
|
"valid": valid,
|
|
"status": iris.StatusOK,
|
|
})
|
|
}
|
|
|
|
// Proxies the request to the logo URL of a service by its ID
|
|
func (s *Server) handleApiPicture(c iris.Context) {
|
|
id := c.Params().Get("id")
|
|
|
|
service, err := database.Pb.GetServiceById(id)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("GetPicture: Could not get service")
|
|
respondWithPlaceholder(c, "?")
|
|
return
|
|
}
|
|
|
|
if service.LogoURL == "" {
|
|
respondWithPlaceholder(c, service.Name)
|
|
return
|
|
}
|
|
|
|
if imageData, err := s.Cache.Get(fmt.Sprintf("img-%s", service.ID)); err == nil {
|
|
ctt, _ := s.Cache.Get(fmt.Sprintf("ctt-%s", service.ID))
|
|
c.ContentType(string(ctt))
|
|
c.StatusCode(iris.StatusOK)
|
|
c.Write(imageData)
|
|
return
|
|
}
|
|
|
|
client := &http.Client{}
|
|
req, err := http.NewRequest("GET", service.LogoURL, nil)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("GetPicture: Failed to create HTTP request")
|
|
return
|
|
}
|
|
|
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.3")
|
|
req.Header.Set("Referer", service.Urls[0])
|
|
resp, err := client.Do(req)
|
|
if err != nil || resp.StatusCode != http.StatusOK {
|
|
log.Error().Err(err).Msgf("GetPicture: Could not get image for %s", service.Name)
|
|
respondWithPlaceholder(c, service.Name)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Error().Err(err).Msgf("GetPicture: Could not read response body for %s", service.Name)
|
|
respondWithPlaceholder(c, service.Name)
|
|
return
|
|
}
|
|
|
|
if bodyBytes == nil {
|
|
log.Error().Msg("GetPicture: Could not read response body")
|
|
respondWithPlaceholder(c, service.Name)
|
|
return
|
|
}
|
|
|
|
s.Cache.Set(fmt.Sprintf("img-%s", service.ID), bodyBytes)
|
|
s.Cache.Set(fmt.Sprintf("ctt-%s", service.ID), []byte(resp.Header.Get("Content-Type")))
|
|
|
|
c.ContentType(resp.Header.Get("Content-Type"))
|
|
c.StatusCode(iris.StatusOK)
|
|
c.Write(bodyBytes)
|
|
}
|
|
|
|
func respondWithPlaceholder(c iris.Context, serviceName string) {
|
|
log.Debug().Msgf("Generating placeholder for %s", serviceName)
|
|
const width, height = 250, 250
|
|
const margin = 15
|
|
firstLetter := strings.ToUpper(string(serviceName[0]))
|
|
|
|
// Create an image
|
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
|
|
// Set background to black
|
|
draw.Draw(img, img.Bounds(), image.NewUniform(color.Black), image.Point{}, draw.Src)
|
|
|
|
// Load the monospace font
|
|
f, err := truetype.Parse(gomonobold.TTF)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("could not parse font")
|
|
return
|
|
}
|
|
|
|
// Initialize freetype context
|
|
ctx := freetype.NewContext()
|
|
ctx.SetDPI(72)
|
|
ctx.SetFont(f)
|
|
|
|
// Parse hex color and set text color
|
|
var r, g, b uint8
|
|
hexColor := "#84cc16"
|
|
_, err = fmt.Sscanf(hexColor, "#%02x%02x%02x", &r, &g, &b)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Invalid hex color")
|
|
return
|
|
}
|
|
textColor := color.RGBA{R: r, G: g, B: b, A: 255}
|
|
ctx.SetSrc(image.NewUniform(textColor))
|
|
|
|
ctx.SetClip(img.Bounds())
|
|
ctx.SetDst(img)
|
|
|
|
// Dynamically calculate the maximum font size
|
|
var fontSize float64
|
|
var textWidth, textHeight int
|
|
for fontSize = 205; fontSize > 10; fontSize -= 0.5 {
|
|
ctx.SetFontSize(fontSize)
|
|
opts := truetype.Options{Size: fontSize}
|
|
face := truetype.NewFace(f, &opts)
|
|
textWidth = font.MeasureString(face, firstLetter).Round()
|
|
ascent, descent := face.Metrics().Ascent, face.Metrics().Descent
|
|
textHeight = (ascent + descent).Ceil()
|
|
|
|
if textWidth <= width-margin*2 && textHeight <= height-margin*2 {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Calculate position for the text
|
|
x := (width - textWidth) / 2
|
|
y := (height-textHeight)/2 + textHeight - int(ctx.PointToFixed(fontSize*0.2)>>6)
|
|
pt := freetype.Pt(x, y)
|
|
|
|
// Draw the text
|
|
if _, err := ctx.DrawString(firstLetter, pt); err != nil {
|
|
log.Error().Err(err).Msg("Failed to draw string")
|
|
return
|
|
}
|
|
|
|
// Encode to PNG
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, img); err != nil {
|
|
log.Error().Err(err).Msg("Failed to encode image")
|
|
return
|
|
}
|
|
|
|
c.ContentType("image/png")
|
|
c.StatusCode(iris.StatusOK)
|
|
c.Write(buf.Bytes())
|
|
}
|