package handler import ( "bytes" "encoding/json" "encoding/xml" "errors" "fmt" "github.com/golang/freetype/truetype" "github.com/gorilla/mux" "golang.org/x/image/draw" "golang.org/x/image/font" "golang.org/x/image/font/gofont/gobold" _ "golang.org/x/image/font/gofont/gobold" "golang.org/x/image/math/fixed" "html/template" "image" "image/jpeg" "image/png" "io" "os" "path/filepath" "status/model" "strconv" "strings" "time" //"image/png" "net/http" ) func exist(path string) bool { _, err := os.Stat(path) return err == nil } func getPath(r rune, emojipath string) (string, error) { if r > 47 && r < 58 { return "", errors.New("numeric char") } name := fmt.Sprintf("%.4x", r) var path string path = fmt.Sprintf("%s/emoji_u%s.png", emojipath, name) if !exist(path) { return "", fmt.Errorf("%s does NOT exist", path) } return path, nil } func loadEmoji(r rune, size int, emojipath string) (image.Image, bool) { var img image.Image path, err := getPath(r, emojipath) if err != nil { //fmt.Fprintln(os.Stderr, err) return img, false } fp, err := os.Open(path) if err != nil { fmt.Fprintln(os.Stderr, err) return img, false } defer fp.Close() img, _, err = image.Decode(fp) if err != nil { fmt.Fprintln(os.Stderr, err) return img, false } rect := image.Rect(0, 0, size, size) dst := image.NewRGBA(rect) draw.ApproxBiLinear.Scale(dst, rect, img, img.Bounds(), draw.Over, nil) return dst, true } func renderLine(img image.Image, dr *font.Drawer, s, emojipath string) { size := dr.Face.Metrics().Ascent.Floor() + dr.Face.Metrics().Descent.Floor() for _, r := range s { emoji, ok := loadEmoji(r, size, emojipath) if ok { // Drawer.Dot is glyph baseline of next glyph // get left/top coordinates for draw.Draw(). p := image.Pt(dr.Dot.X.Floor(), dr.Dot.Y.Floor()-dr.Face.Metrics().Ascent.Floor()) rect := image.Rect(0, 0, size, size).Add(p) // draw emoji and ascend baseline draw.Draw(img.(draw.Image), rect, emoji, image.ZP, draw.Over) dr.Dot.X += fixed.I(size) } else { // fallback: use normal glyph } } } func renderText(img image.Image, face font.Face, text, emojipath string) error { dr := &font.Drawer{ Dst: img.(draw.Image), Src: image.White, Face: face, Dot: fixed.Point26_6{}, } for _, s := range strings.Split(text, "\n") { dr.Dot.X = fixed.I(6) dr.Dot.Y = fixed.I(13) renderLine(img, dr, s, emojipath) } return nil } func outputJPEG(img image.Image, w io.Writer) error { buf := new(bytes.Buffer) err := jpeg.Encode(buf, img, &jpeg.Options{Quality: 100}) if err != nil { return err } _, err = io.Copy(w, buf) if err != nil { return err } return nil } func (h *Handler) showManageView(w http.ResponseWriter, r *http.Request) { logged, err := h.sess.Get(r) if err != nil { unauthorized(w, r) return } var page int64 = 0 if val, ok := r.URL.Query()["page"]; ok && len(val[0]) == 1 { page, _ = strconv.ParseInt(val[0], 10, 64) } statuses, showMore, err := h.storage.StatusByUsername(logged, 20, page) if err != nil { serverError(w, err) return } session, err := h.sess.Store.Get(r, "status") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } flash := "" if flashes := session.Flashes(); len(flashes) > 0 { flash = flashes[0].(string) } session.Save(r, w) h.renderLayout(w, "manage", map[string]interface{}{ "statuses": statuses, "showMore": showMore, "page": page, "flash": flash, "next_page": page + 1, "prev_page": page - 1, }, logged) } func (h *Handler) showUserView(w http.ResponseWriter, r *http.Request) { logged, _ := h.sess.Get(r) var page int64 = 0 if val, ok := r.URL.Query()["page"]; ok && len(val[0]) == 1 { page, _ = strconv.ParseInt(val[0], 10, 64) } username := mux.Vars(r)["user"] user, err := h.storage.UserByName(username) if err != nil { notFound(w) return } statuses, showMore, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 20, page) if err != nil { serverError(w, err) return } face := "" if len(statuses) > 0 { face = statuses[0].Face } h.renderLayout(w, "user", map[string]interface{}{ "user": username, "statuses": statuses, "face": face, "homepage": user.Homepage, "about": template.HTML(user.About), "picture": user.Picture, "email": user.Email, "showMore": showMore, "page": page, "next_page": page + 1, "prev_page": page - 1, }, logged) } type statusjson struct { Author string `json:"author"` Content string `json:"content"` Face string `json:"face"` TimeAgo string `json:"timeAgo"` } func (h *Handler) showUserStatusJSONView(w http.ResponseWriter, r *http.Request) { user := mux.Vars(r)["user"] if !h.storage.UserExists(user) { notFound(w) return } statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0) if err != nil { serverError(w, err) return } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Content-Type", "application/json") var res statusjson if len(statuses) > 0 { res.Author = statuses[0].User res.Content = statuses[0].Content res.Face = statuses[0].Face res.TimeAgo = statuses[0].TimeAgo() } json.NewEncoder(w).Encode(res) } func (h *Handler) showUserStatusView(w http.ResponseWriter, r *http.Request) { statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0) if err != nil { serverError(w, err) return } var status model.Status if len(statuses) > 0 { status = statuses[0] } h.view("status").Execute(w, map[string]interface{}{"status": status}) } func (h *Handler) showUserStatusImageViewEmoji(w http.ResponseWriter, r *http.Request) { user := mux.Vars(r)["user"] if !h.storage.UserExists(user) { notFound(w) return } statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0) if err != nil { serverError(w, err) return } face := "🙂" if len(statuses) > 0 { face = statuses[0].Face } f, err := os.Open(filepath.Join(h.cfg.AssetsDir, "badge.png")) if err != nil { serverError(w, err) return } img, err := png.Decode(f) if err != nil { serverError(w, err) return } ft, err := truetype.Parse(gobold.TTF) if err != nil { serverError(w, err) return } opt := truetype.Options{ Size: 14, DPI: 0, Hinting: 0, GlyphCacheEntries: 0, SubPixelsX: 0, SubPixelsY: 0, } err = renderText(img, truetype.NewFace(ft, &opt), fmt.Sprintf("%s", face), h.cfg.EmojiFolder) if err != nil { serverError(w, err) return } err = outputJPEG(img, w) if err != nil { serverError(w, err) return } } func truncate(s string, max int) string { if len(s) > max { return s[:max] + "..." } return s } func (h *Handler) showAtomView(w http.ResponseWriter, r *http.Request) { username := mux.Vars(r)["user"] user, err := h.storage.UserByName(username) if err != nil { notFound(w) return } feed := Feed{ Title: user.Name, ID: fmt.Sprintf("https://status.cafe/users/%s/", user.Name), Author: &Person{ Name: user.Name, URI: fmt.Sprintf("https://status.cafe/users/%s", user.Name), }, Updated: Time(time.Now()), Link: []Link{ { Rel: "self", Href: fmt.Sprintf("https://status.cafe/users/%s.atom", user.Name), }, { Rel: "alternate", Href: fmt.Sprintf("https://status.cafe/users/%s", user.Name), Type: "text/html", }, }, Icon: user.Picture, Logo: user.Picture, } statuses, _, err := h.storage.StatusByUsername(user.Name, 20, 0) if err != nil { serverError(w, err) return } for _, status := range statuses { feed.Entry = append(feed.Entry, createAtomEntryFromStatus(status)) } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Content-Type", "application/atom+xml") var data []byte data, err = xml.MarshalIndent(&feed, "", " ") if err != nil { serverError(w, err) } w.Write([]byte(xml.Header + string(data))) }