main.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package main
  2. import (
  3. "errors"
  4. "log"
  5. "net"
  6. "net/http"
  7. "os"
  8. "strings"
  9. "time"
  10. config "codeberg.org/vnpower/pixivfe/v2/core/config"
  11. "codeberg.org/vnpower/pixivfe/v2/pages"
  12. "codeberg.org/vnpower/pixivfe/v2/serve"
  13. "github.com/goccy/go-json"
  14. "github.com/gofiber/fiber/v2"
  15. "github.com/gofiber/fiber/v2/middleware/cache"
  16. "github.com/gofiber/fiber/v2/middleware/compress"
  17. "github.com/gofiber/fiber/v2/middleware/limiter"
  18. "github.com/gofiber/fiber/v2/middleware/logger"
  19. "github.com/gofiber/fiber/v2/middleware/recover"
  20. "github.com/gofiber/fiber/v2/utils"
  21. "github.com/gofiber/template/jet/v2"
  22. )
  23. func CanRequestSkipLimiter(c *fiber.Ctx) bool {
  24. path := c.Path()
  25. return strings.HasPrefix(path, "/assets/") ||
  26. strings.HasPrefix(path, "/css/") ||
  27. strings.HasPrefix(path, "/js/") ||
  28. strings.HasPrefix(path, "/proxy/s.pximg.net/")
  29. }
  30. func main() {
  31. config.SetupStorage()
  32. config.GlobalServerConfig.InitializeConfig()
  33. engine := jet.New("./views", ".jet.html")
  34. if config.GlobalServerConfig.InDevelopment {
  35. engine.Reload(true)
  36. }
  37. engine.AddFuncMap(serve.GetTemplateFunctions())
  38. server := fiber.New(fiber.Config{
  39. AppName: "PixivFE",
  40. DisableStartupMessage: true,
  41. Views: engine,
  42. Prefork: false,
  43. JSONEncoder: json.Marshal,
  44. JSONDecoder: json.Unmarshal,
  45. ViewsLayout: "layout",
  46. EnableTrustedProxyCheck: true,
  47. TrustedProxies: []string{"0.0.0.0/0"},
  48. ProxyHeader: fiber.HeaderXForwardedFor,
  49. ErrorHandler: func(c *fiber.Ctx, err error) error {
  50. // Status code defaults to 500
  51. code := fiber.StatusInternalServerError
  52. // // Retrieve the custom status code if it's a *fiber.Error
  53. // var e *fiber.Error
  54. // if errors.As(err, &e) {
  55. // code = e.Code
  56. // }
  57. // Send custom error page
  58. err = c.Status(code).Render("pages/error", fiber.Map{"Title": "Error", "Error": err})
  59. if err != nil {
  60. // In case the SendFile fails
  61. return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
  62. }
  63. // Return from handler
  64. return nil
  65. },
  66. })
  67. server.Use(logger.New(
  68. logger.Config{
  69. Format: "${time} ${ip} | ${path}\n",
  70. Next: CanRequestSkipLimiter,
  71. },
  72. ))
  73. if !config.GlobalServerConfig.InDevelopment {
  74. server.Use(cache.New(
  75. cache.Config{
  76. Next: func(c *fiber.Ctx) bool {
  77. resp_code := c.Response().StatusCode()
  78. if resp_code < 200 || resp_code >= 300 {
  79. return true
  80. }
  81. // Disable cache for settings page
  82. return strings.Contains(c.Path(), "/settings") || c.Path() == "/"
  83. },
  84. Expiration: 5 * time.Minute,
  85. CacheControl: true,
  86. KeyGenerator: func(c *fiber.Ctx) string {
  87. return utils.CopyString(c.OriginalURL())
  88. },
  89. },
  90. ))
  91. }
  92. server.Use(recover.New())
  93. server.Use(compress.New(compress.Config{
  94. Level: compress.LevelBestSpeed, // 1
  95. }))
  96. server.Use(limiter.New(limiter.Config{
  97. Next: CanRequestSkipLimiter,
  98. Expiration: 30 * time.Second,
  99. Max: config.GlobalServerConfig.RequestLimit,
  100. LimiterMiddleware: limiter.SlidingWindow{},
  101. LimitReached: func(c *fiber.Ctx) error {
  102. log.Println("Limit Reached!")
  103. return errors.New("Woah! You are going too fast! I'll have to keep an eye on you.")
  104. },
  105. }))
  106. // Global headers (from GotHub)
  107. server.Use(func(c *fiber.Ctx) error {
  108. c.Set("X-Frame-Options", "SAMEORIGIN")
  109. c.Set("X-XSS-Protection", "1; mode=block")
  110. c.Set("X-Content-Type-Options", "nosniff")
  111. c.Set("Referrer-Policy", "no-referrer")
  112. c.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
  113. return c.Next()
  114. })
  115. server.Use(func(c *fiber.Ctx) error {
  116. baseURL := c.BaseURL() + c.OriginalURL()
  117. c.Bind(fiber.Map{"BaseURL": baseURL})
  118. return c.Next()
  119. })
  120. server.Static("/favicon.ico", "./views/assets/favicon.ico")
  121. server.Static("/css/", "./views/css")
  122. server.Static("/assets/", "./views/assets")
  123. server.Static("/robots.txt", "./views/assets/robots.txt")
  124. // Routes
  125. server.Get("/", pages.IndexPage)
  126. server.Get("/about", pages.AboutPage)
  127. server.Get("/newest", pages.NewestPage)
  128. server.Get("/discovery", pages.DiscoveryPage)
  129. server.Get("/ranking", pages.RankingPage)
  130. server.Get("/rankingCalendar", pages.RankingCalendarPage)
  131. server.Post("/rankingCalendar", pages.RankingCalendarPicker)
  132. server.Get("/users/:id/:category?", pages.UserPage)
  133. server.Get("/artworks/:id/", pages.ArtworkPage).Name("artworks")
  134. server.Get("/artworks-multi/:ids/", pages.ArtworkMultiPage)
  135. // Settings group
  136. settings := server.Group("/settings")
  137. settings.Get("/", pages.SettingsPage)
  138. settings.Post("/:type", pages.SettingsPost)
  139. // Personal group
  140. self := server.Group("/self")
  141. self.Get("/", pages.LoginUserPage)
  142. self.Get("/followingWorks", pages.FollowingWorksPage)
  143. self.Get("/bookmarks", pages.LoginBookmarkPage)
  144. self.Post("/addBookmark/:id", pages.AddBookmarkRoute)
  145. self.Post("/deleteBookmark/:id", pages.DeleteBookmarkRoute)
  146. self.Post("/like/:id", pages.LikeRoute)
  147. server.Get("/tags/:name", pages.TagPage)
  148. server.Post("/tags",
  149. func(c *fiber.Ctx) error {
  150. name := c.FormValue("name")
  151. return c.Redirect("/tags/"+name, http.StatusFound)
  152. })
  153. // Legacy illust URL
  154. server.Get("/member_illust.php", func(c *fiber.Ctx) error {
  155. return c.Redirect("/artworks/" + c.Query("illust_id"))
  156. })
  157. // Proxy routes
  158. proxy := server.Group("/proxy")
  159. proxy.Get("/i.pximg.net/*", pages.IPximgProxy)
  160. proxy.Get("/s.pximg.net/*", pages.SPximgProxy)
  161. proxy.Get("/ugoira.com/*", pages.UgoiraProxy)
  162. // Listen
  163. if config.GlobalServerConfig.UnixSocket != "" {
  164. ln, err := net.Listen("unix", config.GlobalServerConfig.UnixSocket)
  165. if err != nil {
  166. log.Fatalf("Failed to run on Unix socket. %s", err)
  167. os.Exit(1)
  168. }
  169. log.Printf("PixivFE is running on %v\n", config.GlobalServerConfig.UnixSocket)
  170. server.Listener(ln)
  171. } else {
  172. addr := config.GlobalServerConfig.Host + ":" + config.GlobalServerConfig.Port
  173. log.Printf("PixivFE is running on %v\n", addr)
  174. // note: string concatenation is very flaky
  175. server.Listen(addr)
  176. }
  177. }