From 7c87d18d60b620fffea3dd2125a71d7b667fb1a4 Mon Sep 17 00:00:00 2001 From: Akmal Date: Sat, 13 Jul 2024 06:17:00 +0700 Subject: [PATCH] All stdlib --- go.mod | 6 +- go.sum | 13 +-- handlers/embed.go | 205 +++++++++++++++++++++-------------------- handlers/grid.go | 146 ++++++++++++++++------------- handlers/images.go | 37 ++++---- handlers/oembed.go | 36 ++++---- handlers/videos.go | 46 ++++----- main.go | 63 ++++++------- utils/crawlerdetect.go | 72 +++++++-------- views/embed.jade.go | 6 +- views/home.jade.go | 5 +- views/jade.go | 11 +-- views/oembed.manual.go | 6 +- 13 files changed, 321 insertions(+), 331 deletions(-) diff --git a/go.mod b/go.mod index 4b9e821..603795c 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 github.com/cockroachdb/pebble v1.1.1 github.com/elastic/go-freelru v0.13.0 - github.com/gofiber/fiber/v2 v2.52.4 + github.com/julienschmidt/httprouter v1.3.0 github.com/kelindar/binary v1.0.19 github.com/rs/zerolog v1.33.0 github.com/tdewolff/parse/v2 v2.7.15 @@ -36,23 +36,19 @@ require ( github.com/getsentry/sentry-go v0.28.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.54.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect diff --git a/go.sum b/go.sum index 17d54ee..966aa45 100644 --- a/go.sum +++ b/go.sum @@ -39,16 +39,14 @@ github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= -github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kelindar/binary v1.0.19 h1:DNyQCtKjkLhBh9pnP49OWREddLB0Mho+1U/AOt/Qzxw= github.com/kelindar/binary v1.0.19/go.mod h1:/twdz8gRLNMffx0U4UOgqm1LywPs6nd9YK2TX52MDh8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -65,8 +63,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -82,9 +78,6 @@ github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= @@ -108,8 +101,6 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/handlers/embed.go b/handlers/embed.go index 01e6724..415694b 100644 --- a/handlers/embed.go +++ b/handlers/embed.go @@ -5,11 +5,12 @@ import ( "instafix/utils" "instafix/views" "instafix/views/model" + "net/http" "net/url" "strconv" "strings" - "github.com/gofiber/fiber/v2" + "github.com/julienschmidt/httprouter" "github.com/valyala/bytebufferpool" ) @@ -26,122 +27,122 @@ func mediaidToCode(mediaID int) string { return shortCode } -func Embed() fiber.Handler { - return func(c *fiber.Ctx) error { - c.Set("Content-Type", "text/html; charset=utf-8") - viewsData := &model.ViewsData{} - viewsBuf := bytebufferpool.Get() - defer bytebufferpool.Put(viewsBuf) - - postID := c.Params("postID") - mediaNum, err := c.ParamsInt("mediaNum", 0) - if err != nil { - viewsData.Description = "Invalid media number" - views.Embed(viewsData, viewsBuf) - return c.Send(viewsBuf.Bytes()) - } - imgIndex := c.Query("img_index") +func Embed(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + viewsData := &model.ViewsData{} + viewsBuf := bytebufferpool.Get() + defer bytebufferpool.Put(viewsBuf) + + var err error + var mediaNum int + urlQuery := r.URL.Query() + postID := ps.ByName("postID") + mediaNumParams := ps.ByName("mediaNum") + if mediaNumParams == "" { + imgIndex := urlQuery.Get("img_index") if imgIndex != "" { - mediaNum, err = strconv.Atoi(imgIndex) - if err != nil { - viewsData.Description = "Invalid img_index parameter" - views.Embed(viewsData, viewsBuf) - return c.Send(viewsBuf.Bytes()) - } + mediaNumParams = imgIndex } - - direct, err := strconv.ParseBool(c.Query("direct", "false")) + mediaNum, err = strconv.Atoi(mediaNumParams) if err != nil { - viewsData.Description = "Invalid direct parameter" - views.Embed(viewsData, viewsBuf) - return c.Send(viewsBuf.Bytes()) + viewsData.Description = "Invalid img_index parameter" + views.Embed(viewsData, w) + return } + } - isGallery, err := strconv.ParseBool(c.Query("gallery", "false")) - if err != nil { - viewsData.Description = "Invalid gallery parameter" - views.Embed(viewsData, viewsBuf) - return c.Send(viewsBuf.Bytes()) - } + direct, err := strconv.ParseBool(urlQuery.Get("direct")) + if err != nil { + viewsData.Description = "Invalid direct parameter" + views.Embed(viewsData, w) + return + } - // Stories use mediaID (int) instead of postID - if strings.Contains(c.Path(), "/stories/") { - mediaID, err := strconv.Atoi(postID) - if err != nil { - viewsData.Description = "Invalid postID" - views.Embed(viewsData, viewsBuf) - c.Send(viewsBuf.Bytes()) - return nil - } - postID = mediaidToCode(mediaID) - } + isGallery, err := strconv.ParseBool(urlQuery.Get("gallery")) + if err != nil { + viewsData.Description = "Invalid gallery parameter" + views.Embed(viewsData, w) + return + } - // If User-Agent is not bot, redirect to Instagram - viewsData.Title = "InstaFix" - viewsData.URL = "https://instagram.com" + c.Path() - if !utils.IsBot(c.Request().Header.UserAgent()) { - return c.Redirect(viewsData.URL) + // Stories use mediaID (int) instead of postID + if strings.Contains(r.URL.Path, "/stories/") { + mediaID, err := strconv.Atoi(postID) + if err != nil { + viewsData.Description = "Invalid postID" + views.Embed(viewsData, w) + return } + postID = mediaidToCode(mediaID) + } - item, err := scraper.GetData(postID) - if err != nil || len(item.Medias) == 0 { - viewsData.Description = "Post might not be available" - views.Embed(viewsData, viewsBuf) - return c.Send(viewsBuf.Bytes()) - } + // If User-Agent is not bot, redirect to Instagram + viewsData.Title = "InstaFix" + viewsData.URL = "https://instagram.com" + r.URL.Path + if !utils.IsBot(r.Header.Get("User-Agent")) { + w.Header().Set("Location", viewsData.URL) + return + } - if mediaNum > len(item.Medias) { - viewsData.Description = "Media number out of range" - views.Embed(viewsData, viewsBuf) - return c.Send(viewsBuf.Bytes()) - } else if len(item.Username) == 0 { - viewsData.Description = "Post not found" - views.Embed(viewsData, viewsBuf) - return c.Send(viewsBuf.Bytes()) - } + item, err := scraper.GetData(postID) + if err != nil || len(item.Medias) == 0 { + viewsData.Description = "Post might not be available" + views.Embed(viewsData, w) + return + } - var sb strings.Builder - sb.Grow(32) // 32 bytes should be enough for most cases + if mediaNum > len(item.Medias) { + viewsData.Description = "Media number out of range" + views.Embed(viewsData, w) + return + } else if len(item.Username) == 0 { + viewsData.Description = "Post not found" + views.Embed(viewsData, w) + return + } - viewsData.Title = "@" + item.Username - // Gallery do not have any caption - if !isGallery { - viewsData.Description = item.Caption - if len(viewsData.Description) > 255 { - viewsData.Description = utils.Substr(viewsData.Description, 0, 250) + "..." - } - } + var sb strings.Builder + sb.Grow(32) // 32 bytes should be enough for most cases - typename := item.Medias[max(1, mediaNum)-1].TypeName - isImage := strings.Contains(typename, "Image") || strings.Contains(typename, "StoryVideo") - switch { - case mediaNum == 0 && isImage && len(item.Medias) > 1: - viewsData.Card = "summary_large_image" - sb.WriteString("/grid/") - sb.WriteString(postID) - viewsData.ImageURL = sb.String() - case isImage: - viewsData.Card = "summary_large_image" - sb.WriteString("/images/") - sb.WriteString(postID) - sb.WriteString("/") - sb.WriteString(strconv.Itoa(max(1, mediaNum))) - viewsData.ImageURL = sb.String() - default: - viewsData.Card = "player" - sb.WriteString("/videos/") - sb.WriteString(postID) - sb.WriteString("/") - sb.WriteString(strconv.Itoa(max(1, mediaNum))) - viewsData.VideoURL = sb.String() - viewsData.OEmbedURL = c.BaseURL() + "/oembed?text=" + url.QueryEscape(viewsData.Description) + "&url=" + viewsData.URL + viewsData.Title = "@" + item.Username + // Gallery do not have any caption + if !isGallery { + viewsData.Description = item.Caption + if len(viewsData.Description) > 255 { + viewsData.Description = utils.Substr(viewsData.Description, 0, 250) + "..." } + } - if direct { - return c.Redirect(sb.String()) - } + typename := item.Medias[max(1, mediaNum)-1].TypeName + isImage := strings.Contains(typename, "Image") || strings.Contains(typename, "StoryVideo") + switch { + case mediaNum == 0 && isImage && len(item.Medias) > 1: + viewsData.Card = "summary_large_image" + sb.WriteString("/grid/") + sb.WriteString(postID) + viewsData.ImageURL = sb.String() + case isImage: + viewsData.Card = "summary_large_image" + sb.WriteString("/images/") + sb.WriteString(postID) + sb.WriteString("/") + sb.WriteString(strconv.Itoa(max(1, mediaNum))) + viewsData.ImageURL = sb.String() + default: + viewsData.Card = "player" + sb.WriteString("/videos/") + sb.WriteString(postID) + sb.WriteString("/") + sb.WriteString(strconv.Itoa(max(1, mediaNum))) + viewsData.VideoURL = sb.String() + viewsData.OEmbedURL = r.Host + "/oembed?text=" + url.QueryEscape(viewsData.Description) + "&url=" + viewsData.URL + } - views.Embed(viewsData, viewsBuf) - return c.Send(viewsBuf.Bytes()) + if direct { + w.Header().Set("Location", sb.String()) + return } + + views.Embed(viewsData, w) + return } diff --git a/handlers/grid.go b/handlers/grid.go index 6b4f01b..0aaf8db 100644 --- a/handlers/grid.go +++ b/handlers/grid.go @@ -4,6 +4,7 @@ import ( "image" "image/jpeg" scraper "instafix/handlers/scraper" + "io" "math" "net" "net/http" @@ -14,7 +15,7 @@ import ( "time" "github.com/RyanCarrier/dijkstra/v2" - "github.com/gofiber/fiber/v2" + "github.com/julienschmidt/httprouter" "github.com/rs/zerolog/log" "golang.org/x/image/draw" ) @@ -124,81 +125,94 @@ func GenerateGrid(images []image.Image) (image.Image, error) { return canvas, nil } -func Grid() fiber.Handler { - return func(c *fiber.Ctx) error { - postID := c.Params("postID") - gridFname := filepath.Join("static", postID+".jpeg") +func Grid(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + postID := ps.ByName("postID") + gridFname := filepath.Join("static", postID+".jpeg") - // If already exists, return - if _, ok := scraper.LRU.Get(gridFname); ok { - return c.SendFile(gridFname) + // If already exists, return + if _, ok := scraper.LRU.Get(gridFname); ok { + f, err := os.Open(gridFname) + if err != nil { + return } + defer f.Close() + w.Header().Set("Content-Type", "image/jpeg") + io.Copy(w, f) + return + } - item, err := scraper.GetData(postID) - if err != nil { - return err + item, err := scraper.GetData(postID) + if err != nil { + return + } + + // Filter media only include image + var mediaURLs []string + for _, media := range item.Medias { + if !strings.Contains(media.TypeName, "Image") { + continue } + mediaURLs = append(mediaURLs, media.URL) + } + + if len(item.Medias) == 1 || len(mediaURLs) == 1 { + w.Header().Set("Location", "/images/"+postID+"/1") + return + } - // Filter media only include image - var mediaURLs []string - for _, media := range item.Medias { - if !strings.Contains(media.TypeName, "Image") { - continue + var wg sync.WaitGroup + images := make([]image.Image, len(mediaURLs)) + client := http.Client{Transport: transport, Timeout: timeout} + for i, mediaURL := range mediaURLs { + wg.Add(1) + + go func(i int, url string) { + defer wg.Done() + req, err := http.NewRequest(http.MethodGet, url, http.NoBody) + if err != nil { + return } - mediaURLs = append(mediaURLs, media.URL) - } - if len(item.Medias) == 1 || len(mediaURLs) == 1 { - return c.Redirect("/images/" + postID + "/1") - } + // Make request client.Get + res, err := client.Do(req) + if err != nil { + log.Error().Str("postID", postID).Err(err).Msg("Failed to get image") + return + } + defer res.Body.Close() - var wg sync.WaitGroup - images := make([]image.Image, len(mediaURLs)) - client := http.Client{Transport: transport, Timeout: timeout} - for i, mediaURL := range mediaURLs { - wg.Add(1) - - go func(i int, url string) { - defer wg.Done() - req, err := http.NewRequest(http.MethodGet, url, http.NoBody) - if err != nil { - return - } - - // Make request client.Get - res, err := client.Do(req) - if err != nil { - log.Error().Str("postID", postID).Err(err).Msg("Failed to get image") - return - } - defer res.Body.Close() - - images[i], err = jpeg.Decode(res.Body) - if err != nil { - log.Error().Str("postID", postID).Err(err).Msg("Failed to decode image") - return - } - }(i, mediaURL) - } - wg.Wait() + images[i], err = jpeg.Decode(res.Body) + if err != nil { + log.Error().Str("postID", postID).Err(err).Msg("Failed to decode image") + return + } + }(i, mediaURL) + } + wg.Wait() - // Create grid Images - grid, err := GenerateGrid(images) - if err != nil { - return err - } + // Create grid Images + grid, err := GenerateGrid(images) + if err != nil { + return + } - // Write grid to static folder - f, err := os.Create(gridFname) - if err != nil { - return err - } - defer f.Close() + // Write grid to static folder + f, err := os.Create(gridFname) + if err != nil { + return + } + defer f.Close() - if err := jpeg.Encode(f, grid, &jpeg.Options{Quality: 80}); err != nil { - return err - } - scraper.LRU.Add(gridFname, true) - return c.SendFile(gridFname) + if err := jpeg.Encode(f, grid, &jpeg.Options{Quality: 80}); err != nil { + return + } + scraper.LRU.Add(gridFname, true) + f, err = os.Open(gridFname) + if err != nil { + return } + defer f.Close() + w.Header().Set("Content-Type", "image/jpeg") + io.Copy(w, f) + return } diff --git a/handlers/images.go b/handlers/images.go index 6817e7f..1a25135 100644 --- a/handlers/images.go +++ b/handlers/images.go @@ -2,28 +2,29 @@ package handlers import ( scraper "instafix/handlers/scraper" + "net/http" + "strconv" - "github.com/gofiber/fiber/v2" + "github.com/julienschmidt/httprouter" ) -func Images() fiber.Handler { - return func(c *fiber.Ctx) error { - postID := c.Params("postID") - mediaNum, err := c.ParamsInt("mediaNum", 1) - if err != nil { - return err - } +func Images(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + postID := ps.ByName("postID") + mediaNum, err := strconv.Atoi(ps.ByName("mediaNum")) + if err != nil { + return + } - item, err := scraper.GetData(postID) - if err != nil { - return err - } + item, err := scraper.GetData(postID) + if err != nil { + return + } - // Redirect to image URL - if mediaNum > len(item.Medias) { - return err - } - imageURL := item.Medias[max(1, mediaNum)-1].URL - return c.Redirect(imageURL, fiber.StatusFound) + // Redirect to image URL + if mediaNum > len(item.Medias) { + return } + imageURL := item.Medias[max(1, mediaNum)-1].URL + w.Header().Set("Location", imageURL) + return } diff --git a/handlers/oembed.go b/handlers/oembed.go index dedc7a8..9a5da06 100644 --- a/handlers/oembed.go +++ b/handlers/oembed.go @@ -3,30 +3,26 @@ package handlers import ( "instafix/views" "instafix/views/model" + "net/http" "github.com/PurpleSec/escape" - "github.com/gofiber/fiber/v2" - "github.com/valyala/bytebufferpool" + "github.com/julienschmidt/httprouter" ) -func OEmbed() fiber.Handler { - return func(c *fiber.Ctx) error { - headingText := c.Query("text") - headingURL := c.Query("url") - if headingText == "" || headingURL == "" { - return c.SendStatus(fiber.StatusBadRequest) - } - c.Set("Content-Type", "application/json") - viewsBuf := bytebufferpool.Get() - defer bytebufferpool.Put(viewsBuf) - - // Totally safe 100% valid template 👍 - OEmbedData := &model.OEmbedData{ - Text: escape.JSON(headingText), - URL: headingURL, - } +func OEmbed(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + headingText := r.URL.Query().Get("text") + headingURL := r.URL.Query().Get("url") + if headingText == "" || headingURL == "" { + return + } + w.Header().Set("Content-Type", "application/json") - views.OEmbed(OEmbedData, viewsBuf) - return c.Send(viewsBuf.Bytes()) + // Totally safe 100% valid template 👍 + OEmbedData := &model.OEmbedData{ + Text: escape.JSON(headingText), + URL: headingURL, } + + views.OEmbed(OEmbedData, w) + return } diff --git a/handlers/videos.go b/handlers/videos.go index d7e7330..06854da 100644 --- a/handlers/videos.go +++ b/handlers/videos.go @@ -2,34 +2,36 @@ package handlers import ( scraper "instafix/handlers/scraper" + "net/http" + "strconv" "strings" - "github.com/gofiber/fiber/v2" + "github.com/julienschmidt/httprouter" ) -func Videos() fiber.Handler { - return func(c *fiber.Ctx) error { - postID := c.Params("postID") - mediaNum, err := c.ParamsInt("mediaNum", 1) - if err != nil { - return err - } +func Videos(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + postID := ps.ByName("postID") + mediaNum, err := strconv.Atoi(ps.ByName("mediaNum")) + if err != nil { + return + } - item, err := scraper.GetData(postID) - if err != nil { - return err - } + item, err := scraper.GetData(postID) + if err != nil { + return + } - // Redirect to image URL - if mediaNum > len(item.Medias) { - return err - } - videoURL := item.Medias[max(1, mediaNum)-1].URL + // Redirect to image URL + if mediaNum > len(item.Medias) { + return + } + videoURL := item.Medias[max(1, mediaNum)-1].URL - // Redirect to proxy if not TelegramBot in User-Agent - if strings.Contains(c.Get("User-Agent"), "TelegramBot") { - return c.Redirect(videoURL, fiber.StatusFound) - } - return c.Redirect("https://envoy.lol/"+videoURL, fiber.StatusFound) + // Redirect to proxy if not TelegramBot in User-Agent + if strings.Contains(r.Header.Get("User-Agent"), "TelegramBot") { + w.Header().Set("Location", videoURL) + return } + w.Header().Set("Location", "https://envoy.lol/"+videoURL) + return } diff --git a/main.go b/main.go index a3aa49e..0f4e48f 100644 --- a/main.go +++ b/main.go @@ -7,18 +7,16 @@ import ( scraper "instafix/handlers/scraper" "instafix/utils" "instafix/views" + "net/http" "os" "strconv" "strings" "time" "github.com/cockroachdb/pebble" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/pprof" - "github.com/gofiber/fiber/v2/middleware/recover" + "github.com/julienschmidt/httprouter" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/valyala/bytebufferpool" ) func init() { @@ -34,16 +32,6 @@ func main() { remoteScraperAddr := flag.String("remote-scraper", "", "Remote scraper address") flag.Parse() - app := fiber.New() - - recoverConfig := recover.ConfigDefault - recoverConfig.EnableStackTrace = true - app.Use(recover.New(recoverConfig)) - app.Use(pprof.New()) - - // Close database when app closes - defer scraper.DB.Close() - // Initialize remote scraper if *remoteScraperAddr != "" { if !strings.HasPrefix(*remoteScraperAddr, "http") { @@ -70,30 +58,31 @@ func main() { } }() - app.Get("/", func(c *fiber.Ctx) error { - viewsBuf := bytebufferpool.Get() - defer bytebufferpool.Put(viewsBuf) - c.Set("Content-Type", "text/html; charset=utf-8") - views.Home(viewsBuf) - return c.Send(viewsBuf.Bytes()) - }) + // Close database when app closes + // defer scraper.DB.Close() - app.Get("/p/:postID/", handlers.Embed()) - app.Get("/tv/:postID", handlers.Embed()) - app.Get("/reel/:postID", handlers.Embed()) - app.Get("/reels/:postID", handlers.Embed()) - app.Get("/stories/:username/:postID", handlers.Embed()) - app.Get("/p/:postID/:mediaNum", handlers.Embed()) - app.Get("/:username/p/:postID/", handlers.Embed()) - app.Get("/:username/p/:postID/:mediaNum", handlers.Embed()) - app.Get("/:username/reel/:postID", handlers.Embed()) - - app.Get("/images/:postID/:mediaNum", handlers.Images()) - app.Get("/videos/:postID/:mediaNum", handlers.Videos()) - app.Get("/grid/:postID", handlers.Grid()) - app.Get("/oembed", handlers.OEmbed()) - - if err := app.Listen(*listenAddr); err != nil { + router := httprouter.New() + router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + views.Home(w) + }) + // router.GET("/:username/p/:postID", handlers.Embed) + // router.GET("/:username/p/:postID/:mediaNum", handlers.Embed) + // router.GET("/:username/reel/:postID", handlers.Embed) + + router.GET("/p/:postID", handlers.Embed) + router.GET("/tv/:postID", handlers.Embed) + router.GET("/reel/:postID", handlers.Embed) + router.GET("/reels/:postID", handlers.Embed) + router.GET("/stories/:username/:postID", handlers.Embed) + router.GET("/p/:postID/:mediaNum", handlers.Embed) + + router.GET("/images/:postID/:mediaNum", handlers.Images) + router.GET("/videos/:postID/:mediaNum", handlers.Videos) + router.GET("/grid/:postID", handlers.Grid) + router.GET("/oembed", handlers.OEmbed) + + if err := http.ListenAndServe(*listenAddr, router); err != nil { log.Fatal().Err(err).Msg("Failed to listen") } } diff --git a/utils/crawlerdetect.go b/utils/crawlerdetect.go index 588d781..7fb4a00 100644 --- a/utils/crawlerdetect.go +++ b/utils/crawlerdetect.go @@ -1,47 +1,47 @@ package utils import ( - "bytes" + "strings" ) -var knownBots = [][]byte{ - []byte("bot"), - []byte("facebook"), - []byte("embed"), - []byte("got"), - []byte("firefox/92"), - []byte("firefox/38"), - []byte("curl"), - []byte("wget"), - []byte("go-http"), - []byte("yahoo"), - []byte("generator"), - []byte("whatsapp"), - []byte("preview"), - []byte("link"), - []byte("proxy"), - []byte("vkshare"), - []byte("images"), - []byte("analyzer"), - []byte("index"), - []byte("crawl"), - []byte("spider"), - []byte("python"), - []byte("cfnetwork"), - []byte("node"), - []byte("mastodon"), - []byte("http.rb"), - []byte("discord"), - []byte("ruby"), - []byte("bun/"), - []byte("fiddler"), - []byte("revoltchat"), +var knownBots = []string{ + "bot", + "facebook", + "embed", + "got", + "firefox/92", + "firefox/38", + "curl", + "wget", + "go-http", + "yahoo", + "generator", + "whatsapp", + "preview", + "link", + "proxy", + "vkshare", + "images", + "analyzer", + "index", + "crawl", + "spider", + "python", + "cfnetwork", + "node", + "mastodon", + "http.rb", + "discord", + "ruby", + "bun/", + "fiddler", + "revoltchat", } -func IsBot(userAgent []byte) bool { - userAgent = bytes.ToLower(userAgent) +func IsBot(userAgent string) bool { + userAgent = strings.ToLower(userAgent) for _, bot := range knownBots { - if bytes.Contains(userAgent, bot) { + if strings.Contains(userAgent, bot) { return true } } diff --git a/views/embed.jade.go b/views/embed.jade.go index 988fe9f..e5ad573 100644 --- a/views/embed.jade.go +++ b/views/embed.jade.go @@ -4,8 +4,7 @@ package views import ( "instafix/views/model" - - pool "github.com/valyala/bytebufferpool" + "io" ) const ( @@ -33,7 +32,8 @@ const ( embed__26 = `" type="application/json+oembed" title="` ) -func Embed(v *model.ViewsData, buffer *pool.ByteBuffer) { +func Embed(v *model.ViewsData, wr io.Writer) { + buffer := &WriterAsBuffer{wr} buffer.WriteString(embed__0) diff --git a/views/home.jade.go b/views/home.jade.go index 510c89f..beb4383 100644 --- a/views/home.jade.go +++ b/views/home.jade.go @@ -3,7 +3,7 @@ package views import ( - pool "github.com/valyala/bytebufferpool" + "io" ) const ( @@ -11,7 +11,8 @@ const ( home__1 = `
Source code available in GitHub!
• Instagram is a trademark of Instagram, Inc. This app is not affiliated with Instagram, Inc.` ) -func Home(buffer *pool.ByteBuffer) { +func Home(wr io.Writer) { + buffer := &WriterAsBuffer{wr} buffer.WriteString(home__0) diff --git a/views/jade.go b/views/jade.go index 37782fd..658391e 100644 --- a/views/jade.go +++ b/views/jade.go @@ -5,7 +5,6 @@ import ( "bytes" "io" "strconv" - pool "github.com/valyala/bytebufferpool" ) var ( @@ -13,7 +12,7 @@ var ( replacing = []string{"<", ">", """, "'", "&"} ) -func WriteEscString(st string, buffer *pool.ByteBuffer) { +func WriteEscString(st string, buffer *WriterAsBuffer) { for i := 0; i < len(st); i++ { if n := bytes.IndexByte(escaped, st[i]); n >= 0 { buffer.WriteString(replacing[n]) @@ -41,7 +40,7 @@ type stringer interface { String() string } -func WriteAll(a interface{}, escape bool, buffer *pool.ByteBuffer) { +func WriteAll(a interface{}, escape bool, buffer *WriterAsBuffer) { switch v := a.(type) { case string: if escape { @@ -96,7 +95,7 @@ func ternary(condition bool, iftrue, iffalse interface{}) interface{} { // Used part of go source: // https://github.com/golang/go/blob/master/src/strconv/itoa.go -func WriteUint(u uint64, buffer *pool.ByteBuffer) { +func WriteUint(u uint64, buffer *WriterAsBuffer) { var a [64 + 1]byte i := len(a) @@ -126,14 +125,14 @@ func WriteUint(u uint64, buffer *pool.ByteBuffer) { a[i] = byte(us + '0') buffer.Write(a[i:]) } -func WriteInt(i int64, buffer *pool.ByteBuffer) { +func WriteInt(i int64, buffer *WriterAsBuffer) { if i < 0 { buffer.WriteByte('-') i = -i } WriteUint(uint64(i), buffer) } -func WriteBool(b bool, buffer *pool.ByteBuffer) { +func WriteBool(b bool, buffer *WriterAsBuffer) { if b { buffer.WriteString("true") return diff --git a/views/oembed.manual.go b/views/oembed.manual.go index 8770b5d..9f5b6bb 100644 --- a/views/oembed.manual.go +++ b/views/oembed.manual.go @@ -4,8 +4,7 @@ package views import ( "instafix/views/model" - - pool "github.com/valyala/bytebufferpool" + "io" ) const ( @@ -14,7 +13,8 @@ const ( oembed__2 = `","provider_name": "InstaFix","provider_url": "https://github.com/Wikidepia/InstaFix","title": "Instagram","type": "link","version": "1.0"}` ) -func OEmbed(o *model.OEmbedData, buffer *pool.ByteBuffer) { +func OEmbed(o *model.OEmbedData, wr io.Writer) { + buffer := &WriterAsBuffer{wr} buffer.WriteString(oembed__0) WriteAll(o.Text, false, buffer) // Escape in handlers