Compare commits
No commits in common. "main" and "gradient-2" have entirely different histories.
main
...
gradient-2
|
@ -1,9 +0,0 @@
|
|||
root = "."
|
||||
tmp_dir = "tmp"
|
||||
[build]
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
bin = "./tmp/main"
|
||||
delay = 1000 # ms
|
||||
exclude_dir = ["assets", "tmp", "vendor"]
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
exclude_regex = ["_test\\.go"]
|
5
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
/tmp
|
||||
dev.sh
|
||||
/pixivfe
|
||||
tmp
|
||||
.dir-locals.el
|
||||
.sass-cache/
|
|
@ -1,11 +0,0 @@
|
|||
steps:
|
||||
build:
|
||||
image: golang
|
||||
commands:
|
||||
- go get
|
||||
- go mod download
|
||||
- CGO_ENABLED=0 GOOS=linux go build -mod=readonly -o pixivfe
|
||||
- ./pixivfe &
|
||||
- sleep 3
|
||||
- go test -v -bench=. -count 5
|
||||
secrets: [pixivfe_token]
|
|
@ -1,4 +1,4 @@
|
|||
FROM docker.io/golang:1.21.0 as builder
|
||||
FROM docker.io/golang:1.21 as builder
|
||||
WORKDIR /app
|
||||
COPY go.* ./
|
||||
RUN go mod download
|
||||
|
@ -7,7 +7,7 @@ RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o pixivfe
|
|||
|
||||
FROM docker.io/alpine:3
|
||||
COPY --from=builder /app/pixivfe /pixivfe
|
||||
COPY --from=builder /app/template /template
|
||||
COPY --from=builder /app/views /views
|
||||
EXPOSE 8282
|
||||
|
||||
ENTRYPOINT ["/pixivfe"]
|
||||
|
|
44
LICENSE
|
@ -618,45 +618,5 @@ copy of the Program in return for a fee.
|
|||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
PixivFE: a privacy respecting frontend for Pixiv
|
||||
Copyright (C) 2023-2024 VnPower
|
22
README.md
|
@ -1,6 +1,3 @@
|
|||
### Note
|
||||
A backend rewrite is ongoing. Check out branch [v2](https://codeberg.org/VnPower/pixivfe/src/branch/v2).
|
||||
|
||||
# PixivFE
|
||||
|
||||
A privacy-respecting alternative front-end for Pixiv that doesn't suck
|
||||
|
@ -14,11 +11,11 @@ A privacy-respecting alternative front-end for Pixiv that doesn't suck
|
|||
![CI badge](https://ci.codeberg.org/api/badges/12556/status.svg)
|
||||
[![Go Report Card](https://goreportcard.com/badge/codeberg.org/vnpower/pixivfe)](https://goreportcard.com/report/codeberg.org/vnpower/pixivfe)
|
||||
|
||||
Questions? Feedbacks? You can [PM me](https://matrix.to/#/@vnpower:exozy.me) on
|
||||
Questions? Feedbacks? You can [PM me](https://matrix.to/#/@vnpower:eientei.org) on
|
||||
Matrix!
|
||||
|
||||
You can keep track of this project's development
|
||||
[here](https://codeberg.org/VnPower/pixivfe/projects/3481).
|
||||
[here](https://codeberg.org/VnPower/PixivFE/wiki/Things-to-do).
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -29,9 +26,24 @@ You can keep track of this project's development
|
|||
|
||||
## Hosting
|
||||
|
||||
You can use PixivFE for personal use! Assuming that you use an operating system that can run POSIX shell scripts, install `go`, clone this repository, modify the `run.sh` file, and profit!
|
||||
I recommend self-hosting your own instance for personal use, instead of relying entirely on official instances.
|
||||
|
||||
|
||||
Check out [this page](https://codeberg.org/VnPower/pixivfe/wiki/Hosting). We
|
||||
currently have guides for Docker and Caddy.
|
||||
|
||||
## Development
|
||||
|
||||
```
|
||||
# watch and compile styles with node-sass
|
||||
pnpm i -g sass
|
||||
sass --watch views/css
|
||||
|
||||
# run in development mode (auto reload templates)
|
||||
PIXIVFE_DEV=1 ... go run .
|
||||
```
|
||||
|
||||
## Instances
|
||||
|
||||
| Name | Cloudflare? | URL |
|
||||
|
|
14
config.yml
|
@ -1,14 +0,0 @@
|
|||
# This is required for the API. See https://github.com/Nandaka/PixivUtil2/wiki#pixiv-login-using-cookie
|
||||
# Please keep this carefully, there is a risk of account theft if somebody got your cookie.
|
||||
# It is better to use a decoy account, instead of using your main account's cookie.
|
||||
PHPSESSID: 75921176_zsZa7sNLX8zj4N9UKf526ZV0wdWOwN79
|
||||
|
||||
Port: "8080"
|
||||
|
||||
UserAgent: Mozilla/5.0
|
||||
|
||||
# Number of items in each list page
|
||||
PageItems: 30
|
||||
|
||||
# Default image proxy server. Recommended since Pixiv doesn't like you accessing images on their server.
|
||||
ImageProxyServer: px2.rainchan.win
|
|
@ -1,49 +0,0 @@
|
|||
package configs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Token, BaseURL, Port, UserAgent, ProxyServer, StartingTime, Version, AcceptLanguage string
|
||||
|
||||
func parseEnv(key string) (string, error) {
|
||||
value, ok := os.LookupEnv(key)
|
||||
|
||||
if !ok {
|
||||
return value, errors.New("Failed to get environment variable" + key)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func parseEnvWithDefault(key string, defaultValue string) string {
|
||||
value, ok := os.LookupEnv(key)
|
||||
|
||||
if !ok {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func ParseConfig() error {
|
||||
var err error
|
||||
|
||||
Token, err = parseEnv("PIXIVFE_TOKEN")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
BaseURL = parseEnvWithDefault("PIXIVFE_BASEURL", "localhost")
|
||||
Port = parseEnvWithDefault("PIXIVFE_PORT", "8282")
|
||||
UserAgent = parseEnvWithDefault("PIXIVFE_USERAGENT", "Mozilla/5.0")
|
||||
ProxyServer = parseEnvWithDefault("PIXIVFE_IMAGEPROXY", "pximg.cocomi.cf")
|
||||
AcceptLanguage = parseEnvWithDefault("PIXIVFE_ACCEPTLANGUAGE", "en-US,en;q=0.5")
|
||||
StartingTime = time.Now().UTC().Format("2006-01-02 15:04")
|
||||
Version = "v1.0.5"
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package configs
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2/middleware/session"
|
||||
)
|
||||
|
||||
var Store *session.Store
|
||||
|
||||
func SetupStorage() {
|
||||
Store = session.New(session.Config{
|
||||
Expiration: time.Hour * 24 * 30,
|
||||
})
|
||||
Store.RegisterType("")
|
||||
}
|
150
core/config/config.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var GlobalServerConfig ServerConfig
|
||||
|
||||
type ServerConfig struct {
|
||||
// Required
|
||||
Token []string
|
||||
ProxyServer string // authority part of the URL; no '/'
|
||||
|
||||
// can be left empty
|
||||
Host string
|
||||
|
||||
// One of two is required
|
||||
Port string
|
||||
UnixSocket string
|
||||
|
||||
UserAgent string
|
||||
AcceptLanguage string
|
||||
RequestLimit int
|
||||
|
||||
StartingTime string
|
||||
Version string
|
||||
InDevelopment bool
|
||||
}
|
||||
|
||||
func (s *ServerConfig) InitializeConfig() error {
|
||||
_, hasDev := os.LookupEnv("PIXIVFE_DEV")
|
||||
s.InDevelopment = hasDev
|
||||
if s.InDevelopment {
|
||||
log.Printf("Set server to development mode\n")
|
||||
}
|
||||
|
||||
token, hasToken := os.LookupEnv("PIXIVFE_TOKEN")
|
||||
if !hasToken {
|
||||
log.Fatalln("PIXIVFE_TOKEN is required, but was not set.")
|
||||
return errors.New("PIXIVFE_TOKEN is required, but was not set.\n")
|
||||
}
|
||||
s.SetToken(token)
|
||||
|
||||
proxyServer, hasProxyServer := os.LookupEnv("PIXIVFE_IMAGEPROXY")
|
||||
if !hasProxyServer {
|
||||
log.Fatalln("PIXIVFE_IMAGEPROXY is required, but was not set.")
|
||||
return errors.New("PIXIVFE_IMAGEPROXY is required, but was not set.\n")
|
||||
}
|
||||
s.SetProxyServer(proxyServer)
|
||||
|
||||
hostname, hasHostname := os.LookupEnv("PIXIVFE_HOST")
|
||||
if hasHostname {
|
||||
log.Printf("Set TCP hostname to: %s\n", hostname)
|
||||
s.Host = hostname
|
||||
}
|
||||
|
||||
port, hasPort := os.LookupEnv("PIXIVFE_PORT")
|
||||
if hasPort {
|
||||
s.SetPort(port)
|
||||
}
|
||||
|
||||
socket, hasSocket := os.LookupEnv("PIXIVFE_UNIXSOCKET")
|
||||
if hasSocket {
|
||||
s.SetUnixSocket(socket)
|
||||
}
|
||||
|
||||
if !hasPort && !hasSocket {
|
||||
log.Fatalln("Either PIXIVFE_PORT or PIXIVFE_UNIXSOCKET has to be set.")
|
||||
return errors.New("Either PIXIVFE_PORT or PIXIVFE_UNIXSOCKET has to be set.")
|
||||
}
|
||||
|
||||
userAgent, hasUserAgent := os.LookupEnv("PIXIVFE_USERAGENT")
|
||||
if !hasUserAgent {
|
||||
userAgent = "Mozilla/5.0"
|
||||
}
|
||||
s.SetUserAgent(userAgent)
|
||||
|
||||
acceptLanguage, hasAcceptLanguage := os.LookupEnv("PIXIVFE_ACCEPTLANGUAGE")
|
||||
if !hasAcceptLanguage {
|
||||
acceptLanguage = "en-US,en;q=0.5"
|
||||
}
|
||||
s.SetAcceptLanguage(acceptLanguage)
|
||||
|
||||
requestLimit, hasRequestLimit := os.LookupEnv("PIXIVFE_REQUESTLIMIT")
|
||||
if hasRequestLimit {
|
||||
s.SetRequestLimit(requestLimit)
|
||||
} else {
|
||||
s.RequestLimit = 15
|
||||
}
|
||||
|
||||
s.setStartingTime()
|
||||
s.setVersion()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerConfig) SetToken(v string) {
|
||||
// TODO Maybe add some testing?
|
||||
s.Token = strings.Split(v, ",")
|
||||
log.Printf("Set token to: %s\n", v)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) SetProxyServer(v string) {
|
||||
s.ProxyServer = v
|
||||
log.Printf("Set image proxy server to: %s\n", v)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) SetPort(v string) {
|
||||
s.Port = v
|
||||
log.Printf("Set TCP port to: %s\n", v)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) SetUnixSocket(v string) {
|
||||
s.UnixSocket = v
|
||||
log.Printf("Set UNIX socket path to: %s\n", v)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) SetUserAgent(v string) {
|
||||
s.UserAgent = v
|
||||
log.Printf("Set user agent to: %s\n", v)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) SetAcceptLanguage(v string) {
|
||||
s.AcceptLanguage = v
|
||||
log.Printf("Set Accept-Language header to: %s\n", v)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) SetRequestLimit(v string) {
|
||||
t, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.RequestLimit = t
|
||||
log.Printf("Set request limit to %s requests per 30 seconds\n", v)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) setStartingTime() {
|
||||
s.StartingTime = time.Now().UTC().Format("2006-01-02 15:04")
|
||||
log.Printf("Set starting time to: %s\n", s.StartingTime)
|
||||
}
|
||||
|
||||
func (s *ServerConfig) setVersion() {
|
||||
s.Version = "v2.3"
|
||||
log.Printf("Set server version to: %s\n", s.Version)
|
||||
}
|
131
core/config/session.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/session"
|
||||
)
|
||||
|
||||
var Store *session.Store
|
||||
|
||||
func SetupStorage() {
|
||||
Store = session.New(session.Config{
|
||||
Expiration: time.Hour * 24 * 30,
|
||||
})
|
||||
}
|
||||
|
||||
func saveSession(sess *session.Session) error {
|
||||
if err := sess.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProxyImageUrl(c *fiber.Ctx, s string) string {
|
||||
proxy := GetImageProxy(c)
|
||||
s = strings.ReplaceAll(s, `https:\/\/i.pximg.net`, "https://"+proxy)
|
||||
// s = strings.ReplaceAll(s, `https:\/\/i.pximg.net`, "/proxy/i.pximg.net")
|
||||
s = strings.ReplaceAll(s, `https:\/\/s.pximg.net`, "/proxy/s.pximg.net")
|
||||
return s
|
||||
}
|
||||
|
||||
func GetImageProxy(c *fiber.Ctx) string {
|
||||
sess, err := Store.Get(c)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to get current session and its values! Falling back to server default!")
|
||||
return GlobalServerConfig.ProxyServer
|
||||
}
|
||||
value := sess.Get("ImageProxy")
|
||||
if value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
|
||||
return GlobalServerConfig.ProxyServer
|
||||
}
|
||||
|
||||
func GetRandomDefaultToken() string {
|
||||
defaultToken := GlobalServerConfig.Token[rand.Intn(len(GlobalServerConfig.Token))]
|
||||
|
||||
return defaultToken
|
||||
}
|
||||
|
||||
func GetToken(c *fiber.Ctx) string {
|
||||
defaultToken := GlobalServerConfig.Token[rand.Intn(len(GlobalServerConfig.Token))]
|
||||
|
||||
sess, err := Store.Get(c)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to get current session and its values! Falling back to server default!")
|
||||
return defaultToken
|
||||
}
|
||||
value := sess.Get("Token")
|
||||
if value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
|
||||
return defaultToken
|
||||
}
|
||||
|
||||
func CheckToken(c *fiber.Ctx) string {
|
||||
sess, err := Store.Get(c)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to get current session and its values!")
|
||||
return ""
|
||||
}
|
||||
value := sess.Get("Token")
|
||||
if value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetCSRFToken(c *fiber.Ctx) string {
|
||||
sess, err := Store.Get(c)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to get current session and its values!")
|
||||
return ""
|
||||
}
|
||||
value := sess.Get("CSRF")
|
||||
if value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func SetSessionValue(c *fiber.Ctx, name, value string) error {
|
||||
sess, err := Store.Get(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess.Set(name, value)
|
||||
|
||||
if err = saveSession(sess); err != nil {
|
||||
log.Fatalln("Failed to save session storage!")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RemoveSessionValue(c *fiber.Ctx, name string) error {
|
||||
sess, err := Store.Get(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess.Delete(name)
|
||||
|
||||
if err = saveSession(sess); err != nil {
|
||||
log.Fatalln("Failed to save session storage!")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
87
core/http/request.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type HttpResponse struct {
|
||||
Ok bool
|
||||
StatusCode int
|
||||
|
||||
Body string
|
||||
Message string
|
||||
}
|
||||
|
||||
func WebAPIRequest(URL, token string) HttpResponse {
|
||||
req, _ := http.NewRequest("GET", URL, nil)
|
||||
|
||||
req.Header.Add("User-Agent", core.GlobalServerConfig.UserAgent)
|
||||
req.Header.Add("Accept-Language", core.GlobalServerConfig.AcceptLanguage)
|
||||
|
||||
if token == "" {
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "PHPSESSID",
|
||||
Value: core.GetRandomDefaultToken(),
|
||||
})
|
||||
} else {
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "PHPSESSID",
|
||||
Value: token,
|
||||
})
|
||||
}
|
||||
|
||||
// Make the request
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return HttpResponse{
|
||||
Ok: false,
|
||||
StatusCode: 0,
|
||||
Body: "",
|
||||
Message: fmt.Sprintf("Failed to create a request to %s\n.", URL),
|
||||
}
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return HttpResponse{
|
||||
Ok: false,
|
||||
StatusCode: 0,
|
||||
Body: "",
|
||||
Message: fmt.Sprintln("Failed to parse request data."),
|
||||
}
|
||||
}
|
||||
|
||||
return HttpResponse{
|
||||
Ok: true,
|
||||
StatusCode: resp.StatusCode,
|
||||
Body: string(body),
|
||||
Message: "",
|
||||
}
|
||||
}
|
||||
|
||||
func UnwrapWebAPIRequest(URL, token string) (string, error) {
|
||||
resp := WebAPIRequest(URL, token)
|
||||
|
||||
if !resp.Ok {
|
||||
return "", errors.New(resp.Message)
|
||||
}
|
||||
|
||||
err := gjson.Get(resp.Body, "error")
|
||||
|
||||
if !err.Exists() {
|
||||
return "", errors.New("Incompatible request body.")
|
||||
}
|
||||
|
||||
if err.Bool() {
|
||||
return "", errors.New(gjson.Get(resp.Body, "message").String())
|
||||
}
|
||||
|
||||
return gjson.Get(resp.Body, "body").String(), nil
|
||||
}
|
115
core/http/url.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package core
|
||||
|
||||
import "fmt"
|
||||
|
||||
func GetNewestArtworksURL(worktype, r18, lastID string) string {
|
||||
base := "https://www.pixiv.net/ajax/illust/new?limit=30&type=%s&r18=%s&lastId=%s"
|
||||
return fmt.Sprintf(base, worktype, r18, lastID)
|
||||
}
|
||||
|
||||
func GetDiscoveryURL(mode string, limit int) string {
|
||||
base := "https://www.pixiv.net/ajax/discovery/artworks?mode=%s&limit=%d"
|
||||
return fmt.Sprintf(base, mode, limit)
|
||||
}
|
||||
|
||||
func GetRankingURL(mode, content, date, page string) string {
|
||||
base := "https://www.pixiv.net/ranking.php?format=json&mode=%s&content=%s&date=%s&p=%s"
|
||||
baseNoDate := "https://www.pixiv.net/ranking.php?format=json&mode=%s&content=%s&p=%s"
|
||||
|
||||
if date != "" {
|
||||
return fmt.Sprintf(base, mode, content, date, page)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(baseNoDate, mode, content, page)
|
||||
}
|
||||
|
||||
func GetRankingCalendarURL(mode string, year, month int) string {
|
||||
base := "https://www.pixiv.net/ranking_log.php?mode=%s&date=%d%02d"
|
||||
|
||||
return fmt.Sprintf(base, mode, year, month)
|
||||
}
|
||||
|
||||
func GetUserInformationURL(id string) string {
|
||||
base := "https://www.pixiv.net/ajax/user/%s?full=1"
|
||||
|
||||
return fmt.Sprintf(base, id)
|
||||
}
|
||||
|
||||
func GetUserArtworksURL(id string) string {
|
||||
base := "https://www.pixiv.net/ajax/user/%s/profile/all"
|
||||
|
||||
return fmt.Sprintf(base, id)
|
||||
}
|
||||
|
||||
func GetUserFullArtworkURL(id, ids string) string {
|
||||
base := "https://www.pixiv.net/ajax/user/%s/profile/illusts?work_category=illustManga&is_first_page=0&lang=en%s"
|
||||
|
||||
return fmt.Sprintf(base, id, ids)
|
||||
}
|
||||
|
||||
func GetUserBookmarksURL(id, mode string, page int) string {
|
||||
base := "https://www.pixiv.net/ajax/user/%s/illusts/bookmarks?tag=&offset=%d&limit=48&rest=%s"
|
||||
|
||||
return fmt.Sprintf(base, id, page*48, mode)
|
||||
}
|
||||
|
||||
func GetFrequentTagsURL(ids string) string {
|
||||
base := "https://www.pixiv.net/ajax/tags/frequent/illust?%s"
|
||||
|
||||
return fmt.Sprintf(base, ids)
|
||||
}
|
||||
|
||||
func GetNewestFromFollowingURL(mode, page string) string {
|
||||
base := "https://www.pixiv.net/ajax/follow_latest/%s?mode=%s&p=%s"
|
||||
|
||||
// TODO: Recheck this URL
|
||||
return fmt.Sprintf(base, "illust", mode, page)
|
||||
}
|
||||
|
||||
func GetArtworkInformationURL(id string) string {
|
||||
base := "https://www.pixiv.net/ajax/illust/%s"
|
||||
|
||||
return fmt.Sprintf(base, id)
|
||||
}
|
||||
|
||||
func GetArtworkImagesURL(id string) string {
|
||||
base := "https://www.pixiv.net/ajax/illust/%s/pages"
|
||||
|
||||
return fmt.Sprintf(base, id)
|
||||
}
|
||||
|
||||
func GetArtworkRelatedURL(id string, limit int) string {
|
||||
base := "https://www.pixiv.net/ajax/illust/%s/recommend/init?limit=%d"
|
||||
|
||||
return fmt.Sprintf(base, id, limit)
|
||||
}
|
||||
|
||||
func GetArtworkCommentsURL(id string) string {
|
||||
base := "https://www.pixiv.net/ajax/illusts/comments/roots?illust_id=%s&limit=100"
|
||||
|
||||
return fmt.Sprintf(base, id)
|
||||
}
|
||||
|
||||
func GetTagDetailURL(id string) string {
|
||||
base := "https://www.pixiv.net/ajax/search/tags/%s"
|
||||
|
||||
return fmt.Sprintf(base, id)
|
||||
}
|
||||
|
||||
func GetSearchArtworksURL(artworkType, name, order, age_settings, page string) string {
|
||||
base := "https://www.pixiv.net/ajax/search/%s/%s?order=%s&mode=%s&p=%s"
|
||||
|
||||
return fmt.Sprintf(base, artworkType, name, order, age_settings, page)
|
||||
}
|
||||
|
||||
func GetLandingURL(mode string) string {
|
||||
base := "https://www.pixiv.net/ajax/top/illust?mode=%s"
|
||||
|
||||
return fmt.Sprintf(base, mode)
|
||||
}
|
||||
|
||||
func GetNovelURL(id string) string {
|
||||
base := "https://www.pixiv.net/ajax/novel/%s"
|
||||
|
||||
return fmt.Sprintf(base, id)
|
||||
}
|
369
core/webapi/artwork.go
Normal file
|
@ -0,0 +1,369 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
http "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// Pixiv returns 0, 1, 2 to filter SFW and/or NSFW artworks.
|
||||
// Those values are saved in `xRestrict`
|
||||
// 0: Safe
|
||||
// 1: R18
|
||||
// 2: R18G
|
||||
type xRestrict int
|
||||
|
||||
const (
|
||||
Safe xRestrict = 0
|
||||
R18 xRestrict = 1
|
||||
R18G xRestrict = 2
|
||||
)
|
||||
|
||||
var xRestrictModel = map[xRestrict]string{
|
||||
Safe: "",
|
||||
R18: "R18",
|
||||
R18G: "R18G",
|
||||
}
|
||||
|
||||
// Pixiv returns 0, 1, 2 to filter SFW and/or NSFW artworks.
|
||||
// Those values are saved in `aiType`
|
||||
// 0: Not rated / Unknown
|
||||
// 1: Not AI-generated
|
||||
// 2: AI-generated
|
||||
|
||||
type aiType int
|
||||
|
||||
const (
|
||||
Unrated aiType = 0
|
||||
NotAI aiType = 1
|
||||
AI aiType = 2
|
||||
)
|
||||
|
||||
var aiTypeModel = map[aiType]string{
|
||||
Unrated: "Unrated",
|
||||
NotAI: "Not AI",
|
||||
AI: "AI",
|
||||
}
|
||||
|
||||
type ImageResponse struct {
|
||||
Urls map[string]string `json:"urls"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
Small string `json:"thumb_mini"`
|
||||
Medium string `json:"small"`
|
||||
Large string `json:"regular"`
|
||||
Original string `json:"original"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Name string `json:"tag"`
|
||||
TranslatedName string `json:"translation"`
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
AuthorID string `json:"userId"`
|
||||
AuthorName string `json:"userName"`
|
||||
Avatar string `json:"img"`
|
||||
Context string `json:"comment"`
|
||||
Stamp string `json:"stampId"`
|
||||
Date string `json:"commentDate"`
|
||||
}
|
||||
|
||||
type UserBrief struct {
|
||||
ID string `json:"userId"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"imageBig"`
|
||||
}
|
||||
|
||||
type Illust struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description template.HTML `json:"description"`
|
||||
UserID string `json:"userId"`
|
||||
UserName string `json:"userName"`
|
||||
UserAccount string `json:"userAccount"`
|
||||
Date time.Time `json:"uploadDate"`
|
||||
Images []Image `json:"images"`
|
||||
Tags []Tag `json:"tags"`
|
||||
Pages int `json:"pageCount"`
|
||||
Bookmarks int `json:"bookmarkCount"`
|
||||
Likes int `json:"likeCount"`
|
||||
Comments int `json:"commentCount"`
|
||||
Views int `json:"viewCount"`
|
||||
CommentDisabled int `json:"commentOff"`
|
||||
SanityLevel int `json:"sl"`
|
||||
XRestrict xRestrict `json:"xRestrict"`
|
||||
AiType aiType `json:"aiType"`
|
||||
Bookmarked any `json:"bookmarkData"`
|
||||
Liked any `json:"json:"likeData"`
|
||||
User UserBrief
|
||||
RecentWorks []ArtworkBrief
|
||||
RelatedWorks []ArtworkBrief
|
||||
CommentsList []Comment
|
||||
IsUgoira bool
|
||||
}
|
||||
|
||||
func GetUserBasicInformation(c *fiber.Ctx, id string) (UserBrief, error) {
|
||||
var user UserBrief
|
||||
|
||||
URL := http.GetUserInformationURL(id)
|
||||
|
||||
response, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
response = session.ProxyImageUrl(c, response)
|
||||
|
||||
err = json.Unmarshal([]byte(response), &user)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func GetArtworkImages(c *fiber.Ctx, id string) ([]Image, error) {
|
||||
var resp []ImageResponse
|
||||
var images []Image
|
||||
|
||||
URL := http.GetArtworkImagesURL(id)
|
||||
|
||||
response, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response = session.ProxyImageUrl(c, response)
|
||||
|
||||
err = json.Unmarshal([]byte(response), &resp)
|
||||
if err != nil {
|
||||
return images, err
|
||||
}
|
||||
|
||||
// Extract and proxy every images
|
||||
for _, imageRaw := range resp {
|
||||
var image Image
|
||||
|
||||
image.Small = imageRaw.Urls["thumb_mini"]
|
||||
image.Medium = imageRaw.Urls["small"]
|
||||
image.Large = imageRaw.Urls["regular"]
|
||||
image.Original = imageRaw.Urls["original"]
|
||||
|
||||
images = append(images, image)
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func GetArtworkComments(c *fiber.Ctx, id string) ([]Comment, error) {
|
||||
var body struct {
|
||||
Comments []Comment `json:"comments"`
|
||||
}
|
||||
|
||||
URL := http.GetArtworkCommentsURL(id)
|
||||
|
||||
response, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response = session.ProxyImageUrl(c, response)
|
||||
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body.Comments, nil
|
||||
}
|
||||
|
||||
func GetRelatedArtworks(c *fiber.Ctx, id string) ([]ArtworkBrief, error) {
|
||||
var body struct {
|
||||
Illusts []ArtworkBrief `json:"illusts"`
|
||||
}
|
||||
|
||||
// TODO: keep the hard-coded limit?
|
||||
URL := http.GetArtworkRelatedURL(id, 96)
|
||||
|
||||
response, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response = session.ProxyImageUrl(c, response)
|
||||
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body.Illusts, nil
|
||||
}
|
||||
|
||||
func GetArtworkByID(c *fiber.Ctx, id string, full bool) (*Illust, error) {
|
||||
URL := http.GetArtworkInformationURL(id)
|
||||
|
||||
response, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var illust struct {
|
||||
*Illust
|
||||
|
||||
Recent map[int]any `json:"userIllusts"`
|
||||
RawTags json.RawMessage `json:"tags"`
|
||||
}
|
||||
|
||||
// Parse basic illust information
|
||||
err = json.Unmarshal([]byte(response), &illust)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Begin testing here
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
cerr := make(chan error, 6)
|
||||
|
||||
wg.Add(3)
|
||||
|
||||
go func() {
|
||||
// Get illust images
|
||||
defer wg.Done()
|
||||
images, err := GetArtworkImages(c, id)
|
||||
if err != nil {
|
||||
|
||||
cerr <- err
|
||||
return
|
||||
}
|
||||
illust.Images = images
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// Get basic user information (the URL above does not contain avatars)
|
||||
defer wg.Done()
|
||||
var err error
|
||||
userInfo, err := GetUserBasicInformation(c, illust.UserID)
|
||||
if err != nil {
|
||||
cerr <- err
|
||||
return
|
||||
}
|
||||
illust.User = userInfo
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
// Extract tags
|
||||
var tags struct {
|
||||
Tags []struct {
|
||||
Tag string `json:"tag"`
|
||||
Translation map[string]string `json:"translation"`
|
||||
} `json:"tags"`
|
||||
}
|
||||
err = json.Unmarshal(illust.RawTags, &tags)
|
||||
if err != nil {
|
||||
cerr <- err
|
||||
return
|
||||
}
|
||||
|
||||
var tagsList []Tag
|
||||
for _, tag := range tags.Tags {
|
||||
var newTag Tag
|
||||
newTag.Name = tag.Tag
|
||||
newTag.TranslatedName = tag.Translation["en"]
|
||||
|
||||
tagsList = append(tagsList, newTag)
|
||||
}
|
||||
illust.Tags = tagsList
|
||||
}()
|
||||
|
||||
if full {
|
||||
wg.Add(3)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
// Get recent artworks
|
||||
ids := make([]int, 0)
|
||||
|
||||
for k := range illust.Recent {
|
||||
ids = append(ids, k)
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(sort.IntSlice(ids)))
|
||||
|
||||
idsString := ""
|
||||
count := min(len(ids), 20)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
idsString += fmt.Sprintf("&ids[]=%d", ids[i])
|
||||
}
|
||||
|
||||
recent, err := GetUserArtworks(c, illust.UserID, idsString)
|
||||
if err != nil {
|
||||
cerr <- err
|
||||
return
|
||||
}
|
||||
sort.Slice(recent[:], func(i, j int) bool {
|
||||
left, _ := strconv.Atoi(recent[i].ID)
|
||||
right, _ := strconv.Atoi(recent[j].ID)
|
||||
return left > right
|
||||
})
|
||||
illust.RecentWorks = recent
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
related, err := GetRelatedArtworks(c, id)
|
||||
if err != nil {
|
||||
cerr <- err
|
||||
return
|
||||
}
|
||||
illust.RelatedWorks = related
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if illust.CommentDisabled == 1 {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
comments, err := GetArtworkComments(c, id)
|
||||
if err != nil {
|
||||
println("here")
|
||||
cerr <- err
|
||||
return
|
||||
}
|
||||
illust.CommentsList = comments
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(cerr)
|
||||
|
||||
all_errors := []error{}
|
||||
for suberr := range cerr {
|
||||
all_errors = append(all_errors, suberr)
|
||||
}
|
||||
err_summary := errors.Join(all_errors...)
|
||||
if err_summary != nil {
|
||||
return nil, err_summary
|
||||
}
|
||||
|
||||
// If this artwork is an ugoira
|
||||
illust.IsUgoira = strings.Contains(illust.Images[0].Original, "ugoira")
|
||||
|
||||
return illust.Illust, nil
|
||||
}
|
31
core/webapi/discovery.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
http "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func GetDiscoveryArtwork(c *fiber.Ctx, mode string) ([]ArtworkBrief, error) {
|
||||
token := session.GetToken(c)
|
||||
|
||||
URL := http.GetDiscoveryURL(mode, 100)
|
||||
|
||||
var artworks []ArtworkBrief
|
||||
|
||||
resp, err := http.UnwrapWebAPIRequest(URL, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp = session.ProxyImageUrl(c, resp)
|
||||
data := gjson.Get(resp, "thumbnails.illust").String()
|
||||
|
||||
err = json.Unmarshal([]byte(data), &artworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return artworks, nil
|
||||
}
|
114
core/webapi/index.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
http "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type Pixivision struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Thumbnail string `json:"thumbnailUrl"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type RecommendedTags struct {
|
||||
Name string `json:"tag"`
|
||||
Artworks []ArtworkBrief
|
||||
}
|
||||
type LandingArtworks struct {
|
||||
Commissions []ArtworkBrief
|
||||
Following []ArtworkBrief
|
||||
Recommended []ArtworkBrief
|
||||
Newest []ArtworkBrief
|
||||
Rankings []ArtworkBrief
|
||||
Users []ArtworkBrief
|
||||
Pixivision []Pixivision
|
||||
RecommendByTags []RecommendedTags
|
||||
}
|
||||
|
||||
func GetLanding(c *fiber.Ctx, mode string) (LandingArtworks, error) {
|
||||
var pages struct {
|
||||
Pixivision []Pixivision `json:"pixivision"`
|
||||
Follow []int `json:"follow"`
|
||||
Recommended struct {
|
||||
IDs []string `json:"ids"`
|
||||
} `json:"recommend"`
|
||||
// EditorRecommended []any `json:"editorRecommend"`
|
||||
// UserRecommended []any `json:"recommendUser"`
|
||||
// Commission []any `json:"completeRequestIds"`
|
||||
RecommendedByTags []struct {
|
||||
Name string `json:"tag"`
|
||||
IDs []string `json:"ids"`
|
||||
} `json:"recommendByTag"`
|
||||
}
|
||||
|
||||
URL := http.GetLandingURL(mode)
|
||||
|
||||
var landing LandingArtworks
|
||||
|
||||
resp, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
|
||||
if err != nil {
|
||||
return landing, err
|
||||
}
|
||||
resp = session.ProxyImageUrl(c, resp)
|
||||
|
||||
artworks := map[string]ArtworkBrief{}
|
||||
|
||||
// Get thumbnails and save it into a map, since they were kept
|
||||
// separately and need to the index quickly.
|
||||
//
|
||||
// Since there are no duplicates in this object, we are unable
|
||||
// to rely to ranges (ex. one artwork in two separate sections)
|
||||
stuff := gjson.Get(resp, "thumbnails.illust")
|
||||
stuff.ForEach(func(key, value gjson.Result) bool {
|
||||
var artwork ArtworkBrief
|
||||
err = json.Unmarshal([]byte(value.String()), &artwork)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if artwork.ID != "" {
|
||||
artworks[artwork.ID] = artwork
|
||||
}
|
||||
|
||||
return true // keep iterating
|
||||
})
|
||||
|
||||
pagesStr := gjson.Get(resp, "page").String()
|
||||
err = json.Unmarshal([]byte(pagesStr), &pages)
|
||||
|
||||
if err != nil {
|
||||
return landing, err
|
||||
}
|
||||
|
||||
// Parse everything
|
||||
landing.Pixivision = pages.Pixivision
|
||||
|
||||
landing.Following = make([]ArtworkBrief, len(pages.Follow))
|
||||
for _, i := range pages.Follow {
|
||||
landing.Following = append(landing.Following, artworks[fmt.Sprint(i)])
|
||||
}
|
||||
|
||||
for _, i := range pages.RecommendedByTags {
|
||||
temp := make([]ArtworkBrief, 0)
|
||||
for _, j := range i.IDs {
|
||||
temp = append(temp, artworks[j])
|
||||
}
|
||||
landing.RecommendByTags = append(landing.RecommendByTags, RecommendedTags{Name: i.Name, Artworks: temp})
|
||||
}
|
||||
|
||||
landing.Recommended = make([]ArtworkBrief, 0)
|
||||
for _, i := range pages.Recommended.IDs {
|
||||
landing.Recommended = append(landing.Recommended, artworks[i])
|
||||
}
|
||||
|
||||
return landing, nil
|
||||
}
|
44
core/webapi/newest.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
http "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type ArtworkBrief struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
ArtistID string `json:"userId"`
|
||||
ArtistName string `json:"userName"`
|
||||
ArtistAvatar string `json:"profileImageUrl"`
|
||||
Thumbnail string `json:"url"`
|
||||
Pages int `json:"pageCount"`
|
||||
XRestrict int `json:"xRestrict"`
|
||||
AiType int `json:"aiType"`
|
||||
Bookmarked any `json:"bookmarkData"`
|
||||
}
|
||||
|
||||
func GetNewestArtworks(c *fiber.Ctx, worktype string, r18 string) ([]ArtworkBrief, error) {
|
||||
token := session.GetToken(c)
|
||||
URL := http.GetNewestArtworksURL(worktype, r18, "0")
|
||||
|
||||
var body struct {
|
||||
Artworks []ArtworkBrief `json:"illusts"`
|
||||
// LastId string
|
||||
}
|
||||
|
||||
resp, err := http.UnwrapWebAPIRequest(URL, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp = session.ProxyImageUrl(c, resp)
|
||||
|
||||
err = json.Unmarshal([]byte(resp), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body.Artworks, nil
|
||||
}
|
57
core/webapi/novel.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package core
|
||||
|
||||
import "time"
|
||||
|
||||
type Novel struct {
|
||||
BookmarkCount int `json:"bookmarkCount"`
|
||||
CommentCount int `json:"commentCount"`
|
||||
MarkerCount int `json:"markerCount"`
|
||||
CreateDate time.Time `json:"createDate"`
|
||||
UploadDate time.Time `json:"uploadDate"`
|
||||
Description string `json:"description"`
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
LikeCount int `json:"likeCount"`
|
||||
PageCount int `json:"pageCount"`
|
||||
UserID string `json:"userId"`
|
||||
UserName string `json:"userName"`
|
||||
ViewCount int `json:"viewCount"`
|
||||
IsOriginal bool `json:"isOriginal"`
|
||||
IsBungei bool `json:"isBungei"`
|
||||
XRestrict int `json:"xRestrict"`
|
||||
Restrict int `json:"restrict"`
|
||||
Content string `json:"content"`
|
||||
CoverURL string `json:"coverUrl"`
|
||||
IsBookmarkable bool `json:"isBookmarkable"`
|
||||
BookmarkData interface{} `json:"bookmarkData"`
|
||||
LikeData bool `json:"likeData"`
|
||||
PollData interface{} `json:"pollData"`
|
||||
Marker interface{} `json:"marker"`
|
||||
Tags struct {
|
||||
AuthorID string `json:"authorId"`
|
||||
IsLocked bool `json:"isLocked"`
|
||||
Tags []struct {
|
||||
Tag string `json:"tag"`
|
||||
Locked bool `json:"locked"`
|
||||
Deletable bool `json:"deletable"`
|
||||
UserID string `json:"userId"`
|
||||
UserName string `json:"userName"`
|
||||
} `json:"tags"`
|
||||
Writable bool `json:"writable"`
|
||||
} `json:"tags"`
|
||||
SeriesNavData interface{} `json:"seriesNavData"`
|
||||
HasGlossary bool `json:"hasGlossary"`
|
||||
IsUnlisted bool `json:"isUnlisted"`
|
||||
Language string `json:"language"`
|
||||
CommentOff int `json:"commentOff"`
|
||||
CharacterCount int `json:"characterCount"`
|
||||
WordCount int `json:"wordCount"`
|
||||
UseWordCount bool `json:"useWordCount"`
|
||||
ReadingTime int `json:"readingTime"`
|
||||
AiType int `json:"aiType"`
|
||||
Genre string `json:"genre"`
|
||||
}
|
||||
|
||||
func GetNovelByID(id string) {
|
||||
|
||||
}
|
38
core/webapi/personal.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
http "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func GetNewestFromFollowing(c *fiber.Ctx, mode, page string) ([]ArtworkBrief, error) {
|
||||
token := session.GetToken(c)
|
||||
URL := http.GetNewestFromFollowingURL(mode, page)
|
||||
|
||||
var body struct {
|
||||
Thumbnails json.RawMessage `json:"thumbnails"`
|
||||
}
|
||||
|
||||
var artworks struct {
|
||||
Artworks []ArtworkBrief `json:"illust"`
|
||||
}
|
||||
|
||||
resp, err := http.UnwrapWebAPIRequest(URL, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp = session.ProxyImageUrl(c, resp)
|
||||
|
||||
err = json.Unmarshal([]byte(resp), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(body.Thumbnails), &artworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return artworks.Artworks, nil
|
||||
}
|
56
core/webapi/ranking.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
http "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type Ranking struct {
|
||||
Contents []struct {
|
||||
Title string `json:"title"`
|
||||
Image string `json:"url"`
|
||||
Pages string `json:"illust_page_count"`
|
||||
ArtistName string `json:"user_name"`
|
||||
ArtistAvatar string `json:"profile_img"`
|
||||
ID int `json:"illust_id"`
|
||||
ArtistID int `json:"user_id"`
|
||||
Rank int `json:"rank"`
|
||||
} `json:"contents"`
|
||||
|
||||
Mode string `json:"mode"`
|
||||
Content string `json:"content"`
|
||||
Page int `json:"page"`
|
||||
RankTotal int `json:"rank_total"`
|
||||
CurrentDate string `json:"date"`
|
||||
PrevDateRaw json.RawMessage `json:"prev_date"`
|
||||
NextDateRaw json.RawMessage `json:"next_date"`
|
||||
PrevDate string
|
||||
NextDate string
|
||||
}
|
||||
|
||||
func GetRanking(c *fiber.Ctx, mode, content, date, page string) (Ranking, error) {
|
||||
URL := http.GetRankingURL(mode, content, date, page)
|
||||
|
||||
var ranking Ranking
|
||||
|
||||
resp := http.WebAPIRequest(URL, "")
|
||||
if !resp.Ok {
|
||||
return ranking, errors.New(resp.Message)
|
||||
}
|
||||
proxiedResp := session.ProxyImageUrl(c, resp.Body)
|
||||
|
||||
err := json.Unmarshal([]byte(proxiedResp), &ranking)
|
||||
if err != nil {
|
||||
return ranking, err
|
||||
}
|
||||
|
||||
ranking.PrevDate = strings.ReplaceAll(string(ranking.PrevDateRaw[:]), "\"", "")
|
||||
ranking.NextDate = strings.ReplaceAll(string(ranking.NextDateRaw[:]), "\"", "")
|
||||
|
||||
return ranking, nil
|
||||
}
|
98
core/webapi/rankingCalendar.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
url "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func get_weekday(n time.Weekday) int {
|
||||
switch n {
|
||||
case time.Sunday:
|
||||
return 1
|
||||
case time.Monday:
|
||||
return 2
|
||||
case time.Tuesday:
|
||||
return 3
|
||||
case time.Wednesday:
|
||||
return 4
|
||||
case time.Thursday:
|
||||
return 5
|
||||
case time.Friday:
|
||||
return 6
|
||||
case time.Saturday:
|
||||
return 7
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func GetRankingCalendar(c *fiber.Ctx, mode string, year, month int) (template.HTML, error) {
|
||||
token := session.GetToken(c)
|
||||
URL := url.GetRankingCalendarURL(mode, year, month)
|
||||
|
||||
req, _ := http.NewRequest("GET", URL, nil)
|
||||
req.Header.Add("User-Agent", "Mozilla/5.0")
|
||||
req.Header.Add("Cookie", "PHPSESSID="+token)
|
||||
// req.AddCookie(&http.Cookie{
|
||||
// Name: "PHPSESSID",
|
||||
// Value: token,
|
||||
// })
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Use the html package to parse the response body from the request
|
||||
doc, err := html.Parse(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Find and print all links on the web page
|
||||
var links []string
|
||||
var link func(*html.Node)
|
||||
link = func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "img" {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == "data-src" {
|
||||
// adds a new link entry when the attribute matches
|
||||
links = append(links, session.ProxyImageUrl(c, a.Val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// traverses the HTML of the webpage from the first child node
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
link(c)
|
||||
}
|
||||
}
|
||||
link(doc)
|
||||
|
||||
// now := time.Now()
|
||||
// yearNow := now.Year()
|
||||
// monthNow := now.Month()
|
||||
lastMonth := time.Date(year, time.Month(month), 0, 0, 0, 0, 0, time.UTC)
|
||||
thisMonth := time.Date(year, time.Month(month+1), 0, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
renderString := ""
|
||||
for i := 0; i < get_weekday(lastMonth.Weekday()); i++ {
|
||||
renderString += "<div class=\"calendar-node calendar-node-empty\"></div>"
|
||||
}
|
||||
for i := 0; i < thisMonth.Day(); i++ {
|
||||
date := fmt.Sprintf("%d%02d%02d", year, month, i+1)
|
||||
if len(links) > i {
|
||||
renderString += fmt.Sprintf(`<a href="/ranking?mode=%s&date=%s"><div class="calendar-node" style="background-image: url(%s)"><span>%d</span></div></a>`, mode, date, links[i], i+1)
|
||||
} else {
|
||||
renderString += fmt.Sprintf(`<div class="calendar-node"><span>%d</span></div>`, i+1)
|
||||
}
|
||||
}
|
||||
return template.HTML(renderString), nil
|
||||
}
|
94
core/webapi/tag.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
http "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type TagDetail struct {
|
||||
Name string `json:"tag"`
|
||||
AlternativeName string `json:"word"`
|
||||
Metadata struct {
|
||||
Detail string `json:"abstract"`
|
||||
Image string `json:"image"`
|
||||
Name string `json:"tag"`
|
||||
ID json.Number `json:"id"`
|
||||
} `json:"pixpedia"`
|
||||
}
|
||||
|
||||
type SearchArtworks struct {
|
||||
Artworks []ArtworkBrief `json:"data"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
Artworks SearchArtworks
|
||||
Popular struct {
|
||||
Permanent []ArtworkBrief `json:"permanent"`
|
||||
Recent []ArtworkBrief `json:"recent"`
|
||||
} `json:"popular"`
|
||||
RelatedTags []string `json:"relatedTags"`
|
||||
}
|
||||
|
||||
func GetTagData(c *fiber.Ctx, name string) (TagDetail, error) {
|
||||
var tag TagDetail
|
||||
|
||||
URL := http.GetTagDetailURL(name)
|
||||
|
||||
response, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return tag, err
|
||||
}
|
||||
|
||||
response = session.ProxyImageUrl(c, response)
|
||||
|
||||
err = json.Unmarshal([]byte(response), &tag)
|
||||
if err != nil {
|
||||
return tag, err
|
||||
}
|
||||
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
func GetSearch(c *fiber.Ctx, artworkType, name, order, age_settings, page string) (*SearchResult, error) {
|
||||
|
||||
URL := http.GetSearchArtworksURL(artworkType, name, order, age_settings, page)
|
||||
|
||||
response, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response = session.ProxyImageUrl(c, response)
|
||||
|
||||
// IDK how to do better than this lol
|
||||
temp := strings.ReplaceAll(string(response), `"illust"`, `"works"`)
|
||||
temp = strings.ReplaceAll(temp, `"manga"`, `"works"`)
|
||||
temp = strings.ReplaceAll(temp, `"illustManga"`, `"works"`)
|
||||
|
||||
var resultRaw struct {
|
||||
*SearchResult
|
||||
ArtworksRaw json.RawMessage `json:"works"`
|
||||
}
|
||||
var artworks SearchArtworks
|
||||
var result *SearchResult
|
||||
|
||||
err = json.Unmarshal([]byte(temp), &resultRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = resultRaw.SearchResult
|
||||
|
||||
err = json.Unmarshal([]byte(resultRaw.ArtworksRaw), &artworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.Artworks = artworks
|
||||
|
||||
return result, nil
|
||||
}
|
283
core/webapi/user.go
Normal file
|
@ -0,0 +1,283 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
http "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type FrequentTag struct {
|
||||
Name string `json:"tag"`
|
||||
TranslatedName string `json:"tag_translation"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"userId"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"imageBig"`
|
||||
Following int `json:"following"`
|
||||
MyPixiv int `json:"mypixivCount"`
|
||||
Comment template.HTML `json:"commentHtml"`
|
||||
Webpage string `json:"webpage"`
|
||||
SocialRaw json.RawMessage `json:"social"`
|
||||
Artworks []ArtworkBrief `json:"artworks"`
|
||||
Background map[string]interface{} `json:"background"`
|
||||
ArtworksCount int
|
||||
FrequentTags []FrequentTag
|
||||
Social map[string]map[string]string
|
||||
BackgroundImage string
|
||||
}
|
||||
|
||||
func (s *User) ParseSocial() {
|
||||
if string(s.SocialRaw[:]) == "[]" {
|
||||
// Fuck Pixiv
|
||||
return
|
||||
}
|
||||
|
||||
_ = json.Unmarshal(s.SocialRaw, &s.Social)
|
||||
}
|
||||
|
||||
func GetFrequentTags(c *fiber.Ctx, ids string) ([]FrequentTag, error) {
|
||||
var tags []FrequentTag
|
||||
|
||||
URL := http.GetFrequentTagsURL(ids)
|
||||
|
||||
response, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func GetUserArtworks(c *fiber.Ctx, id, ids string) ([]ArtworkBrief, error) {
|
||||
var works []ArtworkBrief
|
||||
|
||||
URL := http.GetUserFullArtworkURL(id, ids)
|
||||
|
||||
resp, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp = session.ProxyImageUrl(c, resp)
|
||||
|
||||
var body struct {
|
||||
Illusts map[int]json.RawMessage `json:"works"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(resp), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range body.Illusts {
|
||||
var illust ArtworkBrief
|
||||
err = json.Unmarshal(v, &illust)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
works = append(works, illust)
|
||||
}
|
||||
|
||||
return works, nil
|
||||
}
|
||||
|
||||
func GetUserArtworksID(c *fiber.Ctx, id, category string, page int) (string, int, error) {
|
||||
URL := http.GetUserArtworksURL(id)
|
||||
|
||||
resp, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Illusts json.RawMessage `json:"illusts"`
|
||||
Mangas json.RawMessage `json:"manga"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(resp), &body)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
var ids []int
|
||||
var idsString string
|
||||
|
||||
err = json.Unmarshal([]byte(resp), &body)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
var illusts map[int]string
|
||||
var mangas map[int]string
|
||||
count := 0
|
||||
|
||||
if err = json.Unmarshal(body.Illusts, &illusts); err != nil {
|
||||
illusts = make(map[int]string)
|
||||
}
|
||||
if err = json.Unmarshal(body.Mangas, &mangas); err != nil {
|
||||
mangas = make(map[int]string)
|
||||
}
|
||||
|
||||
// Get the keys, because Pixiv only returns IDs (very evil)
|
||||
|
||||
if category == "illustrations" || category == "artworks" {
|
||||
for k := range illusts {
|
||||
ids = append(ids, k)
|
||||
count++
|
||||
}
|
||||
}
|
||||
if category == "manga" || category == "artworks" {
|
||||
for k := range mangas {
|
||||
ids = append(ids, k)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse sort the ids
|
||||
sort.Sort(sort.Reverse(sort.IntSlice(ids)))
|
||||
|
||||
worksNumber := float64(count)
|
||||
worksPerPage := 30.0
|
||||
|
||||
if page < 1 || float64(page) > math.Ceil(worksNumber/worksPerPage)+1.0 {
|
||||
return "", -1, errors.New("No page available.")
|
||||
}
|
||||
|
||||
start := (page - 1) * int(worksPerPage)
|
||||
end := int(min(float64(page)*worksPerPage, worksNumber)) // no overflow
|
||||
|
||||
for _, k := range ids[start:end] {
|
||||
idsString += fmt.Sprintf("&ids[]=%d", k)
|
||||
}
|
||||
|
||||
return idsString, count, nil
|
||||
}
|
||||
|
||||
func GetUserArtwork(c *fiber.Ctx, id, category string, page int) (User, error) {
|
||||
var user User
|
||||
|
||||
token := session.GetToken(c)
|
||||
|
||||
URL := http.GetUserInformationURL(id)
|
||||
|
||||
resp, err := http.UnwrapWebAPIRequest(URL, token)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
resp = session.ProxyImageUrl(c, resp)
|
||||
|
||||
err = json.Unmarshal([]byte(resp), &user)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
if category != "bookmarks" {
|
||||
ids, count, err := GetUserArtworksID(c, id, category, page)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
// Check if the user has artworks available or not
|
||||
works, err := GetUserArtworks(c, id, ids)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
// IDK but the order got shuffled even though Pixiv sorted the IDs in the response
|
||||
sort.Slice(works[:], func(i, j int) bool {
|
||||
left, _ := strconv.Atoi(works[i].ID)
|
||||
right, _ := strconv.Atoi(works[j].ID)
|
||||
return left > right
|
||||
})
|
||||
user.Artworks = works
|
||||
|
||||
user.FrequentTags, err = GetFrequentTags(c, ids)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
}
|
||||
|
||||
// Artworks count
|
||||
user.ArtworksCount = count
|
||||
|
||||
} else {
|
||||
// Bookmarks
|
||||
works, count, err := GetUserBookmarks(c, id, "show", page)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
user.Artworks = works
|
||||
|
||||
// Public bookmarks count
|
||||
user.ArtworksCount = count
|
||||
|
||||
}
|
||||
|
||||
user.ParseSocial()
|
||||
|
||||
if user.Background != nil {
|
||||
user.BackgroundImage = user.Background["url"].(string)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func GetUserBookmarks(c *fiber.Ctx, id, mode string, page int) ([]ArtworkBrief, int, error) {
|
||||
page--
|
||||
|
||||
URL := http.GetUserBookmarksURL(id, mode, page)
|
||||
|
||||
resp, err := http.UnwrapWebAPIRequest(URL, "")
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
resp = session.ProxyImageUrl(c, resp)
|
||||
|
||||
var body struct {
|
||||
Artworks []json.RawMessage `json:"works"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(resp), &body)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
artworks := make([]ArtworkBrief, len(body.Artworks))
|
||||
|
||||
for index, value := range body.Artworks {
|
||||
var artwork ArtworkBrief
|
||||
|
||||
err = json.Unmarshal([]byte(value), &artwork)
|
||||
if err != nil {
|
||||
artworks[index] = ArtworkBrief{
|
||||
ID: "#",
|
||||
Title: "Deleted or Private",
|
||||
Thumbnail: "https://s.pximg.net/common/images/limit_unknown_360.png",
|
||||
}
|
||||
continue
|
||||
}
|
||||
artworks[index] = artwork
|
||||
}
|
||||
|
||||
return artworks, body.Total, nil
|
||||
}
|
|
@ -17,4 +17,7 @@ services:
|
|||
ports:
|
||||
- "8282:8282"
|
||||
environment:
|
||||
# Visit https://codeberg.org/VnPower/PixivFE/wiki/Environment-variables for more details
|
||||
- PIXIVFE_TOKEN=changethis
|
||||
- PIXIVFE_PORT=8282
|
||||
- PIXIVFE_IMAGEPROXY=pximg.cocomi.cf
|
||||
|
|
29
go.mod
|
@ -1,32 +1,33 @@
|
|||
module codeberg.org/vnpower/pixivfe
|
||||
module codeberg.org/vnpower/pixivfe/v2
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/gofiber/fiber/v2 v2.47.0
|
||||
github.com/gofiber/template/jet/v2 v2.1.3
|
||||
github.com/gofiber/fiber/v2 v2.52.0
|
||||
github.com/gofiber/template/jet/v2 v2.1.6
|
||||
github.com/tidwall/gjson v1.17.0
|
||||
golang.org/x/net v0.17.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
|
||||
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/gofiber/template v1.8.2 // indirect
|
||||
github.com/gofiber/utils v1.1.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/klauspost/compress v1.16.5 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/philhofer/fwd v1.1.2 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tinylib/msgp v1.1.8 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.47.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
)
|
||||
|
|
75
go.sum
|
@ -2,74 +2,64 @@ github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4s
|
|||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
|
||||
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofiber/fiber/v2 v2.46.0 h1:wkkWotblsGVlLjXj2dpgKQAYHtXumsK/HyFugQM68Ns=
|
||||
github.com/gofiber/fiber/v2 v2.46.0/go.mod h1:DNl0/c37WLe0g92U6lx1VMQuxGUQY5V7EIaVoEsUffc=
|
||||
github.com/gofiber/fiber/v2 v2.47.0 h1:EN5lHVCc+Pyqh5OEsk8fzRiifgwpbrP0rulQ4iNf3fs=
|
||||
github.com/gofiber/fiber/v2 v2.47.0/go.mod h1:mbFMVN1lQuzziTkkakgtKKdjfsXSw9BKR5lmcNksUoU=
|
||||
github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE=
|
||||
github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk=
|
||||
github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
|
||||
github.com/gofiber/template/jet/v2 v2.1.3 h1:l/mDuBrJAG1z2sPNQ8/Fn8PRX+6ywhhNCtEqUHEPpAE=
|
||||
github.com/gofiber/template/jet/v2 v2.1.3/go.mod h1:eWS6P1s/VloKrzOIHLkYFOfjI7KAdFb9ZEaLU6Gtca8=
|
||||
github.com/gofiber/template/jet/v2 v2.1.6 h1:PdNovaGkkMhygGW3NFHPgiq3PtMQxllvGlcUX+MsTok=
|
||||
github.com/gofiber/template/jet/v2 v2.1.6/go.mod h1:zxeIxxUeD/TMVGlvuxTSyZpSNsT2zGMfOS4SuJ6Z0Q0=
|
||||
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
|
||||
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
||||
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
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/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
|
||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.47.0 h1:y7moDoxYzMooFpT5aHgNgVOQDrS3qlkfiP9mDtGGK9c=
|
||||
github.com/valyala/fasthttp v1.47.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -77,12 +67,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
|
@ -92,9 +78,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
@ -1,211 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/models"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
func (p *PixivClient) GetArtworkImages(id string) ([]models.Image, error) {
|
||||
var resp []models.ImageResponse
|
||||
var images []models.Image
|
||||
|
||||
URL := fmt.Sprintf(ArtworkImagesURL, id)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &resp)
|
||||
if err != nil {
|
||||
return images, err
|
||||
}
|
||||
|
||||
// Extract and proxy every images
|
||||
for _, imageRaw := range resp {
|
||||
var image models.Image
|
||||
|
||||
image.Small = imageRaw.Urls["thumb_mini"]
|
||||
image.Medium = imageRaw.Urls["small"]
|
||||
image.Large = imageRaw.Urls["regular"]
|
||||
image.Original = imageRaw.Urls["original"]
|
||||
|
||||
images = append(images, image)
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetArtworkByID(id string) (*models.Illust, error) {
|
||||
var images []models.Image
|
||||
|
||||
URL := fmt.Sprintf(ArtworkInformationURL, id)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var illust struct {
|
||||
*models.Illust
|
||||
|
||||
Recent map[int]any `json:"userIllusts"`
|
||||
RawTags json.RawMessage `json:"tags"`
|
||||
}
|
||||
|
||||
// Parse basic illust information
|
||||
err = json.Unmarshal([]byte(response), &illust)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Begin testing here
|
||||
|
||||
c1 := make(chan []models.Image)
|
||||
c2 := make(chan []models.IllustShort)
|
||||
c3 := make(chan models.UserShort)
|
||||
c4 := make(chan []models.Tag)
|
||||
c5 := make(chan []models.IllustShort)
|
||||
c6 := make(chan []models.Comment)
|
||||
|
||||
go func() {
|
||||
// Get illust images
|
||||
images, err = p.GetArtworkImages(id)
|
||||
if err != nil {
|
||||
c1 <- nil
|
||||
}
|
||||
c1 <- images
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// Get recent artworks
|
||||
ids := make([]int, 0)
|
||||
|
||||
for k := range illust.Recent {
|
||||
ids = append(ids, k)
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(sort.IntSlice(ids)))
|
||||
|
||||
idsString := ""
|
||||
count := min(len(ids), 20)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
idsString += fmt.Sprintf("&ids[]=%d", ids[i])
|
||||
}
|
||||
|
||||
recent, err := p.GetUserArtworks(illust.UserID, idsString)
|
||||
if err != nil {
|
||||
c2 <- nil
|
||||
}
|
||||
sort.Slice(recent[:], func(i, j int) bool {
|
||||
left, _ := strconv.Atoi(recent[i].ID)
|
||||
right, _ := strconv.Atoi(recent[j].ID)
|
||||
return left > right
|
||||
})
|
||||
c2 <- recent
|
||||
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// Get basic user information (the URL above does not contain avatars)
|
||||
userInfo, err := p.GetUserBasicInformation(illust.UserID)
|
||||
if err != nil {
|
||||
//
|
||||
}
|
||||
c3 <- userInfo
|
||||
}()
|
||||
|
||||
go func() {
|
||||
var tagsList []models.Tag
|
||||
// Extract tags
|
||||
var tags struct {
|
||||
Tags []struct {
|
||||
Tag string `json:"tag"`
|
||||
Translation map[string]string `json:"translation"`
|
||||
} `json:"tags"`
|
||||
}
|
||||
err = json.Unmarshal(illust.RawTags, &tags)
|
||||
if err != nil {
|
||||
c4 <- nil
|
||||
}
|
||||
|
||||
for _, tag := range tags.Tags {
|
||||
var newTag models.Tag
|
||||
newTag.Name = tag.Tag
|
||||
newTag.TranslatedName = tag.Translation["en"]
|
||||
|
||||
tagsList = append(tagsList, newTag)
|
||||
}
|
||||
c4 <- tagsList
|
||||
}()
|
||||
|
||||
go func() {
|
||||
related, _ := p.GetRelatedArtworks(id)
|
||||
// Error handling...
|
||||
c5 <- related
|
||||
}()
|
||||
|
||||
go func() {
|
||||
comments, _ := p.GetArtworkComments(id)
|
||||
// Error handling...
|
||||
c6 <- comments
|
||||
}()
|
||||
|
||||
illust.Images = <-c1
|
||||
illust.RecentWorks = <-c2
|
||||
illust.User = <-c3
|
||||
illust.Tags = <-c4
|
||||
illust.RelatedWorks = <-c5
|
||||
illust.CommentsList = <-c6
|
||||
|
||||
// If this artwork is an ugoira
|
||||
illust.IsUgoira = strings.Contains(illust.Images[0].Original, "ugoira")
|
||||
|
||||
return illust.Illust, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetArtworkComments(id string) ([]models.Comment, error) {
|
||||
var body struct {
|
||||
Comments []models.Comment `json:"comments"`
|
||||
}
|
||||
|
||||
URL := fmt.Sprintf(ArtworkCommentsURL, id)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body.Comments, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetRelatedArtworks(id string) ([]models.IllustShort, error) {
|
||||
var body struct {
|
||||
Illusts []models.IllustShort `json:"illusts"`
|
||||
}
|
||||
|
||||
URL := fmt.Sprintf(ArtworkRelatedURL, id, 96)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body.Illusts, nil
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/models"
|
||||
)
|
||||
|
||||
type PixivClient struct {
|
||||
Client *http.Client
|
||||
|
||||
Cookie map[string]string
|
||||
Header map[string]string
|
||||
Lang string
|
||||
}
|
||||
|
||||
func (p *PixivClient) SetHeader(header map[string]string) {
|
||||
p.Header = header
|
||||
}
|
||||
|
||||
func (p *PixivClient) AddHeader(key, value string) {
|
||||
p.Header[key] = value
|
||||
}
|
||||
|
||||
func (p *PixivClient) SetUserAgent(value string) {
|
||||
p.AddHeader("User-Agent", value)
|
||||
}
|
||||
|
||||
func (p *PixivClient) SetCookie(cookie map[string]string) {
|
||||
p.Cookie = cookie
|
||||
}
|
||||
|
||||
func (p *PixivClient) AddCookie(key, value string) {
|
||||
p.Cookie[key] = value
|
||||
}
|
||||
|
||||
func (p *PixivClient) SetSessionID(value string) {
|
||||
p.Cookie["PHPSESSID"] = value
|
||||
}
|
||||
|
||||
func (p *PixivClient) SetLang(lang string) {
|
||||
p.Lang = lang
|
||||
}
|
||||
|
||||
func (p *PixivClient) Request(URL string, token ...string) (*http.Response, error) {
|
||||
req, _ := http.NewRequest("GET", URL, nil)
|
||||
|
||||
// Add headers
|
||||
for k, v := range p.Header {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
|
||||
for k, v := range p.Cookie {
|
||||
req.AddCookie(&http.Cookie{Name: k, Value: v})
|
||||
}
|
||||
|
||||
if token != nil {
|
||||
req.AddCookie(&http.Cookie{Name: "PHPSESSID", Value: token[0]})
|
||||
}
|
||||
|
||||
// Make a request
|
||||
resp, err := p.Client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return resp, errors.New(fmt.Sprintf("Pixiv returned code: %d for request %s", resp.StatusCode, URL))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) TextRequest(URL string, tokens ...string) (string, error) {
|
||||
var token string
|
||||
|
||||
if len(token) > 0 {
|
||||
token = tokens[0]
|
||||
}
|
||||
|
||||
/// Make a request to a URL and return the response's string body
|
||||
resp, err := p.Request(URL, token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Extract the bytes from server's response
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return string(body), err
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) PixivRequest(URL string, tokens ...string) (json.RawMessage, error) {
|
||||
/// Make a request to a Pixiv API URL with a standard response, handle errors and return the raw JSON response
|
||||
var response models.PixivResponse
|
||||
var token string
|
||||
|
||||
if len(token) > 0 {
|
||||
token = tokens[0]
|
||||
}
|
||||
body, err := p.TextRequest(URL, token)
|
||||
// body = strings.ReplaceAll(body, "i.pximg.net", configs.ProxyServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(body), &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.Error {
|
||||
// Pixiv returned an error
|
||||
return nil, errors.New("Pixiv responded: " + response.Message)
|
||||
}
|
||||
|
||||
return response.Body, nil
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package handler
|
||||
|
||||
const (
|
||||
ArtworkInformationURL = "https://www.pixiv.net/ajax/illust/%s"
|
||||
ArtworkImagesURL = "https://www.pixiv.net/ajax/illust/%s/pages"
|
||||
ArtworkRelatedURL = "https://www.pixiv.net/ajax/illust/%s/recommend/init?limit=%d"
|
||||
ArtworkCommentsURL = "https://www.pixiv.net/ajax/illusts/comments/roots?illust_id=%s&limit=100"
|
||||
ArtworkNewestURL = "https://www.pixiv.net/ajax/illust/new?limit=30&type=%s&r18=%s&lastId=%s"
|
||||
ArtworkRankingURL = "https://www.pixiv.net/ranking.php?format=json&mode=%s&content=%s%s&p=%s"
|
||||
ArtworkDiscoveryURL = "https://www.pixiv.net/ajax/discovery/artworks?mode=%s&limit=%d"
|
||||
SearchTagURL = "https://www.pixiv.net/ajax/search/tags/%s"
|
||||
SearchArtworksURL = "https://www.pixiv.net/ajax/search/%s/%s?order=%s&mode=%s&p=%s"
|
||||
SearchTopURL = "https://www.pixiv.net/ajax/search/top/%s"
|
||||
UserInformationURL = "https://www.pixiv.net/ajax/user/%s?full=1"
|
||||
UserBasicInformationURL = "https://www.pixiv.net/ajax/user/%s"
|
||||
UserArtworksURL = "https://www.pixiv.net/ajax/user/%s/profile/all"
|
||||
UserArtworksFullURL = "https://www.pixiv.net/ajax/user/%s/profile/illusts?work_category=illustManga&is_first_page=0&lang=en%s"
|
||||
UserBookmarksURL = "https://www.pixiv.net/ajax/user/%s/illusts/bookmarks?tag=&offset=%d&limit=48&rest=%s"
|
||||
FrequentTagsURL = "https://www.pixiv.net/ajax/tags/frequent/illust?%s"
|
||||
LandingPageURL = "https://www.pixiv.net/ajax/top/illust?mode=%s"
|
||||
NewestFromFollowURL = "https://www.pixiv.net/ajax/follow_latest/%s?mode=%s&p=%s"
|
||||
)
|
219
handler/misc.go
|
@ -1,219 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/models"
|
||||
"github.com/goccy/go-json"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func get_weekday(n time.Weekday) int {
|
||||
switch n {
|
||||
case time.Sunday:
|
||||
return 1
|
||||
case time.Monday:
|
||||
return 2
|
||||
case time.Tuesday:
|
||||
return 3
|
||||
case time.Wednesday:
|
||||
return 4
|
||||
case time.Thursday:
|
||||
return 5
|
||||
case time.Friday:
|
||||
return 6
|
||||
case time.Saturday:
|
||||
return 7
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetNewestArtworks(worktype string, r18 string) ([]models.IllustShort, error) {
|
||||
var newWorks []models.IllustShort
|
||||
lastID := "0"
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
URL := fmt.Sprintf(ArtworkNewestURL, worktype, r18, lastID)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Illusts []models.IllustShort `json:"illusts"`
|
||||
LastID string `json:"lastId"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newWorks = append(newWorks, body.Illusts...)
|
||||
|
||||
lastID = body.LastID
|
||||
}
|
||||
|
||||
return newWorks, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetRanking(mode string, content string, date string, page string) (models.RankingResponse, error) {
|
||||
// Ranking data is formatted differently
|
||||
var pr models.RankingResponse
|
||||
|
||||
if len(date) > 0 {
|
||||
date = "&date=" + date
|
||||
}
|
||||
|
||||
url := fmt.Sprintf(ArtworkRankingURL, mode, content, date, page)
|
||||
|
||||
s, err := p.TextRequest(url)
|
||||
|
||||
if err != nil {
|
||||
return pr, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(s), &pr)
|
||||
if err != nil {
|
||||
return pr, err
|
||||
}
|
||||
pr.PrevDate = strings.ReplaceAll(string(pr.PrevDateRaw[:]), "\"", "")
|
||||
pr.NextDate = strings.ReplaceAll(string(pr.NextDateRaw[:]), "\"", "")
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetSearch(artworkType string, name string, order string, age_settings string, page string) (*models.SearchResult, error) {
|
||||
URL := fmt.Sprintf(SearchArtworksURL, artworkType, name, order, age_settings, page)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// IDK how to do better than this lol
|
||||
temp := strings.ReplaceAll(string(response), `"illust"`, `"works"`)
|
||||
temp = strings.ReplaceAll(temp, `"manga"`, `"works"`)
|
||||
temp = strings.ReplaceAll(temp, `"illustManga"`, `"works"`)
|
||||
|
||||
var resultRaw struct {
|
||||
*models.SearchResult
|
||||
ArtworksRaw json.RawMessage `json:"works"`
|
||||
}
|
||||
var artworks models.SearchArtworks
|
||||
var result *models.SearchResult
|
||||
|
||||
err = json.Unmarshal([]byte(temp), &resultRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = resultRaw.SearchResult
|
||||
|
||||
err = json.Unmarshal([]byte(resultRaw.ArtworksRaw), &artworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.Artworks = artworks
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetDiscoveryArtwork(mode string, count int) ([]models.IllustShort, error) {
|
||||
var artworks []models.IllustShort
|
||||
|
||||
for count > 0 {
|
||||
itemsForRequest := min(100, count)
|
||||
|
||||
count -= itemsForRequest
|
||||
|
||||
URL := fmt.Sprintf(ArtworkDiscoveryURL, mode, itemsForRequest)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var thumbnail struct {
|
||||
Data json.RawMessage `json:"thumbnails"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &thumbnail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Artworks []models.IllustShort `json:"illust"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(thumbnail.Data), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
artworks = append(artworks, body.Artworks...)
|
||||
}
|
||||
|
||||
return artworks, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetRankingLog(mode string, year, month int, image_proxy string) (template.HTML, error) {
|
||||
url := fmt.Sprintf("https://www.pixiv.net/ranking_log.php?mode=%s&date=%d%02d", mode, year, month)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Use the html package to parse the response body from the request
|
||||
doc, err := html.Parse(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Find and print all links on the web page
|
||||
var links []string
|
||||
var link func(*html.Node)
|
||||
link = func(n *html.Node) {
|
||||
if n.Type == html.ElementNode && n.Data == "img" {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == "data-src" {
|
||||
// adds a new link entry when the attribute matches
|
||||
links = append(links, models.ProxyImage(a.Val, image_proxy))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// traverses the HTML of the webpage from the first child node
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
link(c)
|
||||
}
|
||||
}
|
||||
link(doc)
|
||||
|
||||
// now := time.Now()
|
||||
// yearNow := now.Year()
|
||||
// monthNow := now.Month()
|
||||
lastMonth := time.Date(year, time.Month(month), 0, 0, 0, 0, 0, time.UTC)
|
||||
thisMonth := time.Date(year, time.Month(month+1), 0, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
renderString := ""
|
||||
for i := 0; i < get_weekday(lastMonth.Weekday()); i++ {
|
||||
renderString += "<div class=\"calendar-node calendar-node-empty\"></div>"
|
||||
}
|
||||
for i := 0; i < thisMonth.Day(); i++ {
|
||||
date := fmt.Sprintf("%d%02d%02d", year, month, i+1)
|
||||
if len(links) > i {
|
||||
renderString += fmt.Sprintf(`<a href="/ranking?mode=%s&date=%s"><div class="calendar-node" style="background-image: url(%s)"><span>%d</span></div></a>`, mode, date, links[i], i+1)
|
||||
} else {
|
||||
renderString += fmt.Sprintf(`<div class="calendar-node"><span>%d</span></div>`, i+1)
|
||||
}
|
||||
}
|
||||
return template.HTML(renderString), nil
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/models"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
func (p *PixivClient) GetNewestFromFollowing(mode, page, token string) ([]models.IllustShort, error) {
|
||||
URL := fmt.Sprintf(NewestFromFollowURL, "illust", mode, page)
|
||||
|
||||
var body struct {
|
||||
Thumbnails json.RawMessage `json:"thumbnails"`
|
||||
}
|
||||
|
||||
var artworks struct {
|
||||
Artworks []models.IllustShort `json:"illust"`
|
||||
}
|
||||
|
||||
response, err := p.PixivRequest(URL, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(body.Thumbnails), &artworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return artworks.Artworks, nil
|
||||
}
|
||||
|
||||
// func (p *PixivClient) FollowUser(id string) error {
|
||||
// formData := url.Values{}
|
||||
// formData.Add("mode", "add")
|
||||
// formData.Add("type", "user")
|
||||
// formData.Add("user_id", id)
|
||||
// formData.Add("tag", "")
|
||||
// formData.Add("restrict", "0")
|
||||
// formData.Add("format", "json")
|
||||
|
||||
// init, err := p.GetCSRF()
|
||||
// println(init)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// pattern := regexp.MustCompile(`.*pixiv.context.token = "([a-z0-9]{32})"?.*`)
|
||||
// quotesPattern := regexp.MustCompile(`([a-z0-9]{32})`)
|
||||
// token := quotesPattern.FindString(pattern.FindString(init))
|
||||
// println(token)
|
||||
|
||||
// _, err = p.RequestWithFormData(FollowUserURL, formData, token)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
|
@ -1,44 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/models"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
func (p *PixivClient) GetTagData(name string) (models.TagDetail, error) {
|
||||
var tag models.TagDetail
|
||||
|
||||
URL := fmt.Sprintf(SearchTagURL, name)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return tag, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &tag)
|
||||
if err != nil {
|
||||
return tag, err
|
||||
}
|
||||
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetFrequentTags(ids string) ([]models.FrequentTag, error) {
|
||||
var tags []models.FrequentTag
|
||||
|
||||
URL := fmt.Sprintf(FrequentTagsURL, ids)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/models"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
func (p *PixivClient) GetLandingPage(mode string) (models.LandingArtworks, error) {
|
||||
var context models.LandingArtworks
|
||||
URL := fmt.Sprintf(LandingPageURL, mode)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return context, err
|
||||
}
|
||||
|
||||
type IDS struct {
|
||||
Ids []any `json:"ids"`
|
||||
}
|
||||
|
||||
var pages struct {
|
||||
Pixivision []models.Pixivision `json:"pixivision"`
|
||||
Follow []any `json:"follow"`
|
||||
Commission []any `json:"completeRequestIds"`
|
||||
Newest []any `json:"newPost"`
|
||||
Recommended IDS `json:"recommend"`
|
||||
EditorRecommended []any `json:"editorRecommend"`
|
||||
UserRecommended []any `json:"recommendUser"`
|
||||
RecommendedByTags []struct {
|
||||
models.LandingRecommendByTags
|
||||
Ids []any `json:"ids"`
|
||||
} `json:"recommendByTag"`
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Thumbnails json.RawMessage `json:"thumbnails"`
|
||||
Page json.RawMessage `json:"page"`
|
||||
}
|
||||
|
||||
var artworks struct {
|
||||
Artworks []models.IllustShort `json:"illust"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return context, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(body.Thumbnails), &artworks)
|
||||
if err != nil {
|
||||
return context, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(body.Page), &pages)
|
||||
if err != nil {
|
||||
return context, err
|
||||
}
|
||||
|
||||
context.Pixivision = pages.Pixivision
|
||||
|
||||
// Keep track
|
||||
count := len(pages.Commission)
|
||||
|
||||
context.Commissions = artworks.Artworks[:count]
|
||||
context.Following = artworks.Artworks[count : count+len(pages.Follow)]
|
||||
|
||||
count += len(pages.Follow)
|
||||
|
||||
context.Recommended = artworks.Artworks[count : count+len(pages.Recommended.Ids)]
|
||||
count += len(pages.Recommended.Ids)
|
||||
|
||||
context.Newest = artworks.Artworks[count : count+len(pages.Newest)]
|
||||
count += len(pages.Newest)
|
||||
|
||||
// For rankings, we just take 100 anyway
|
||||
context.Rankings = artworks.Artworks[count : count+100]
|
||||
count += 100
|
||||
|
||||
// IDK what this is
|
||||
count += len(pages.EditorRecommended)
|
||||
|
||||
context.Users = artworks.Artworks[count : count+len(pages.UserRecommended)*3]
|
||||
|
||||
count += len(pages.UserRecommended) * 3
|
||||
|
||||
for i := 0; i < len(pages.RecommendedByTags); i++ {
|
||||
temp := pages.RecommendedByTags[i]
|
||||
temp.Artworks = artworks.Artworks[count : count+min(len(temp.Ids), 18)]
|
||||
|
||||
context.RecommendByTags = append(context.RecommendByTags, temp.LandingRecommendByTags)
|
||||
count += len(temp.Ids)
|
||||
}
|
||||
|
||||
return context, nil
|
||||
}
|
240
handler/user.go
|
@ -1,240 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/models"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
func (p *PixivClient) GetUserArtworksID(id string, category string, page int) (string, int, error) {
|
||||
URL := fmt.Sprintf(UserArtworksURL, id)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
var ids []int
|
||||
var idsString string
|
||||
var body struct {
|
||||
Illusts json.RawMessage `json:"illusts"`
|
||||
Mangas json.RawMessage `json:"manga"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(response, &body)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
var illusts map[int]string
|
||||
var mangas map[int]string
|
||||
count := 0
|
||||
|
||||
if err = json.Unmarshal(body.Illusts, &illusts); err != nil {
|
||||
illusts = make(map[int]string)
|
||||
}
|
||||
if err = json.Unmarshal(body.Mangas, &mangas); err != nil {
|
||||
mangas = make(map[int]string)
|
||||
}
|
||||
|
||||
// Get the keys, because Pixiv only returns IDs (very evil)
|
||||
|
||||
if category == "illustrations" || category == "artworks" {
|
||||
for k := range illusts {
|
||||
ids = append(ids, k)
|
||||
count++
|
||||
}
|
||||
}
|
||||
if category == "manga" || category == "artworks" {
|
||||
for k := range mangas {
|
||||
ids = append(ids, k)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse sort the ids
|
||||
sort.Sort(sort.Reverse(sort.IntSlice(ids)))
|
||||
|
||||
worksNumber := float64(count)
|
||||
worksPerPage := 30.0
|
||||
|
||||
if page < 1 || float64(page) > math.Ceil(worksNumber/worksPerPage)+1.0 {
|
||||
return "", -1, errors.New("Page overflow")
|
||||
}
|
||||
|
||||
start := (page - 1) * int(worksPerPage)
|
||||
end := int(min(float64(page)*worksPerPage, worksNumber)) // no overflow
|
||||
|
||||
for _, k := range ids[start:end] {
|
||||
idsString += fmt.Sprintf("&ids[]=%d", k)
|
||||
}
|
||||
|
||||
return idsString, count, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetUserArtworks(id string, ids string) ([]models.IllustShort, error) {
|
||||
var works []models.IllustShort
|
||||
|
||||
URL := fmt.Sprintf(UserArtworksFullURL, id, ids)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Illusts map[int]json.RawMessage `json:"works"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(response, &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range body.Illusts {
|
||||
var illust models.IllustShort
|
||||
err = json.Unmarshal(v, &illust)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
works = append(works, illust)
|
||||
}
|
||||
|
||||
return works, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetUserBasicInformation(id string) (models.UserShort, error) {
|
||||
var user models.UserShort
|
||||
|
||||
URL := fmt.Sprintf(UserBasicInformationURL, id)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &user)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetUserInformation(id string, category string, page int) (*models.User, error) {
|
||||
var user *models.User
|
||||
|
||||
URL := fmt.Sprintf(UserInformationURL, id)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
|
||||
var body struct {
|
||||
*models.User
|
||||
Background map[string]interface{} `json:"background"`
|
||||
}
|
||||
|
||||
// Basic user information
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user = body.User
|
||||
|
||||
|
||||
if category != "bookmarks" {
|
||||
// Artworks
|
||||
ids, count, err := p.GetUserArtworksID(id, category, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
works, _ := p.GetUserArtworks(id, ids)
|
||||
// IDK but the order got shuffled even though Pixiv sorted the IDs in the response
|
||||
sort.Slice(works[:], func(i, j int) bool {
|
||||
left, _ := strconv.Atoi(works[i].ID)
|
||||
right, _ := strconv.Atoi(works[j].ID)
|
||||
return left > right
|
||||
})
|
||||
user.Artworks = works
|
||||
|
||||
// Artworks count
|
||||
user.ArtworksCount = count
|
||||
|
||||
// Frequent tags
|
||||
user.FrequentTags, err = p.GetFrequentTags(ids)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Bookmarks
|
||||
works, count, err := p.GetUserBookmarks(id, "show", page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user.Artworks = works
|
||||
|
||||
// Public bookmarks count
|
||||
user.ArtworksCount = count
|
||||
|
||||
// Parse social medias
|
||||
}
|
||||
user.ParseSocial()
|
||||
|
||||
// Background image
|
||||
if body.Background != nil {
|
||||
user.BackgroundImage = body.Background["url"].(string)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (p *PixivClient) GetUserBookmarks(id string, mode string, page int) ([]models.IllustShort, int, error) {
|
||||
page--
|
||||
URL := fmt.Sprintf(UserBookmarksURL, id, page*48, mode)
|
||||
|
||||
response, err := p.PixivRequest(URL)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Artworks []json.RawMessage `json:"works"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(response), &body)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
artworks := make([]models.IllustShort, len(body.Artworks))
|
||||
|
||||
for index, value := range body.Artworks {
|
||||
var artwork models.IllustShort
|
||||
|
||||
err = json.Unmarshal([]byte(value), &artwork)
|
||||
if err != nil {
|
||||
artworks[index] = models.IllustShort{
|
||||
ID: "#",
|
||||
Title: "Deleted or Private",
|
||||
Thumbnail: "https://s.pximg.net/common/images/limit_unknown_360.png",
|
||||
}
|
||||
continue
|
||||
}
|
||||
artworks[index] = artwork
|
||||
}
|
||||
|
||||
return artworks, body.Total, nil
|
||||
}
|
160
main.go
|
@ -1,28 +1,50 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/configs"
|
||||
"codeberg.org/vnpower/pixivfe/handler"
|
||||
"codeberg.org/vnpower/pixivfe/views"
|
||||
config "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
"codeberg.org/vnpower/pixivfe/v2/pages"
|
||||
"codeberg.org/vnpower/pixivfe/v2/serve"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cache"
|
||||
"github.com/gofiber/fiber/v2/middleware/compress"
|
||||
"github.com/gofiber/fiber/v2/middleware/limiter"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||
"github.com/gofiber/fiber/v2/utils"
|
||||
"github.com/gofiber/template/jet/v2"
|
||||
)
|
||||
|
||||
func setup_router() *fiber.App {
|
||||
// HTML templates, automatically loaded
|
||||
engine := jet.New("./template", ".jet.html")
|
||||
func CanRequestSkipLimiter(c *fiber.Ctx) bool {
|
||||
path := c.Path()
|
||||
return strings.HasPrefix(path, "/assets/") ||
|
||||
strings.HasPrefix(path, "/css/") ||
|
||||
strings.HasPrefix(path, "/js/") ||
|
||||
strings.HasPrefix(path, "/proxy/s.pximg.net/")
|
||||
}
|
||||
|
||||
engine.AddFuncMap(handler.GetTemplateFunctions())
|
||||
func main() {
|
||||
config.SetupStorage()
|
||||
config.GlobalServerConfig.InitializeConfig()
|
||||
|
||||
engine := jet.New("./views", ".jet.html")
|
||||
engine.AddFuncMap(serve.GetTemplateFunctions())
|
||||
if config.GlobalServerConfig.InDevelopment {
|
||||
engine.Reload(true)
|
||||
}
|
||||
// // no error even if the templates are invalid???
|
||||
// err := engine.Load()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
server := fiber.New(fiber.Config{
|
||||
AppName: "PixivFE",
|
||||
|
@ -57,7 +79,14 @@ func setup_router() *fiber.App {
|
|||
},
|
||||
})
|
||||
|
||||
server.Use(logger.New())
|
||||
server.Use(logger.New(
|
||||
logger.Config{
|
||||
Format: "${time} ${ip} | ${path}\n",
|
||||
Next: CanRequestSkipLimiter,
|
||||
},
|
||||
))
|
||||
|
||||
if !config.GlobalServerConfig.InDevelopment {
|
||||
server.Use(cache.New(
|
||||
cache.Config{
|
||||
Next: func(c *fiber.Ctx) bool {
|
||||
|
@ -65,14 +94,9 @@ func setup_router() *fiber.App {
|
|||
if resp_code < 200 || resp_code >= 300 {
|
||||
return true
|
||||
}
|
||||
if c.Path() == "/" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Disable cache for settings page
|
||||
if strings.Contains(c.Path(), "/settings") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return strings.Contains(c.Path(), "/settings") || c.Path() == "/"
|
||||
},
|
||||
Expiration: 5 * time.Minute,
|
||||
CacheControl: true,
|
||||
|
@ -82,12 +106,25 @@ func setup_router() *fiber.App {
|
|||
},
|
||||
},
|
||||
))
|
||||
server.Use(recover.New())
|
||||
}
|
||||
|
||||
server.Use(recover.New(recover.Config{EnableStackTrace: config.GlobalServerConfig.InDevelopment}))
|
||||
|
||||
server.Use(compress.New(compress.Config{
|
||||
Level: compress.LevelBestSpeed, // 1
|
||||
}))
|
||||
|
||||
server.Use(limiter.New(limiter.Config{
|
||||
Next: CanRequestSkipLimiter,
|
||||
Expiration: 30 * time.Second,
|
||||
Max: config.GlobalServerConfig.RequestLimit,
|
||||
LimiterMiddleware: limiter.SlidingWindow{},
|
||||
LimitReached: func(c *fiber.Ctx) error {
|
||||
log.Println("Limit Reached!")
|
||||
return errors.New("Woah! You are going too fast! I'll have to keep an eye on you.")
|
||||
},
|
||||
}))
|
||||
|
||||
// Global headers (from GotHub)
|
||||
server.Use(func(c *fiber.Ctx) error {
|
||||
c.Set("X-Frame-Options", "SAMEORIGIN")
|
||||
|
@ -100,46 +137,77 @@ func setup_router() *fiber.App {
|
|||
})
|
||||
|
||||
server.Use(func(c *fiber.Ctx) error {
|
||||
var baseURL string
|
||||
if configs.BaseURL != "localhost" {
|
||||
baseURL = "https://" + configs.BaseURL
|
||||
}
|
||||
c.Bind(fiber.Map{"FullURL": baseURL + c.OriginalURL(), "BaseURL": baseURL})
|
||||
baseURL := c.BaseURL() + c.OriginalURL()
|
||||
c.Bind(fiber.Map{"BaseURL": baseURL})
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
// Static files
|
||||
server.Static("/favicon.ico", "./template/favicon.ico")
|
||||
server.Static("css/", "./template/css")
|
||||
server.Static("assets/", "./template/assets")
|
||||
server.Static("/robots.txt", "./template/robots.txt")
|
||||
server.Static("/favicon.ico", "./views/assets/favicon.ico")
|
||||
server.Static("/robots.txt", "./views/assets/robots.txt")
|
||||
server.Static("/assets/", "./views/assets")
|
||||
server.Static("/css/", "./views/css")
|
||||
server.Static("/js/", "./views/js")
|
||||
|
||||
// Routes/Views
|
||||
views.SetupRoutes(server)
|
||||
// Routes
|
||||
|
||||
// Disable trusted proxies since we do not use any for now
|
||||
// server.SetTrustedProxies(nil)
|
||||
server.Get("/", pages.IndexPage)
|
||||
server.Get("/about", pages.AboutPage)
|
||||
server.Get("/newest", pages.NewestPage)
|
||||
server.Get("/discovery", pages.DiscoveryPage)
|
||||
server.Get("/ranking", pages.RankingPage)
|
||||
server.Get("/rankingCalendar", pages.RankingCalendarPage)
|
||||
server.Post("/rankingCalendar", pages.RankingCalendarPicker)
|
||||
server.Get("/users/:id/:category?", pages.UserPage)
|
||||
server.Get("/artworks/:id/", pages.ArtworkPage).Name("artworks")
|
||||
server.Get("/artworks-multi/:ids/", pages.ArtworkMultiPage)
|
||||
|
||||
return server
|
||||
}
|
||||
// Settings group
|
||||
settings := server.Group("/settings")
|
||||
settings.Get("/", pages.SettingsPage)
|
||||
settings.Post("/:type", pages.SettingsPost)
|
||||
|
||||
func main() {
|
||||
err := configs.ParseConfig()
|
||||
configs.SetupStorage()
|
||||
// Personal group
|
||||
self := server.Group("/self")
|
||||
self.Get("/", pages.LoginUserPage)
|
||||
self.Get("/followingWorks", pages.FollowingWorksPage)
|
||||
self.Get("/bookmarks", pages.LoginBookmarkPage)
|
||||
self.Post("/addBookmark/:id", pages.AddBookmarkRoute)
|
||||
self.Post("/deleteBookmark/:id", pages.DeleteBookmarkRoute)
|
||||
self.Post("/like/:id", pages.LikeRoute)
|
||||
|
||||
server.Get("/tags/:name", pages.TagPage)
|
||||
server.Post("/tags",
|
||||
func(c *fiber.Ctx) error {
|
||||
name := c.FormValue("name")
|
||||
|
||||
return c.Redirect("/tags/"+name, http.StatusFound)
|
||||
})
|
||||
|
||||
// Legacy illust URL
|
||||
server.Get("/member_illust.php", func(c *fiber.Ctx) error {
|
||||
return c.Redirect("/artworks/" + c.Query("illust_id"))
|
||||
})
|
||||
|
||||
// Proxy routes
|
||||
proxy := server.Group("/proxy")
|
||||
proxy.Get("/i.pximg.net/*", pages.IPximgProxy)
|
||||
proxy.Get("/s.pximg.net/*", pages.SPximgProxy)
|
||||
proxy.Get("/ugoira.com/*", pages.UgoiraProxy)
|
||||
|
||||
// Listen
|
||||
if config.GlobalServerConfig.UnixSocket != "" {
|
||||
ln, err := net.Listen("unix", config.GlobalServerConfig.UnixSocket)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("Failed to run on Unix socket. %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Printf("PixivFE is running on %v\n", config.GlobalServerConfig.UnixSocket)
|
||||
server.Listener(ln)
|
||||
} else {
|
||||
addr := config.GlobalServerConfig.Host + ":" + config.GlobalServerConfig.Port
|
||||
log.Printf("PixivFE is running on %v\n", addr)
|
||||
|
||||
r := setup_router()
|
||||
|
||||
if strings.Contains(configs.Port, "/") {
|
||||
ln, err := net.Listen("unix", configs.Port)
|
||||
if err != nil {
|
||||
panic("Failed to listen to " + configs.Port)
|
||||
// note: string concatenation is very flaky
|
||||
server.Listen(addr)
|
||||
}
|
||||
r.Listener(ln)
|
||||
}
|
||||
println("PixivFE is up and running on port " + configs.Port + "!")
|
||||
r.Listen(":" + configs.Port)
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ProxyImage(URL string, target string) string {
|
||||
if strings.Contains(URL, "s.pximg.net") {
|
||||
// This subdomain didn't get proxied
|
||||
return URL
|
||||
}
|
||||
|
||||
regex := regexp.MustCompile(`.*?pximg\.net`)
|
||||
proxy := "https://" + target
|
||||
|
||||
return regex.ReplaceAllString(URL, proxy)
|
||||
}
|
||||
|
||||
func ProxyShortArtworkSlice(artworks []IllustShort, proxy string) []IllustShort {
|
||||
for i := range artworks {
|
||||
artworks[i].Thumbnail = ProxyImage(artworks[i].Thumbnail, proxy)
|
||||
artworks[i].ArtistAvatar = ProxyImage(artworks[i].ArtistAvatar, proxy)
|
||||
}
|
||||
|
||||
return artworks
|
||||
}
|
||||
|
||||
func ProxyRecommendedByTagsSlice(artworks []LandingRecommendByTags, proxy string) []LandingRecommendByTags {
|
||||
for i := range artworks {
|
||||
artworks[i].Artworks = ProxyShortArtworkSlice(artworks[i].Artworks, proxy)
|
||||
}
|
||||
return artworks
|
||||
}
|
||||
|
||||
func ProxyRankedArtworkSlice(artworks []RankedArtwork, proxy string) []RankedArtwork {
|
||||
for i := range artworks {
|
||||
artworks[i].Image = ProxyImage(artworks[i].Image, proxy)
|
||||
artworks[i].ArtistAvatar = ProxyImage(artworks[i].ArtistAvatar, proxy)
|
||||
}
|
||||
return artworks
|
||||
}
|
||||
|
||||
func ProxyCommentsSlice(comments []Comment, proxy string) []Comment {
|
||||
for i := range comments {
|
||||
comments[i].Avatar = ProxyImage(comments[i].Avatar, proxy)
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
func ProxyPixivisionSlice(articles []Pixivision, proxy string) []Pixivision {
|
||||
for i := range articles {
|
||||
articles[i].Thumbnail = ProxyImage(articles[i].Thumbnail, proxy)
|
||||
}
|
||||
return articles
|
||||
}
|
268
models/models.go
|
@ -1,268 +0,0 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type PaginationData struct {
|
||||
PreviousPage int
|
||||
CurrentPage int
|
||||
NextPage int
|
||||
}
|
||||
|
||||
type PixivResponse struct {
|
||||
Error bool
|
||||
Message string
|
||||
Body json.RawMessage
|
||||
}
|
||||
|
||||
type RankingResponse struct {
|
||||
Artworks []RankedArtwork `json:"contents"`
|
||||
Mode string `json:"mode"`
|
||||
Content string `json:"content"`
|
||||
CurrentDate string `json:"date"`
|
||||
PrevDateRaw json.RawMessage `json:"prev_date"`
|
||||
NextDateRaw json.RawMessage `json:"next_date"`
|
||||
PrevDate string
|
||||
NextDate string
|
||||
}
|
||||
|
||||
func (s *RankingResponse) ProxyImages(proxy string) {
|
||||
s.Artworks = ProxyRankedArtworkSlice(s.Artworks, proxy)
|
||||
}
|
||||
|
||||
type ImageResponse struct {
|
||||
Urls map[string]string `json:"urls"`
|
||||
}
|
||||
|
||||
type TagResponse struct {
|
||||
AuthorID string `json:"authorId"`
|
||||
RawTags json.RawMessage `json:"tags"`
|
||||
}
|
||||
|
||||
// Pixiv returns 0, 1, 2 to filter SFW and/or NSFW artworks.
|
||||
// Those values are saved in `xRestrict`
|
||||
// 0: Safe
|
||||
// 1: R18
|
||||
// 2: R18G
|
||||
type xRestrict int
|
||||
|
||||
const (
|
||||
Safe xRestrict = 0
|
||||
R18 xRestrict = 1
|
||||
R18G xRestrict = 2
|
||||
)
|
||||
|
||||
var xRestrictModel = map[xRestrict]string{
|
||||
Safe: "",
|
||||
R18: "R18",
|
||||
R18G: "R18G",
|
||||
}
|
||||
|
||||
// Pixiv returns 0, 1, 2 to filter SFW and/or NSFW artworks.
|
||||
// Those values are saved in `aiType`
|
||||
// 0: Not rated / Unknown
|
||||
// 1: Not AI-generated
|
||||
// 2: AI-generated
|
||||
|
||||
type aiType int
|
||||
|
||||
const (
|
||||
Unrated aiType = 0
|
||||
NotAI aiType = 1
|
||||
AI aiType = 2
|
||||
)
|
||||
|
||||
var aiTypeModel = map[aiType]string{
|
||||
Unrated: "Unrated",
|
||||
NotAI: "Not AI",
|
||||
AI: "AI",
|
||||
}
|
||||
|
||||
// Pixiv gives us 5 types of an image. I don't need the mini one tho.
|
||||
// PS: Where tf is my 360x360 image, Pixiv?
|
||||
type Image struct {
|
||||
Small string `json:"thumb_mini"`
|
||||
Medium string `json:"small"`
|
||||
Large string `json:"regular"`
|
||||
Original string `json:"original"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Name string `json:"tag"`
|
||||
TranslatedName string `json:"translation"`
|
||||
}
|
||||
|
||||
type FrequentTag struct {
|
||||
Name string `json:"tag"`
|
||||
TranslatedName string `json:"tag_translation"`
|
||||
}
|
||||
|
||||
type Illust struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description template.HTML `json:"description"`
|
||||
UserID string `json:"userId"`
|
||||
UserName string `json:"userName"`
|
||||
UserAccount string `json:"userAccount"`
|
||||
Date time.Time `json:"uploadDate"`
|
||||
Images []Image `json:"images"`
|
||||
Tags []Tag `json:"tags"`
|
||||
Pages int `json:"pageCount"`
|
||||
Bookmarks int `json:"bookmarkCount"`
|
||||
Likes int `json:"likeCount"`
|
||||
Comments int `json:"commentCount"`
|
||||
Views int `json:"viewCount"`
|
||||
CommentDisabled int `json:"commentOff"`
|
||||
SanityLevel int `json:"sl"`
|
||||
XRestrict xRestrict `json:"xRestrict"`
|
||||
AiType aiType `json:"aiType"`
|
||||
User UserShort
|
||||
RecentWorks []IllustShort
|
||||
RelatedWorks []IllustShort
|
||||
CommentsList []Comment
|
||||
IsUgoira bool
|
||||
}
|
||||
|
||||
func (s *Illust) ProxyImages(proxy string) {
|
||||
for i := range s.Images {
|
||||
s.Images[i].Small = ProxyImage(s.Images[i].Small, proxy)
|
||||
s.Images[i].Medium = ProxyImage(s.Images[i].Medium, proxy)
|
||||
s.Images[i].Large = ProxyImage(s.Images[i].Large, proxy)
|
||||
s.Images[i].Original = ProxyImage(s.Images[i].Original, proxy)
|
||||
}
|
||||
for i := range s.RecentWorks {
|
||||
s.RecentWorks[i].Thumbnail = ProxyImage(s.RecentWorks[i].Thumbnail, proxy)
|
||||
}
|
||||
s.RelatedWorks = ProxyShortArtworkSlice(s.RelatedWorks, proxy)
|
||||
s.CommentsList = ProxyCommentsSlice(s.CommentsList, proxy)
|
||||
s.User.Avatar = ProxyImage(s.User.Avatar, proxy)
|
||||
}
|
||||
|
||||
type IllustShort struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description template.HTML `json:"description"`
|
||||
ArtistID string `json:"userId"`
|
||||
ArtistName string `json:"userName"`
|
||||
ArtistAvatar string `json:"profileImageUrl"`
|
||||
Date time.Time `json:"uploadDate"`
|
||||
Thumbnail string `json:"url"`
|
||||
Pages int `json:"pageCount"`
|
||||
XRestrict xRestrict `json:"xRestrict"`
|
||||
AiType aiType `json:"aiType"`
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
AuthorID string `json:"userId"`
|
||||
AuthorName string `json:"userName"`
|
||||
Avatar string `json:"img"`
|
||||
Context string `json:"comment"`
|
||||
Stamp string `json:"stampId"`
|
||||
Date string `json:"commentDate"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"userId"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"imageBig"`
|
||||
BackgroundImage string `json:"background"`
|
||||
Following int `json:"following"`
|
||||
MyPixiv int `json:"mypixivCount"`
|
||||
Comment template.HTML `json:"commentHtml"`
|
||||
Webpage string `json:"webpage"`
|
||||
SocialRaw json.RawMessage `json:"social"`
|
||||
Artworks []IllustShort `json:"artworks"`
|
||||
ArtworksCount int
|
||||
FrequentTags []FrequentTag
|
||||
Social map[string]map[string]string
|
||||
}
|
||||
|
||||
func (s *User) ProxyImages(proxy string) {
|
||||
s.Avatar = ProxyImage(s.Avatar, proxy)
|
||||
s.BackgroundImage = ProxyImage(s.BackgroundImage, proxy)
|
||||
s.Artworks = ProxyShortArtworkSlice(s.Artworks, proxy)
|
||||
}
|
||||
|
||||
func (s *User) ParseSocial() {
|
||||
if string(s.SocialRaw[:]) == "[]" {
|
||||
// Fuck Pixiv
|
||||
return
|
||||
}
|
||||
|
||||
_ = json.Unmarshal(s.SocialRaw, &s.Social)
|
||||
}
|
||||
|
||||
type UserShort struct {
|
||||
ID string `json:"userId"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"imageBig"`
|
||||
}
|
||||
|
||||
type RankedArtwork struct {
|
||||
ID int `json:"illust_id"`
|
||||
Title string `json:"title"`
|
||||
Rank int `json:"rank"`
|
||||
Pages string `json:"illust_page_count"`
|
||||
Image string `json:"url"`
|
||||
ArtistID int `json:"user_id"`
|
||||
ArtistName string `json:"user_name"`
|
||||
ArtistAvatar string `json:"profile_img"`
|
||||
}
|
||||
|
||||
type TagDetail struct {
|
||||
Name string `json:"tag"`
|
||||
AlternativeName string `json:"word"`
|
||||
Metadata struct {
|
||||
Detail string `json:"abstract"`
|
||||
Image string `json:"image"`
|
||||
Name string `json:"tag"`
|
||||
ID json.Number `json:"id"`
|
||||
} `json:"pixpedia"`
|
||||
}
|
||||
|
||||
type SearchArtworks struct {
|
||||
Artworks []IllustShort `json:"data"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
Artworks SearchArtworks
|
||||
Popular struct {
|
||||
Permanent []IllustShort `json:"permanent"`
|
||||
Recent []IllustShort `json:"recent"`
|
||||
} `json:"popular"`
|
||||
RelatedTags []string `json:"relatedTags"`
|
||||
}
|
||||
|
||||
func (s *SearchResult) ProxyImages(proxy string) {
|
||||
s.Artworks.Artworks = ProxyShortArtworkSlice(s.Artworks.Artworks, proxy)
|
||||
s.Popular.Permanent = ProxyShortArtworkSlice(s.Popular.Permanent, proxy)
|
||||
s.Popular.Recent = ProxyShortArtworkSlice(s.Popular.Recent, proxy)
|
||||
}
|
||||
|
||||
type Pixivision struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Thumbnail string `json:"thumbnailUrl"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type LandingRecommendByTags struct {
|
||||
Name string `json:"tag"`
|
||||
Artworks []IllustShort
|
||||
}
|
||||
|
||||
type LandingArtworks struct {
|
||||
Commissions []IllustShort
|
||||
Following []IllustShort
|
||||
Recommended []IllustShort
|
||||
Newest []IllustShort
|
||||
Rankings []IllustShort
|
||||
Users []IllustShort
|
||||
Pixivision []Pixivision
|
||||
RecommendByTags []LandingRecommendByTags
|
||||
}
|
32
nginx.conf
|
@ -1,32 +0,0 @@
|
|||
server {
|
||||
server_name changethis;
|
||||
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
ssl_certificate /etc/letsencrypt/live/changethis/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/changethis/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
resolver 1.1.1.1;
|
||||
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/changethis/chain.pem;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
|
||||
access_log /dev/null;
|
||||
error_log /dev/null;
|
||||
|
||||
location / {
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_pass http://localhost:8282;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name changethis;
|
||||
return 301 https://changethis$request_uri;
|
||||
}
|
16
pages/about.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func AboutPage(c *fiber.Ctx) error {
|
||||
info := fiber.Map{
|
||||
"Time": core.GlobalServerConfig.StartingTime,
|
||||
"Version": core.GlobalServerConfig.Version,
|
||||
"ImageProxy": core.GlobalServerConfig.ProxyServer,
|
||||
"AcceptLanguage": core.GlobalServerConfig.AcceptLanguage,
|
||||
}
|
||||
return c.Render("pages/about", info)
|
||||
}
|
122
pages/actions.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func pixivPostRequest(url, payload, token, csrf string) error {
|
||||
|
||||
requestBody := []byte(payload)
|
||||
|
||||
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
|
||||
req.Header.Add("User-Agent", "Mozilla/5.0")
|
||||
req.Header.Add("Accept", "application/json")
|
||||
req.Header.Add("Content-Type", "application/json; charset=utf-8")
|
||||
req.Header.Add("Cookie", "PHPSESSID="+token)
|
||||
req.Header.Add("x-csrf-token", csrf)
|
||||
// req.AddCookie(&http.Cookie{
|
||||
// Name: "PHPSESSID",
|
||||
// Value: token,
|
||||
// })
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.New("Failed to do this action.")
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New("Cannot parse the response from Pixiv. Please report this issue.")
|
||||
}
|
||||
|
||||
errr := gjson.Get(string(body), "error")
|
||||
|
||||
if !errr.Exists() {
|
||||
return errors.New("Incompatible request body.")
|
||||
}
|
||||
|
||||
if errr.Bool() {
|
||||
return errors.New("Pixiv: Invalid request.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddBookmarkRoute(c *fiber.Ctx) error {
|
||||
token := session.CheckToken(c)
|
||||
csrf := session.GetCSRFToken(c)
|
||||
|
||||
if token == "" || csrf == "" {
|
||||
return c.Redirect("/login")
|
||||
}
|
||||
|
||||
id := c.Params("id")
|
||||
if id == "" {
|
||||
return errors.New("No ID provided.")
|
||||
}
|
||||
|
||||
URL := "https://www.pixiv.net/ajax/illusts/bookmarks/add"
|
||||
payload := fmt.Sprintf(`{
|
||||
"illust_id": "%s",
|
||||
"restrict": 0,
|
||||
"comment": "",
|
||||
"tags": []
|
||||
}`, id)
|
||||
if err := pixivPostRequest(URL, payload, token, csrf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.SendString("Success")
|
||||
}
|
||||
|
||||
func DeleteBookmarkRoute(c *fiber.Ctx) error {
|
||||
token := session.CheckToken(c)
|
||||
csrf := session.GetCSRFToken(c)
|
||||
|
||||
if token == "" || csrf == "" {
|
||||
return c.Redirect("/login")
|
||||
}
|
||||
|
||||
id := c.Params("id")
|
||||
if id == "" {
|
||||
return errors.New("No ID provided.")
|
||||
}
|
||||
|
||||
// You can't unlike
|
||||
URL := "https://www.pixiv.net/ajax/illusts/bookmarks/delete"
|
||||
payload := fmt.Sprintf(`bookmark_id=%s`, id)
|
||||
if err := pixivPostRequest(URL, payload, token, csrf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.SendString("Success")
|
||||
}
|
||||
|
||||
func LikeRoute(c *fiber.Ctx) error {
|
||||
token := session.CheckToken(c)
|
||||
csrf := session.GetCSRFToken(c)
|
||||
|
||||
if token == "" || csrf == "" {
|
||||
return c.Redirect("/login")
|
||||
}
|
||||
|
||||
id := c.Params("id")
|
||||
if id == "" {
|
||||
return errors.New("No ID provided.")
|
||||
}
|
||||
|
||||
URL := "https://www.pixiv.net/ajax/illusts/like"
|
||||
payload := fmt.Sprintf(`{"illust_id": "%s"}`, id)
|
||||
if err := pixivPostRequest(URL, payload, token, csrf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.SendString("Success")
|
||||
}
|
56
pages/artwork-multi.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func ArtworkMultiPage(c *fiber.Ctx) error {
|
||||
param_ids := c.Params("ids")
|
||||
ids := strings.Split(param_ids, ",")
|
||||
|
||||
artworks := make([]ArtWorkData, len(ids))
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(ids))
|
||||
for i, id := range ids {
|
||||
if _, err := strconv.Atoi(id); err != nil {
|
||||
return errors.New("invalid id")
|
||||
}
|
||||
|
||||
go func(i int, id string) {
|
||||
defer wg.Done()
|
||||
|
||||
illust, err := core.GetArtworkByID(c, id, false)
|
||||
if err != nil {
|
||||
artworks[i] = ArtWorkData{
|
||||
Title: err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
metaDescription := ""
|
||||
for _, i := range illust.Tags {
|
||||
metaDescription += "#" + i.Name + ", "
|
||||
}
|
||||
|
||||
artworks[i] = ArtWorkData{
|
||||
Illust: illust,
|
||||
Title: illust.Title,
|
||||
PageType: "artwork",
|
||||
MetaDescription: metaDescription,
|
||||
MetaImage: illust.Images[0].Original,
|
||||
}
|
||||
}(i, id)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return c.Render("pages/artwork-multi", fiber.Map{
|
||||
"Artworks": artworks,
|
||||
})
|
||||
}
|
43
pages/artwork.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type ArtWorkData struct {
|
||||
Illust *core.Illust
|
||||
Title string
|
||||
PageType string
|
||||
MetaDescription string
|
||||
MetaImage string
|
||||
}
|
||||
|
||||
func ArtworkPage(c *fiber.Ctx) error {
|
||||
param_id := c.Params("id")
|
||||
if _, err := strconv.Atoi(param_id); err != nil {
|
||||
return errors.New("invalid id")
|
||||
}
|
||||
|
||||
illust, err := core.GetArtworkByID(c, param_id, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metaDescription := ""
|
||||
for _, i := range illust.Tags {
|
||||
metaDescription += "#" + i.Name + ", "
|
||||
}
|
||||
|
||||
// todo: passing ArtWorkData{} here will not work. maybe lowercase?
|
||||
return c.Render("pages/artwork", fiber.Map{
|
||||
"Illust": illust,
|
||||
"Title": illust.Title,
|
||||
"PageType": "artwork",
|
||||
"MetaDescription": metaDescription,
|
||||
"MetaImage": illust.Images[0].Original,
|
||||
})
|
||||
}
|
20
pages/discovery.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func DiscoveryPage(c *fiber.Ctx) error {
|
||||
mode := c.Query("mode", "safe")
|
||||
|
||||
works, err := core.GetDiscoveryArtwork(c, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("pages/discovery", fiber.Map{
|
||||
"Artworks": works,
|
||||
"Title": "Discovery",
|
||||
})
|
||||
}
|
34
pages/index.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func IndexPage(c *fiber.Ctx) error {
|
||||
|
||||
// If token is set, do the landing request...
|
||||
if token := session.CheckToken(c); token != "" {
|
||||
mode := c.Query("mode", "all")
|
||||
|
||||
works, err := core.GetLanding(c, mode)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("pages/index", fiber.Map{
|
||||
"Title": "Landing", "Data": works,
|
||||
})
|
||||
}
|
||||
|
||||
// ...otherwise, default to today's illustration ranking
|
||||
works, err := core.GetRanking(c, "daily", "illust", "", "1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Render("pages/index", fiber.Map{
|
||||
"Title": "Landing", "NoTokenData": works,
|
||||
})
|
||||
}
|
22
pages/newest.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func NewestPage(c *fiber.Ctx) error {
|
||||
worktype := c.Query("type", "illust")
|
||||
|
||||
r18 := c.Query("r18", "false")
|
||||
|
||||
works, err := core.GetNewestArtworks(c, worktype, r18)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("pages/newest", fiber.Map{
|
||||
"Items": works,
|
||||
"Title": "Newest works",
|
||||
})
|
||||
}
|
61
pages/personal.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func LoginUserPage(c *fiber.Ctx) error {
|
||||
token := session.CheckToken(c)
|
||||
|
||||
if token == "" {
|
||||
return c.Redirect("/settings")
|
||||
}
|
||||
|
||||
// The left part of the token is the member ID
|
||||
userId := strings.Split(token, "_")
|
||||
|
||||
c.Redirect("/users/" + userId[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoginBookmarkPage(c *fiber.Ctx) error {
|
||||
token := session.CheckToken(c)
|
||||
if token == "" {
|
||||
return c.Redirect("/settings")
|
||||
}
|
||||
|
||||
// The left part of the token is the member ID
|
||||
userId := strings.Split(token, "_")
|
||||
|
||||
c.Redirect("/users/" + userId[0] + "/bookmarks#checkpoint")
|
||||
return nil
|
||||
}
|
||||
|
||||
func FollowingWorksPage(c *fiber.Ctx) error {
|
||||
if token := session.CheckToken(c); token == "" {
|
||||
return c.Redirect("/settings")
|
||||
}
|
||||
|
||||
mode := c.Query("mode", "all")
|
||||
page := c.Query("page", "1")
|
||||
|
||||
pageInt, _ := strconv.Atoi(page)
|
||||
|
||||
works, err := core.GetNewestFromFollowing(c, mode, page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("pages/following", fiber.Map{
|
||||
"Title": "Following works",
|
||||
"Mode": mode,
|
||||
"Artworks": works,
|
||||
"CurPage": page,
|
||||
"Page": pageInt,
|
||||
})
|
||||
}
|
74
pages/proxy.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
config "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func SPximgProxy(c *fiber.Ctx) error {
|
||||
URL := fmt.Sprintf("https://s.pximg.net/%s", c.Params("*"))
|
||||
req, _ := http.NewRequest("GET", URL, nil)
|
||||
|
||||
// Make the request
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Set("Content-Type", resp.Header.Get("Content-Type"))
|
||||
|
||||
return c.Send([]byte(body))
|
||||
}
|
||||
|
||||
func IPximgProxy(c *fiber.Ctx) error {
|
||||
proxy_authority := config.GetImageProxy(c)
|
||||
URL := fmt.Sprintf("https://%s/%s", proxy_authority, c.Params("*"))
|
||||
req, _ := http.NewRequest("GET", URL, nil)
|
||||
|
||||
// Make the request
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Set("Content-Type", resp.Header.Get("Content-Type"))
|
||||
|
||||
return c.Send([]byte(body))
|
||||
}
|
||||
|
||||
func UgoiraProxy(c *fiber.Ctx) error {
|
||||
URL := fmt.Sprintf("https://ugoira.com/api/mp4/%s", c.Params("*"))
|
||||
req, _ := http.NewRequest("GET", URL, nil)
|
||||
|
||||
// Make the request
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Set("Content-Type", resp.Header.Get("Content-Type"))
|
||||
|
||||
return c.Send([]byte(body))
|
||||
}
|
35
pages/ranking.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func RankingPage(c *fiber.Ctx) error {
|
||||
mode := c.Query("mode", "daily")
|
||||
content := c.Query("content", "all")
|
||||
date := c.Query("date", "")
|
||||
|
||||
page := c.Query("page", "1")
|
||||
pageInt, err := strconv.Atoi(page)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
works, err := core.GetRanking(c, mode, content, date, page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("pages/rank", fiber.Map{
|
||||
"Title": "Ranking",
|
||||
"Page": pageInt,
|
||||
"PageLimit": 10, // hard-coded by pixiv
|
||||
"Mode": mode,
|
||||
"Content": content,
|
||||
"Date": date,
|
||||
"Data": works,
|
||||
})
|
||||
}
|
90
pages/rankingCalendar.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type DateWrap struct {
|
||||
Link string
|
||||
Year int
|
||||
Month int
|
||||
MonthPadded string
|
||||
MonthLiteral string
|
||||
}
|
||||
|
||||
func parseDate(t time.Time) DateWrap {
|
||||
var d DateWrap
|
||||
|
||||
year := t.Year()
|
||||
month := t.Month()
|
||||
monthPadded := fmt.Sprintf("%02d", month)
|
||||
|
||||
d.Link = fmt.Sprintf("%d-%s-01", year, monthPadded)
|
||||
d.Year = year
|
||||
d.Month = int(month)
|
||||
d.MonthPadded = monthPadded
|
||||
d.MonthLiteral = month.String()
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func RankingCalendarPicker(c *fiber.Ctx) error {
|
||||
mode := c.FormValue("mode", "daily")
|
||||
date := c.FormValue("date", "")
|
||||
|
||||
return c.RedirectToRoute("/rankingCalendar", fiber.Map{
|
||||
"queries": map[string]string{
|
||||
"mode": mode,
|
||||
"date": date,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func RankingCalendarPage(c *fiber.Ctx) error {
|
||||
mode := c.Query("mode", "daily")
|
||||
date := c.Query("date", "")
|
||||
|
||||
var year int
|
||||
var month int
|
||||
|
||||
// If the user supplied a date
|
||||
if len(date) == 10 {
|
||||
var err error
|
||||
year, err = strconv.Atoi(date[:4])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
month, err = strconv.Atoi(date[5:7])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
now := time.Now()
|
||||
year = now.Year()
|
||||
month = int(now.Month())
|
||||
}
|
||||
|
||||
realDate := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
|
||||
monthBefore := realDate.AddDate(0, -1, 0)
|
||||
monthAfter := realDate.AddDate(0, 1, 0)
|
||||
|
||||
render, err := core.GetRankingCalendar(c, mode, year, month)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("pages/rankingCalendar", fiber.Map{
|
||||
"Title": "Ranking calendar",
|
||||
"Render": render,
|
||||
"Mode": mode,
|
||||
"Year": year,
|
||||
"MonthBefore": parseDate(monthBefore),
|
||||
"MonthAfter": parseDate(monthAfter),
|
||||
"ThisMonth": parseDate(realDate),
|
||||
})
|
||||
}
|
34
pages/search.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package pages
|
||||
|
||||
// import (
|
||||
// session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
// core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
// "github.com/gofiber/fiber/v2"
|
||||
// )
|
||||
|
||||
// func IndexPage(c *fiber.Ctx) error {
|
||||
|
||||
// // If token is set, do the landing request...
|
||||
// if token := session.CheckToken(c); token != "" {
|
||||
// mode := c.Query("mode", "all")
|
||||
|
||||
// works, err := core.GetLanding(c, mode)
|
||||
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return c.Render("pages/index", fiber.Map{
|
||||
// "Title": "Landing", "Data": works,
|
||||
// })
|
||||
// }
|
||||
|
||||
// // ...otherwise, default to today's illustration ranking
|
||||
// works, err := core.GetRanking(c, "daily", "illust", "", "1")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// return c.Render("pages/index", fiber.Map{
|
||||
// "Title": "Landing", "NoTokenData": works,
|
||||
// })
|
||||
// }
|
107
pages/settings.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
session "codeberg.org/vnpower/pixivfe/v2/core/config"
|
||||
httpc "codeberg.org/vnpower/pixivfe/v2/core/http"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func setToken(c *fiber.Ctx) error {
|
||||
// Parse the value from the form
|
||||
token := c.FormValue("token")
|
||||
if token != "" {
|
||||
URL := httpc.GetNewestFromFollowingURL("all", "1")
|
||||
|
||||
_, err := httpc.UnwrapWebAPIRequest(URL, token)
|
||||
if err != nil {
|
||||
return errors.New("Cannot authorize with supplied token.")
|
||||
}
|
||||
|
||||
// Make a test request to verify the token.
|
||||
// THE TEST URL IS NSFW!
|
||||
req, _ := http.NewRequest("GET", "https://www.pixiv.net/en/artworks/115365120", nil)
|
||||
req.Header.Add("User-Agent", "Mozilla/5.0")
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "PHPSESSID",
|
||||
Value: token,
|
||||
})
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return errors.New("Cannot authorize with supplied token.")
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New("Cannot parse the response from Pixiv. Please report this issue.")
|
||||
}
|
||||
|
||||
// CSRF token
|
||||
r := regexp.MustCompile(`"token":"([0-9a-f]+)"`)
|
||||
csrf := r.FindStringSubmatch(string(body))[1]
|
||||
|
||||
if csrf == "" {
|
||||
return errors.New("Cannot authorize with supplied token.")
|
||||
}
|
||||
|
||||
// Set the tokens
|
||||
if err := session.SetSessionValue(c, "Token", token); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := session.SetSessionValue(c, "CSRF", csrf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return errors.New("You submitted an empty/invalid form.")
|
||||
}
|
||||
|
||||
func setImageServer(c *fiber.Ctx) error {
|
||||
// Parse the value from the form
|
||||
token := c.FormValue("image-proxy")
|
||||
if token != "" {
|
||||
if err := session.SetSessionValue(c, "ImageProxy", token); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return errors.New("You submitted an empty/invalid form.")
|
||||
}
|
||||
|
||||
func setLogout(c *fiber.Ctx) error {
|
||||
session.RemoveSessionValue(c, "Token")
|
||||
return nil
|
||||
}
|
||||
|
||||
func SettingsPage(c *fiber.Ctx) error {
|
||||
return c.Render("pages/settings", fiber.Map{})
|
||||
}
|
||||
|
||||
func SettingsPost(c *fiber.Ctx) error {
|
||||
t := c.Params("type")
|
||||
var err error
|
||||
|
||||
switch t {
|
||||
case "image_server":
|
||||
err = setImageServer(c)
|
||||
case "token":
|
||||
err = setToken(c)
|
||||
case "logout":
|
||||
err = setLogout(c)
|
||||
default:
|
||||
err = errors.New("No such methods available.")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Redirect("/")
|
||||
return nil
|
||||
}
|
31
pages/tag.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func TagPage(c *fiber.Ctx) error {
|
||||
queries := make(map[string]string, 3)
|
||||
queries["Mode"] = c.Query("mode", "safe")
|
||||
queries["Category"] = c.Query("category", "artworks")
|
||||
queries["Order"] = c.Query("order", "date_d")
|
||||
|
||||
name := c.Params("name")
|
||||
|
||||
page := c.Query("page", "1")
|
||||
pageInt, _ := strconv.Atoi(page)
|
||||
|
||||
tag, err := core.GetTagData(c, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := core.GetSearch(c, queries["Category"], name, queries["Order"], queries["Mode"], page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("pages/tag", fiber.Map{"Title": "Results for " + tag.Name, "Tag": tag, "Data": result, "Queries": queries, "Page": pageInt})
|
||||
}
|
50
pages/user.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package pages
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func UserPage(c *fiber.Ctx) error {
|
||||
id := c.Params("id")
|
||||
if _, err := strconv.Atoi(id); err != nil {
|
||||
return err
|
||||
}
|
||||
category := c.Params("category", "artworks")
|
||||
if !(category == "artworks" || category == "illustrations" || category == "manga" || category == "bookmarks") {
|
||||
return errors.New("Invalid work category: only illustrations, manga, artworks and bookmarks are available")
|
||||
}
|
||||
|
||||
page := c.Query("page", "1")
|
||||
pageInt, _ := strconv.Atoi(page)
|
||||
|
||||
user, err := core.GetUserArtwork(c, id, category, pageInt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var worksCount int
|
||||
var worksPerPage float64
|
||||
|
||||
if category == "bookmarks" {
|
||||
worksPerPage = 48.0
|
||||
} else {
|
||||
worksPerPage = 30.0
|
||||
}
|
||||
|
||||
worksCount = user.ArtworksCount
|
||||
pageLimit := math.Ceil(float64(worksCount) / worksPerPage)
|
||||
|
||||
return c.Render("pages/user", fiber.Map{
|
||||
"Title": user.Name,
|
||||
"User": user,
|
||||
"Category": category,
|
||||
"PageLimit": int(pageLimit),
|
||||
"Page": pageInt,
|
||||
"MetaImage": user.BackgroundImage,
|
||||
})
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Benchmark_Main(t *testing.B) {
|
||||
r, err := http.Get("http://localhost:8282/")
|
||||
if r.StatusCode != 200 {
|
||||
t.Errorf("Status code not 200: was %d", r.StatusCode)
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Ranking(t *testing.B) {
|
||||
r, err := http.Get("http://localhost:8282/ranking")
|
||||
if r.StatusCode != 200 {
|
||||
t.Errorf("Status code not 200: was %d", r.StatusCode)
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Ranking_Complex(t *testing.B) {
|
||||
r, err := http.Get("http://localhost:8282/ranking?content=all&mode=daily_r18&page=1&date=20230826")
|
||||
if r.StatusCode != 200 {
|
||||
t.Errorf("Status code not 200: was %d", r.StatusCode)
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Artwork(t *testing.B) {
|
||||
r, err := http.Get("http://localhost:8282/artworks/111157207")
|
||||
if r.StatusCode != 200 {
|
||||
t.Errorf("Status code not 200: was %d", r.StatusCode)
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Artwork_R18(t *testing.B) {
|
||||
r, err := http.Get("http://localhost:8282/artworks/111130033")
|
||||
if r.StatusCode != 200 {
|
||||
t.Errorf("Status code not 200: was %d", r.StatusCode)
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_User_NoSocial(t *testing.B) {
|
||||
r, err := http.Get("http://localhost:8282/users/1035047")
|
||||
if r.StatusCode != 200 {
|
||||
t.Errorf("Status code not 200: was %d", r.StatusCode)
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_User_WithSocial(t *testing.B) {
|
||||
r, err := http.Get("http://localhost:8282/users/59336265")
|
||||
if r.StatusCode != 200 {
|
||||
t.Errorf("Status code not 200: was %d", r.StatusCode)
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
16
run.sh
Normal file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Update the program every time you run?
|
||||
# git pull
|
||||
|
||||
# Visit https://codeberg.org/VnPower/PixivFE/wiki/Environment-variables for more details
|
||||
export PIXIVFE_TOKEN=token_123456
|
||||
export PIXIVFE_IMAGEPROXY=pximg.cocomi.cf
|
||||
# export PIXIVFE_UNIXSOCKET=/srv/http/pages/pixivfe
|
||||
export PIXIVFE_PORT=8282
|
||||
|
||||
go mod download
|
||||
go get codeberg.org/vnpower/pixivfe/v2/...
|
||||
CGO_ENABLED=0 GOOS=linux go build -mod=readonly -o pixivfe
|
||||
|
||||
./pixivfe
|
26
semgrep.yml
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Usage: semgrep scan -f semgrep.yml
|
||||
rules:
|
||||
- id: rule-0
|
||||
message: "find http requests made not with *fiber.Ctx available"
|
||||
languages: [go]
|
||||
severity: WARNING
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: |
|
||||
http.UnwrapWebAPIRequest(...)
|
||||
- pattern: |
|
||||
http.WebAPIRequest(...)
|
||||
- pattern-not-inside: |
|
||||
func $FUNC(c *fiber.Ctx, ...) $RET {
|
||||
...
|
||||
}
|
||||
- id: rule-1
|
||||
message: "find http requests made (limiter should be installed at all places)"
|
||||
languages: [go]
|
||||
severity: INFO
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: |
|
||||
http.UnwrapWebAPIRequest(...)
|
||||
- pattern: |
|
||||
http.WebAPIRequest(...)
|
|
@ -1,4 +1,4 @@
|
|||
package handler
|
||||
package serve
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -7,7 +7,10 @@ import (
|
|||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
core "codeberg.org/vnpower/pixivfe/v2/core/webapi"
|
||||
)
|
||||
|
||||
func GetRandomColor() string {
|
||||
|
@ -87,7 +90,7 @@ func ParseEmojis(s string) template.HTML {
|
|||
s = s[1 : len(s)-1] // Get the string inside
|
||||
id := emojiList[s]
|
||||
|
||||
return fmt.Sprintf(`<img src="https://s.pximg.net/common/images/emoji/%s.png" alt="(%s)" class="emoji" />`, id, s)
|
||||
return fmt.Sprintf(`<img src="/proxy/s.pximg.net/common/images/emoji/%s.png" alt="(%s)" class="emoji" />`, id, s)
|
||||
})
|
||||
return template.HTML(parsedString)
|
||||
}
|
||||
|
@ -116,36 +119,75 @@ func ParseTime(date time.Time) string {
|
|||
}
|
||||
|
||||
func CreatePaginator(base, ending string, current_page, max_page int) template.HTML {
|
||||
peek := 2
|
||||
limit := peek*peek + 1
|
||||
pageUrl := func(page int) string {
|
||||
return fmt.Sprintf(`%s%d%s`, base, page, ending)
|
||||
}
|
||||
|
||||
const (
|
||||
peek = 5 // this can be changed freely
|
||||
limit = peek*2 + 1 // tied to the algorithm below, do not change
|
||||
)
|
||||
hasMaxPage := max_page != -1
|
||||
count := 0
|
||||
pages := ""
|
||||
|
||||
pages += fmt.Sprintf(`<a href="%s1%s" class="pagination-button">«</a>`, base, ending)
|
||||
pages += fmt.Sprintf(`<a href="%s%d%s" class="pagination-button">‹</a>`, base, max(1, current_page-1), ending)
|
||||
pages += `<div class="pagination-buttons">`
|
||||
{ // "jump to page" <form>
|
||||
hidden_section := ""
|
||||
urlParsed, err := url.Parse(base)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for k, vs := range urlParsed.Query() {
|
||||
if k == "page" {
|
||||
continue
|
||||
}
|
||||
for _, v := range vs {
|
||||
hidden_section += fmt.Sprintf(`<input type="hidden" name="%s" value="%s"/>`, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
max_section := ""
|
||||
if hasMaxPage {
|
||||
max_section = fmt.Sprintf(`max="%d"`, max_page)
|
||||
}
|
||||
|
||||
pages += fmt.Sprintf(`<form action="%s">%s<input name="page" type="number" required value="%d" min="%d" %s placeholder="Page№" title="Jump To Page Number"/></form>`, pageUrl(current_page), hidden_section, current_page, 1, max_section)
|
||||
pages += "<br />"
|
||||
}
|
||||
{
|
||||
// previous,first (two buttons)
|
||||
pages += `<span>`
|
||||
{
|
||||
pages += fmt.Sprintf(`<a href="%s" class="pagination-button">«</a>`, pageUrl(1))
|
||||
pages += fmt.Sprintf(`<a href="%s" class="pagination-button">‹</a>`, pageUrl(max(1, current_page-1)))
|
||||
}
|
||||
pages += `</span>`
|
||||
|
||||
for i := current_page - peek; (i <= max_page || max_page == -1) && count < limit; i++ {
|
||||
if i < 1 {
|
||||
continue
|
||||
}
|
||||
if i == current_page {
|
||||
pages += fmt.Sprintf(`<a href="%s%d%s" class="pagination-button" id="highlight">%d</a>`, base, i, ending, i)
|
||||
|
||||
pages += fmt.Sprintf(`<a href="%s" class="pagination-button" id="highlight">%d</a>`, pageUrl(i), i)
|
||||
} else {
|
||||
pages += fmt.Sprintf(`<a href="%s%d%s" class="pagination-button">%d</a>`, base, i, ending, i)
|
||||
|
||||
pages += fmt.Sprintf(`<a href="%s" class="pagination-button">%d</a>`, pageUrl(i), i)
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
|
||||
if max_page == -1 {
|
||||
pages += fmt.Sprintf(`<a href="%s%d%s" class="pagination-button">›</a>`, base, current_page+1, ending)
|
||||
pages += fmt.Sprintf(`<a href="%s%d%s" class="pagination-button" id="disabled">»</a>`, base, max_page, ending)
|
||||
// next,last (two buttons)
|
||||
pages += `<span>`
|
||||
if hasMaxPage {
|
||||
pages += fmt.Sprintf(`<a href="%s" class="pagination-button">›</a>`, pageUrl(min(max_page, current_page+1)))
|
||||
pages += fmt.Sprintf(`<a href="%s" class="pagination-button">»</a>`, pageUrl(max_page))
|
||||
} else {
|
||||
pages += fmt.Sprintf(`<a href="%s%d%s" class="pagination-button">›</a>`, base, min(max_page, current_page+1), ending)
|
||||
pages += fmt.Sprintf(`<a href="%s%d%s" class="pagination-button">»</a>`, base, max_page, ending)
|
||||
pages += fmt.Sprintf(`<a href="%s" class="pagination-button">›</a>`, pageUrl(current_page+1))
|
||||
pages += fmt.Sprintf(`<a href="%s" class="pagination-button" class="disabled">»</a>`, pageUrl(max_page))
|
||||
}
|
||||
pages += `</span>`
|
||||
}
|
||||
pages += `</div>`
|
||||
|
||||
return template.HTML(pages)
|
||||
}
|
||||
|
@ -197,5 +239,12 @@ func GetTemplateFunctions() template.FuncMap {
|
|||
"createPaginator": func(base, ending string, current_page, max_page int) template.HTML {
|
||||
return CreatePaginator(base, ending, current_page, max_page)
|
||||
},
|
||||
"joinArtworkIds": func(artworks []core.ArtworkBrief) string {
|
||||
ids := []string{}
|
||||
for _, art := range artworks {
|
||||
ids = append(ids, art.ID)
|
||||
}
|
||||
return strings.Join(ids, ",")
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAgBA;EACE;EACA;;;AAGF;EACE;EACA;EAEA,kBAzBG;EA0BH,OAxBG;EA0BH;EACA,aAjBY;EAmBZ;EACA;EACA;;;AAGF;EACE,OAjCK;EAkCL;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;AAAA;AAAA;AAAA;EAIE;EACA;EACA;;;AAGF;AAAA;EAEE;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;AAAA;AAAA;EAGE;EACA,eA9Ec;EA+Ed;EACA;;;AAGF;AAAA;EAEE;EACA,kBA3Fa;EA4Fb,OA1Fa;;;AA6Ff;AAAA;EAEE,kBAjGa;;;AAoGf;EACE;EACA;;;AAGF;EACE;EACA,eAtGc;EAuGd;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQE,eA3Hc;EA4Hd;EACA;EACA;EACA,aA9HY;EA+HZ;EACA;;;AAGF;AAAA;AAAA;AAAA;EAIE,aAlIY;;;AAqId;AAAA;AAAA;AAAA;EAIE;;;AAGF;AAAA;AAAA;AAAA;EAIE;EACA;;;AAGF;AAAA;AAAA;AAAA;AAAA;AAAA;EAME,kBAlKK;EAmKL;EACA,OAtKG;;;AAyKL;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA,kBAhMW;EAiMX;;;AAIJ;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA,OAtNH;;AA2NC;EACE;EACA;;AAKF;EACE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;;AAGF;EACE,kBAvPD;EAwPC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EAEA;EACA;;AAEA;EACE;EACA;EACA;EACA,OA7QL;EA8QK;EACA;EACA;;AAEA;EACE,kBApRG;;AAuRL;EACE;EACA;EACA;EACA;;;AAoBZ;EAEI;IACE;;;AAKN;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EAIA;EACA;;;AAGF;EACE;EACA;EACA;;;AAKA;AAAA;EACE;EACA,OA7VC;;AAiWD;AAAA;EACE,OAtVgB;EAuVhB,QAvVgB;;AA0VlB;AAAA;EACE,WA3VgB;;AA8VlB;AAAA;EACE,WA/VgB;;AAgWhB;AAAA;EACE,WAlWc;;AAwWlB;AAAA;EACE,OAzWgB;EA0WhB,QA1WgB;;AA6WlB;AAAA;EACE,WA9WgB;;AAiXlB;AAAA;EACE,WAlXgB;;AAoXhB;AAAA;EACE,WArXc;;AA0XpB;AAAA;EACE;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBAraI;EAsaJ,OAzaH;;AA4aC;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OApbH;EAqbG;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;;AAKN;AAAA;EACE;EACA;EACA,eAjcU;;AAocZ;AAAA;EACE;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;AAAA;EACE;EACA;;AAEA;AAAA;EACE;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;;;AAOV;EACE,kBAvfa;EAwfb,eAnfc;EAofd;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAEA;EACE;;AAEF;EACE;;AACA;EACE,OA5hBK;;AAkiBb;EACE;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE,OAxjBI;;AA2jBN;EACE,OA7jBD;EA8jBC;EACA;;AAKN;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA,OAzmBD;;AA2mBC;EACE;EACA;EACA;EACA;EACA;;;AAMR;EACE;EAOA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;;AAKN;EACE;EACA;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA,kBAlsBa;EAmsBb;;AAEA;EACE;EACA,OAtsBC;EAusBD,kBAxsBW;EAysBX;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;IACE;;EAGF;AAAA;IAEE;;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;EAEA;EACA;;AAIA;EACE;;;AAKN;EACE,kBAnvBa;EAovBb;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE;EACA","file":"style.css"}
|
|
@ -1,6 +0,0 @@
|
|||
{{ range . }} {{ if .ID }}
|
||||
<div class="artwork-thumbnail-small artwork-thumbnail">
|
||||
{{ include "thumbnail-dt" . }} {{ include "thumbnail-tt" . }} {{ include
|
||||
"thumbnail-at" . }}
|
||||
</div>
|
||||
{{ end }} {{ end }}
|
|
@ -1,3 +0,0 @@
|
|||
<div class="artwork-title">
|
||||
<a href="/artworks/{{ .ID }}"> {{ .Title }} </a>
|
||||
</div>
|
|
@ -1,36 +0,0 @@
|
|||
<div class="container">
|
||||
<h2>Ranking calendar ({{ Month }} {{ Year }})</h2>
|
||||
<div class="switcher">
|
||||
{{ url := "/ranking_log?date=" + ThisMonth + "&mode=" }}
|
||||
<span class="switch-title">Modes</span>
|
||||
<a href="{{ url }}daily" class="switch-button">Daily</a>
|
||||
<a href="{{ url }}weekly" class="switch-button">Weekly</a>
|
||||
<a href="{{ url }}monthly" class="switch-button">Monthly</a>
|
||||
<a href="{{ url }}rookie" class="switch-button">Rookie</a>
|
||||
<span class="switch-seperator"></span>
|
||||
<a href="{{ url }}daily_r18" class="switch-button">Daily (R-18)</a>
|
||||
<a href="{{ url }}weekly_r18" class="switch-button">Weekly (R-18)</a>
|
||||
</div>
|
||||
<a href="https://codeberg.org/VnPower/pixivfe/wiki/Questions-you-may-ask"><small>R18?</small></a>
|
||||
|
||||
<br>
|
||||
<div id="calendar">
|
||||
<div class="calendar-weeks">
|
||||
<div>Sun</div>
|
||||
<div>Mon</div>
|
||||
<div>Tue</div>
|
||||
<div>Wed</div>
|
||||
<div>Thu</div>
|
||||
<div>Fri</div>
|
||||
<div>Sat</div>
|
||||
</div>
|
||||
<div class="calendar-board">
|
||||
{{ raw: Render }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
{{ url := "/ranking_log?mode=" + Mode + "&date=" }}
|
||||
<a href="{{ url }}{{ MonthBefore }}" class="pagination-button ">Last month</a>
|
||||
<a href="{{ url }}{{ MonthAfter }}" class="pagination-button ">Next month</a>
|
||||
</div>
|
||||
</div>
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 303 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 383 B |
Before Width: | Height: | Size: 830 B After Width: | Height: | Size: 830 B |
Before Width: | Height: | Size: 732 B After Width: | Height: | Size: 732 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 577 B After Width: | Height: | Size: 577 B |
Before Width: | Height: | Size: 569 B After Width: | Height: | Size: 569 B |
Before Width: | Height: | Size: 949 B After Width: | Height: | Size: 949 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 466 B |
Before Width: | Height: | Size: 125 B After Width: | Height: | Size: 125 B |
Before Width: | Height: | Size: 159 B After Width: | Height: | Size: 159 B |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 776 B After Width: | Height: | Size: 776 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 903 B |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 897 B After Width: | Height: | Size: 897 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -10,8 +10,12 @@ body {
|
|||
font-size: 1.8rem;
|
||||
font-family: "Roboto", "Open Sans", "Noto Sans", sans-serif, "Noto Sans CJK JP";
|
||||
margin-bottom: 10px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media (min-width: 440px) {
|
||||
main {
|
||||
margin-inline: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -22,7 +26,6 @@ a {
|
|||
/* Scrollbars */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #118bee auto;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
|
@ -180,18 +183,51 @@ input[type=submit][hidden] {
|
|||
.pagination {
|
||||
text-align: center;
|
||||
}
|
||||
.pagination form {
|
||||
display: inline-block;
|
||||
}
|
||||
.pagination .pagination-buttons {
|
||||
text-align: center;
|
||||
}
|
||||
.pagination .pagination-buttons input {
|
||||
width: 5em;
|
||||
}
|
||||
.pagination .pagination-button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.pagination #highlight {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
.pagination #disabled {
|
||||
.pagination .disabled {
|
||||
pointer-events: none;
|
||||
background-color: #222;
|
||||
filter: brightness(1);
|
||||
}
|
||||
|
||||
#loading-indicator {
|
||||
z-index: 2;
|
||||
isolation: isolate;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 4px;
|
||||
margin-bottom: -4px;
|
||||
animation: rolling-something 3s linear infinite;
|
||||
animation-play-state: paused;
|
||||
background-size: 60px auto;
|
||||
}
|
||||
#loading-indicator.htmx-request {
|
||||
background-image: repeating-linear-gradient(90deg, #118bee 0px 30px, transparent 30px 60px);
|
||||
animation-play-state: running;
|
||||
}
|
||||
@keyframes rolling-something {
|
||||
0% {
|
||||
background-position-x: -20px;
|
||||
}
|
||||
100% {
|
||||
background-position-x: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
|
@ -231,6 +267,8 @@ nav .navigation-wrapper .sidebar-label {
|
|||
cursor: pointer;
|
||||
}
|
||||
nav .navigation-wrapper .sidebar {
|
||||
z-index: 1;
|
||||
isolation: isolate;
|
||||
background-color: #131516;
|
||||
position: absolute;
|
||||
padding-top: 6px;
|
||||
|
@ -238,7 +276,6 @@ nav .navigation-wrapper .sidebar {
|
|||
width: 220px;
|
||||
transform: translateX(-220px);
|
||||
transition: transform 250ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
z-index: 999;
|
||||
}
|
||||
nav .navigation-wrapper .sidebar br {
|
||||
align-self: stretch;
|
||||
|
@ -285,10 +322,20 @@ nav .navigation-wrapper .sidebar .sidebar-list .sidebar-item img {
|
|||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin-inline: auto;
|
||||
padding-inline: 4px;
|
||||
}
|
||||
@media (min-width: 440px) {
|
||||
.container {
|
||||
padding-inline: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.artwork-container-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.artwork-container {
|
||||
|
@ -549,13 +596,13 @@ nav .navigation-wrapper .sidebar .sidebar-list .sidebar-item img {
|
|||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.illust .illust-other-works > a {
|
||||
.illust .illust-other-works a.illust-other-works-author {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
}
|
||||
.illust .illust-other-works > a > img {
|
||||
.illust .illust-other-works a.illust-other-works-author > img {
|
||||
aspect-ratio: 1/1;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
@ -591,8 +638,6 @@ nav .navigation-wrapper .sidebar .sidebar-list .sidebar-item img {
|
|||
aspect-ratio: 1/1;
|
||||
width: 170px;
|
||||
height: 170px;
|
||||
}
|
||||
.user .user-avatar img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.user .user-social {
|
||||
|
@ -610,6 +655,20 @@ nav .navigation-wrapper .sidebar .sidebar-list .sidebar-item img {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.user-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 8px;
|
||||
column-gap: 1em;
|
||||
margin-block: 4px 20px;
|
||||
}
|
||||
.user-tags > a {
|
||||
line-height: 1;
|
||||
}
|
||||
.user-tags > a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#calendar {
|
||||
width: 100%;
|
||||
height: auto;
|
1
views/css/style.css.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["style.scss"],"names":[],"mappings":"AAiBA;EACE;;;AAGF;EACE;EACA;EAEA,kBAzBG;EA0BH,OAxBG;EA0BH;EACA,aAjBY;EAmBZ;;;AAIA;EADF;IAEI;;;;AAIJ;EACE,OArCK;EAsCL;;;AAGF;AACA;EACI;;;AAKJ;EACI;EACA;;;AAGJ;EACI;;;AAGJ;EACI,kBA1DG;EA2DH;;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;AAAA;AAAA;AAAA;EAIE;EACA;EACA;;;AAGF;AAAA;EAEE;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;AAAA;AAAA;EAGE;EACA,eAvGc;EAwGd;EACA;;;AAGF;AAAA;EAEE;EACA,kBApHa;EAqHb,OAnHa;;;AAsHf;AAAA;EAEE,kBA1Ha;;;AA6Hf;EACE;EACA;;;AAGF;EACE;EACA,eA/Hc;EAgId;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQE,eApJc;EAqJd;EACA;EACA;EACA,aAvJY;EAwJZ;EACA;;;AAGF;AAAA;AAAA;AAAA;EAIE,aA3JY;;;AA8Jd;AAAA;AAAA;AAAA;EAIE;;;AAGF;AAAA;AAAA;AAAA;EAIE;EACA;;;AAGF;AAAA;AAAA;AAAA;AAAA;AAAA;EAME,kBA3LK;EA4LL;EACA,OA/LG;;;AAkML;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAEA;EACD;;AAID;EACE;;AAGF;EACE;;AAGF;EACE;EACA,kBArOW;EAsOX;;;AAKJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAGA;;AACA;EACE;EACA;;AAEF;EACE;IACE;;EAEF;IACE;;;;AAKN;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA,OAtRH;;AA2RC;EACE;EACA;;AAKF;EACE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;EACA,kBAzTD;EA0TC;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EAEA;EACA;;AAEA;EACE;EACA;EACA;EACA,OA9UL;EA+UK;EACA;EACA;;AAEA;EACE,kBArVG;;AAwVL;EACE;EACA;EACA;EACA;;;AAoBZ;EAEI;IACE;;;AAKN;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AACA;EAJF;IAKI;;;;AAIJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EAEA;EAIA;EACA;;;AAGF;EACE;EACA;EACA;;;AAKA;AAAA;EACE;EACA,OAvaC;;AA2aD;AAAA;EACE,OAhagB;EAiahB,QAjagB;;AAoalB;AAAA;EACE,WAragB;;AAwalB;AAAA;EACE,WAzagB;;AA0ahB;AAAA;EACE,WA5ac;;AAkblB;AAAA;EACE,OAnbgB;EAobhB,QApbgB;;AAublB;AAAA;EACE,WAxbgB;;AA2blB;AAAA;EACE,WA5bgB;;AA8bhB;AAAA;EACE,WA/bc;;AAocpB;AAAA;EACE;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBA/eI;EAgfJ,OAnfH;;AAsfC;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OA9fH;EA+fG;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACE;;AAKN;AAAA;EACE;EACA;EACA,eA3gBU;;AA8gBZ;AAAA;EACE;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAIJ;AAAA;EACE;EACA;;AAEA;AAAA;EACE;EACA;;AAEA;AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EACE;EACA;EACA;EACA;EACA;;;AAOV;EACE,kBAjkBa;EAkkBb,eA7jBc;EA8jBd;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;;AAEA;EACE;;AAEF;EACE;;AACA;EACE,OAtmBK;;AA4mBb;EACE;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE,OAloBI;;AAqoBN;EACE,OAvoBD;EAwoBC;EACA;;AAKN;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA,OAnrBD;;AAqrBC;EACE;EACA;EACA;EACA;EACA;;;AAMR;EACE;EAOA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;;;AAKN;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;;AACA;EACE;;;AAKN;EACE;EACA;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA,kBAjyBa;EAkyBb;;AAEA;EACE;EACA,OAryBC;EAsyBD,kBAvyBW;EAwyBX;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;IACE;;EAGF;AAAA;IAEE;;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;EAEA;EACA;;AAIA;EACE;;;AAKN;EACE,kBAl1Ba;EAm1Bb;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE;EACA","file":"style.css"}
|
|
@ -13,6 +13,7 @@ $color-shadow: #bbbbbb20;
|
|||
$font-family: "Roboto", "Open Sans", "Noto Sans", sans-serif, "Noto Sans CJK JP";
|
||||
$small-artwork-width: 184px;
|
||||
$large-artwork-width: 288px;
|
||||
$small-breakpoint: 440px;
|
||||
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
|
@ -29,8 +30,12 @@ body {
|
|||
font-family: $font-family;
|
||||
|
||||
margin-bottom: 10px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
main {
|
||||
@media (min-width: $small-breakpoint) {
|
||||
margin-inline: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -41,7 +46,8 @@ a {
|
|||
/* Scrollbars */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: $link auto;
|
||||
// invalid line
|
||||
// scrollbar-color: $link auto;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
|
@ -200,6 +206,18 @@ input[type="submit"][hidden] {
|
|||
.pagination {
|
||||
text-align: center;
|
||||
|
||||
form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.pagination-buttons {
|
||||
text-align: center;
|
||||
|
||||
input {
|
||||
width: 5em;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
@ -208,13 +226,40 @@ input[type="submit"][hidden] {
|
|||
filter: brightness($hover-brightness);
|
||||
}
|
||||
|
||||
#disabled {
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
background-color: $bg-secondary;
|
||||
filter: brightness(1);
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/63787241/css-how-to-create-an-infinitely-moving-repeating-linear-gradient/63787567#63787567
|
||||
#loading-indicator {
|
||||
z-index: 2;
|
||||
isolation: isolate;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 4px;
|
||||
margin-bottom: -4px;
|
||||
animation: rolling-something 3s linear infinite;
|
||||
animation-play-state: paused;
|
||||
|
||||
$segment-size: 60px;
|
||||
background-size: $segment-size auto;
|
||||
&.htmx-request {
|
||||
background-image: repeating-linear-gradient(90deg, $link 0px 30px, transparent 30px 60px);
|
||||
animation-play-state: running;
|
||||
}
|
||||
@keyframes rolling-something {
|
||||
0% {
|
||||
background-position-x: -20px
|
||||
}
|
||||
100% {
|
||||
background-position-x: $segment-size - 20px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
|
@ -264,6 +309,8 @@ nav {
|
|||
}
|
||||
|
||||
.sidebar {
|
||||
z-index: 1;
|
||||
isolation: isolate;
|
||||
background-color: $bg;
|
||||
position: absolute;
|
||||
padding-top: 6px;
|
||||
|
@ -271,7 +318,6 @@ nav {
|
|||
width: 220px;
|
||||
transform: translateX(-220px);
|
||||
transition: transform 250ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
z-index: 999;
|
||||
|
||||
br {
|
||||
align-self: stretch;
|
||||
|
@ -342,14 +388,23 @@ nav {
|
|||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin-inline: auto;
|
||||
padding-inline: 4px;
|
||||
@media (min-width: $small-breakpoint) {
|
||||
padding-inline: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.artwork-container-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.artwork-container {
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(calc($small-artwork-width + 15px), 1fr)
|
||||
|
@ -632,7 +687,7 @@ nav {
|
|||
}
|
||||
|
||||
.illust-other-works {
|
||||
& > a {
|
||||
a.illust-other-works-author {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
|
@ -684,11 +739,8 @@ nav {
|
|||
aspect-ratio: 1/1;
|
||||
width: 170px;
|
||||
height: 170px;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.user-social {
|
||||
text-align: center;
|
||||
|
@ -709,6 +761,20 @@ nav {
|
|||
}
|
||||
}
|
||||
|
||||
.user-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 8px;
|
||||
column-gap: 1em;
|
||||
margin-block: 4px 20px;
|
||||
&>a {
|
||||
line-height: 1;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#calendar {
|
||||
width: 100%;
|
||||
height: auto;
|
1
views/js/htmx@1.9.10.min.js
vendored
Normal file
|
@ -7,47 +7,43 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="View this page on PixivFE." />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'" />
|
||||
<meta name="referrer" content="no-referrer, same-origin" />
|
||||
<link href="/css/style.css" rel="stylesheet" />
|
||||
<script src="/js/htmx@1.9.10.min.js" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"></script>
|
||||
|
||||
{{ if BaseURL }}
|
||||
<meta property="og:site_name" content="PixivFE" />
|
||||
<meta property="og:title" content="{{ title }}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="{{ FullURL }}" />
|
||||
<meta property="twitter:url" content="{{ FullURL }}">
|
||||
<meta name="twitter:title" content="{{ title }}" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="{{ BaseURL }}">
|
||||
<meta property="og:url" content="{{ BaseURL }}" />
|
||||
<meta property="og:site_name" content="PixivFE" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta content="summary_large_image" name="twitter:card" />
|
||||
|
||||
{{ if isset(PageType) }} {{ if PageType == "artwork" }} <meta
|
||||
property="og:description"
|
||||
content="View this artwork by {{ Illust.User.Name }} on PixivFE."
|
||||
/>
|
||||
<meta name="twitter:description" content="View this artwork by {{ Illust.User.Name }} on PixivFE.">
|
||||
<meta property="og:image" content="{{ Illust.Images[0].Large }}" />
|
||||
<meta name="twitter:image" content="{{ Illust.Images[0].Large }}">
|
||||
{{ else if PageType == "user" }}
|
||||
{{ if isset(MetaDescription) }}
|
||||
<meta
|
||||
property="og:description"
|
||||
content="View this user's profile on PixivFE."
|
||||
content="{{MetaDescription}}"
|
||||
/>
|
||||
<meta name="twitter:description" content="View this user's profile on PixivFE.">
|
||||
{{ if User.BackgroundImage }}
|
||||
<meta property="og:image" content="{{ User.BackgroundImage }}" />
|
||||
<meta name="twitter:image" content="{{ User.BackgroundImage }}">
|
||||
{{ else }}
|
||||
<meta property="og:image" content="{{ User.Avatar }}" />
|
||||
<meta name="twitter:image" content="{{ User.Avatar }}">
|
||||
{{ end }} {{ end }} {{ end }} {{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if isset(MetaImage) }}
|
||||
<meta property="og:image" content="{{ MetaImage }}" />
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body hx-boost="true" hx-target="main" hx-select="main" hx-indicator="#loading-indicator">
|
||||
<script>
|
||||
document.body.addEventListener('htmx:beforeOnLoad', function (evt) {
|
||||
evt.detail.shouldSwap = true;
|
||||
evt.detail.isError = false;
|
||||
});
|
||||
</script>
|
||||
<div id="loading-indicator"></div>
|
||||
<nav>
|
||||
<div class="navigation-wrapper">
|
||||
<span>
|
||||
<input type="checkbox" class="sidebar-toggler" id="sidebar-toggler" checked />
|
||||
<input type="checkbox" class="sidebar-toggler" id="sidebar-toggler" />
|
||||
<label for="sidebar-toggler" class="sidebar-label">
|
||||
<img
|
||||
src="/assets/menu-thin.png"
|
||||
|
@ -65,11 +61,14 @@
|
|||
<a class="sidebar-item" href="/ranking">
|
||||
<img src="/assets/crown.png" alt="icon" />Ranking</a
|
||||
>
|
||||
<a class="sidebar-item" href="/rankingCalendar">
|
||||
<img src="/assets/calendar.png" alt="icon" />Ranking history</a
|
||||
>
|
||||
<a class="sidebar-item" href="/newest">
|
||||
<img src="/assets/sparkling.png" alt="icon" />Newest</a
|
||||
>
|
||||
<br />
|
||||
<a class="sidebar-item" href="/self/following_works">
|
||||
<a class="sidebar-item" href="/self/followingWorks">
|
||||
<img src="/assets/users.png" alt="icon" />Latest by followed</a
|
||||
>
|
||||
<a class="sidebar-item" href="/self/bookmarks">
|
||||
|
@ -95,7 +94,7 @@
|
|||
</ul>
|
||||
</div>
|
||||
<span class="navbar-brand">
|
||||
<img src="https://pixivfe.exozy.me/favicon.ico" alt="Icon" />
|
||||
<img src="/assets/favicon.ico" alt="Icon" />
|
||||
<a href="/">
|
||||
<span>PixivFE</span>
|
||||
</a>
|
||||
|
@ -131,6 +130,8 @@
|
|||
</div>
|
||||
<div class="navbar-shadow"></div>
|
||||
</nav>
|
||||
<main>
|
||||
{{ embed() }}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
372
views/pages.go
|
@ -1,372 +0,0 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codeberg.org/vnpower/pixivfe/configs"
|
||||
"codeberg.org/vnpower/pixivfe/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func get_session_value(c *fiber.Ctx, key string) *string {
|
||||
sess, err := configs.Store.Get(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
value := sess.Get(key)
|
||||
if value != nil {
|
||||
placeholder := value.(string)
|
||||
return &placeholder
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func artwork_page(c *fiber.Ctx) error {
|
||||
image_proxy := get_session_value(c, "image-proxy")
|
||||
if image_proxy == nil {
|
||||
image_proxy = &configs.ProxyServer
|
||||
}
|
||||
|
||||
id := c.Params("id")
|
||||
if _, err := strconv.Atoi(id); err != nil {
|
||||
return errors.New("Bad id")
|
||||
}
|
||||
|
||||
illust, err := PC.GetArtworkByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
illust.ProxyImages(*image_proxy)
|
||||
|
||||
// Optimize this
|
||||
return c.Render("pages/artwork", fiber.Map{
|
||||
"Illust": illust,
|
||||
"Title": illust.Title,
|
||||
"PageType": "artwork",
|
||||
})
|
||||
}
|
||||
|
||||
func index_page(c *fiber.Ctx) error {
|
||||
had_token := true
|
||||
image_proxy := get_session_value(c, "image-proxy")
|
||||
if image_proxy == nil {
|
||||
image_proxy = &configs.ProxyServer
|
||||
}
|
||||
token := get_session_value(c, "token")
|
||||
if token == nil {
|
||||
had_token = false
|
||||
token = &configs.Token
|
||||
}
|
||||
|
||||
PC := NewPixivClient(5000)
|
||||
PC.SetSessionID(*token)
|
||||
PC.SetUserAgent(configs.UserAgent)
|
||||
PC.AddHeader("Accept-Language", configs.AcceptLanguage)
|
||||
|
||||
mode := c.Query("mode", "all")
|
||||
|
||||
artworks, err := PC.GetLandingPage(mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if had_token {
|
||||
artworks.Following = models.ProxyShortArtworkSlice(artworks.Following, *image_proxy)
|
||||
artworks.Commissions = models.ProxyShortArtworkSlice(artworks.Commissions, *image_proxy)
|
||||
artworks.Recommended = models.ProxyShortArtworkSlice(artworks.Recommended, *image_proxy)
|
||||
artworks.Newest = models.ProxyShortArtworkSlice(artworks.Newest, *image_proxy)
|
||||
artworks.Users = models.ProxyShortArtworkSlice(artworks.Users, *image_proxy)
|
||||
artworks.RecommendByTags = models.ProxyRecommendedByTagsSlice(artworks.RecommendByTags, *image_proxy)
|
||||
}
|
||||
|
||||
artworks.Rankings = models.ProxyShortArtworkSlice(artworks.Rankings, *image_proxy)
|
||||
artworks.Pixivision = models.ProxyPixivisionSlice(artworks.Pixivision, *image_proxy)
|
||||
|
||||
return c.Render("pages/index", fiber.Map{"Title": "Landing", "Artworks": artworks, "Token": had_token})
|
||||
}
|
||||
|
||||
func user_page(c *fiber.Ctx) error {
|
||||
image_proxy := get_session_value(c, "image-proxy")
|
||||
if image_proxy == nil {
|
||||
image_proxy = &configs.ProxyServer
|
||||
}
|
||||
|
||||
id := c.Params("id")
|
||||
if _, err := strconv.Atoi(id); err != nil {
|
||||
return err
|
||||
}
|
||||
category := c.Params("category", "artworks")
|
||||
if !(category == "artworks" || category == "illustrations" || category == "manga" || category == "bookmarks") {
|
||||
return errors.New("Invalid work category: only illustrations, manga, artworks and bookmarks are available")
|
||||
}
|
||||
|
||||
page := c.Query("page", "1")
|
||||
pageInt, _ := strconv.Atoi(page)
|
||||
|
||||
user, err := PC.GetUserInformation(id, category, pageInt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.ProxyImages(*image_proxy)
|
||||
|
||||
var worksCount int
|
||||
|
||||
worksCount = user.ArtworksCount
|
||||
pageLimit := math.Ceil(float64(worksCount) / 30.0)
|
||||
|
||||
return c.Render("pages/user", fiber.Map{"Title": user.Name, "User": user, "Category": category, "PageLimit": int(pageLimit), "Page": pageInt})
|
||||
}
|
||||
|
||||
func ranking_page(c *fiber.Ctx) error {
|
||||
image_proxy := get_session_value(c, "image-proxy")
|
||||
if image_proxy == nil {
|
||||
image_proxy = &configs.ProxyServer
|
||||
}
|
||||
|
||||
queries := make(map[string]string, 4)
|
||||
queries["Mode"] = c.Query("mode", "daily")
|
||||
queries["Content"] = c.Query("content", "all")
|
||||
queries["Date"] = c.Query("date", "")
|
||||
|
||||
page := c.Query("page", "1")
|
||||
pageInt, _ := strconv.Atoi(page)
|
||||
|
||||
response, err := PC.GetRanking(queries["Mode"], queries["Content"], queries["Date"], page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response.ProxyImages(*image_proxy)
|
||||
|
||||
return c.Render("pages/rank", fiber.Map{
|
||||
"Title": "Ranking",
|
||||
"Data": response,
|
||||
"Queries": queries,
|
||||
"Page": pageInt,
|
||||
})
|
||||
}
|
||||
|
||||
func newest_artworks_page(c *fiber.Ctx) error {
|
||||
image_proxy := get_session_value(c, "image-proxy")
|
||||
if image_proxy == nil {
|
||||
image_proxy = &configs.ProxyServer
|
||||
}
|
||||
|
||||
worktype := c.Query("type", "illust")
|
||||
|
||||
r18 := c.Query("r18", "false")
|
||||
|
||||
works, err := PC.GetNewestArtworks(worktype, r18)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
works = models.ProxyShortArtworkSlice(works, *image_proxy)
|
||||
|
||||
return c.Render("pages/newest", fiber.Map{
|
||||
"Items": works,
|
||||
"Title": "Newest works",
|
||||
})
|
||||
}
|
||||
|
||||
func search_page(c *fiber.Ctx) error {
|
||||
image_proxy := get_session_value(c, "image-proxy")
|
||||
if image_proxy == nil {
|
||||
image_proxy = &configs.ProxyServer
|
||||
}
|
||||
|
||||
queries := make(map[string]string, 3)
|
||||
queries["Mode"] = c.Query("mode", "safe")
|
||||
queries["Category"] = c.Query("category", "artworks")
|
||||
queries["Order"] = c.Query("order", "date_d")
|
||||
|
||||
name := c.Params("name")
|
||||
|
||||
page := c.Query("page", "1")
|
||||
pageInt, _ := strconv.Atoi(page)
|
||||
|
||||
tag, err := PC.GetTagData(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tag.Metadata.Image) > 0 {
|
||||
tag.Metadata.Image = models.ProxyImage(tag.Metadata.Image, *image_proxy)
|
||||
}
|
||||
result, err := PC.GetSearch(queries["Category"], name, queries["Order"], queries["Mode"], page)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.ProxyImages(*image_proxy)
|
||||
|
||||
return c.Render("pages/tag", fiber.Map{"Title": "Results for " + tag.Name, "Tag": tag, "Data": result, "Queries": queries, "Page": pageInt})
|
||||
}
|
||||
|
||||
func search(c *fiber.Ctx) error {
|
||||
name := c.FormValue("name")
|
||||
|
||||
return c.Redirect("/tags/"+name, http.StatusFound)
|
||||
}
|
||||
|
||||
func discovery_page(c *fiber.Ctx) error {
|
||||
image_proxy := get_session_value(c, "image-proxy")
|
||||
if image_proxy == nil {
|
||||
image_proxy = &configs.ProxyServer
|
||||
}
|
||||
|
||||
mode := c.Query("mode", "safe")
|
||||
|
||||
artworks, err := PC.GetDiscoveryArtwork(mode, 100)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
artworks = models.ProxyShortArtworkSlice(artworks, *image_proxy)
|
||||
|
||||
return c.Render("pages/discovery", fiber.Map{"Title": "Discovery", "Artworks": artworks})
|
||||
}
|
||||
|
||||
func ranking_log_page(c *fiber.Ctx) error {
|
||||
image_proxy := get_session_value(c, "image-proxy")
|
||||
if image_proxy == nil {
|
||||
image_proxy = &configs.ProxyServer
|
||||
}
|
||||
|
||||
mode := c.Query("mode", "daily")
|
||||
date := c.Query("date", "")
|
||||
|
||||
var year int
|
||||
var month int
|
||||
var monthLit string
|
||||
|
||||
// If the user supplied a date
|
||||
if len(date) == 6 {
|
||||
var err error
|
||||
year, err = strconv.Atoi(date[:4])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
month, err = strconv.Atoi(date[4:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
now := time.Now()
|
||||
year = now.Year()
|
||||
month = int(now.Month())
|
||||
}
|
||||
|
||||
realDate := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
|
||||
monthLit = realDate.Month().String()
|
||||
|
||||
monthBefore := realDate.AddDate(0, -1, 0)
|
||||
monthAfter := realDate.AddDate(0, 1, 0)
|
||||
thisMonthLink := fmt.Sprintf("%d%02d", realDate.Year(), realDate.Month())
|
||||
monthBeforeLink := fmt.Sprintf("%d%02d", monthBefore.Year(), monthBefore.Month())
|
||||
monthAfterLink := fmt.Sprintf("%d%02d", monthAfter.Year(), monthAfter.Month())
|
||||
|
||||
render, err := PC.GetRankingLog(mode, year, month, *image_proxy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render("pages/ranking_log", fiber.Map{"Title": "Ranking calendar", "Render": render, "Mode": mode, "Month": monthLit, "Year": year, "MonthBefore": monthBeforeLink, "MonthAfter": monthAfterLink, "ThisMonth": thisMonthLink})
|
||||
}
|
||||
|
||||
func following_works_page(c *fiber.Ctx) error {
|
||||
image_proxy := get_session_value(c, "image-proxy")
|
||||
if image_proxy == nil {
|
||||
image_proxy = &configs.ProxyServer
|
||||
}
|
||||
token := get_session_value(c, "token")
|
||||
if token == nil {
|
||||
return c.Redirect("/login")
|
||||
}
|
||||
queries := make(map[string]string, 2)
|
||||
queries["Mode"] = c.Query("mode", "all")
|
||||
queries["Page"] = c.Query("page", "1")
|
||||
pageInt, _ := strconv.Atoi(queries["Page"])
|
||||
|
||||
artworks, err := PC.GetNewestFromFollowing(queries["Mode"], queries["Page"], *token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
artworks = models.ProxyShortArtworkSlice(artworks, *image_proxy)
|
||||
|
||||
return c.Render("pages/following", fiber.Map{"Title": "Following works", "Queries": queries, "Artworks": artworks, "Page": pageInt})
|
||||
}
|
||||
|
||||
func your_bookmark_page(c *fiber.Ctx) error {
|
||||
token := get_session_value(c, "token")
|
||||
if token == nil {
|
||||
return c.Redirect("/login")
|
||||
}
|
||||
|
||||
// The left part of the token is the member ID
|
||||
userId := strings.Split(*token, "_")
|
||||
|
||||
c.Redirect("/users/" + userId[0] + "/bookmarks#checkpoint")
|
||||
return nil
|
||||
}
|
||||
|
||||
func login_page(c *fiber.Ctx) error {
|
||||
return c.Render("pages/login", fiber.Map{})
|
||||
}
|
||||
|
||||
func settings_page(c *fiber.Ctx) error {
|
||||
return c.Render("pages/settings", fiber.Map{})
|
||||
}
|
||||
|
||||
func settings_post(c *fiber.Ctx) error {
|
||||
t := c.Params("type")
|
||||
error := ""
|
||||
|
||||
switch t {
|
||||
case "image_server":
|
||||
error = set_image_server(c)
|
||||
case "token":
|
||||
error = set_token(c)
|
||||
case "logout":
|
||||
error = set_logout(c)
|
||||
default:
|
||||
error = "No method available"
|
||||
}
|
||||
|
||||
if error != "" {
|
||||
return errors.New(error)
|
||||
}
|
||||
c.Redirect("/settings")
|
||||
return nil
|
||||
}
|
||||
|
||||
func get_logged_in_user(c *fiber.Ctx) error {
|
||||
token := get_session_value(c, "token")
|
||||
if token == nil {
|
||||
return c.Redirect("/login")
|
||||
}
|
||||
|
||||
// The left part of the token is the member ID
|
||||
userId := strings.Split(*token, "_")
|
||||
|
||||
c.Redirect("/users/" + userId[0])
|
||||
return nil
|
||||
}
|
||||
|
||||
func about_page(c *fiber.Ctx) error {
|
||||
info := fiber.Map{
|
||||
"Time": configs.StartingTime,
|
||||
"BaseURL": configs.BaseURL,
|
||||
"Version": configs.Version,
|
||||
"ImageProxy": configs.ProxyServer,
|
||||
"AcceptLanguage": configs.AcceptLanguage,
|
||||
}
|
||||
return c.Render("pages/about", info)
|
||||
}
|
|
@ -9,12 +9,6 @@
|
|||
<b>Started PixivFE (UTC)</b>: {{ Time }} <br />The date when PixivFE
|
||||
started on this server
|
||||
</li>
|
||||
<li>
|
||||
<b>Base URL</b>: {{ if isset(BaseURL) and !BaseURL == "localhost" }} {{
|
||||
BaseURL }} <br />
|
||||
Meta tags are available for embeds {{ else }} Not set <br />
|
||||
Meta tags are not available, embeds may not work {{ end }}
|
||||
</li>
|
||||
<li>
|
||||
<b>Default image proxy server</b>: {{ ImageProxy }}<br />The default image
|
||||
proxy server that was set on this server, used to proxy images from
|
3
views/pages/artwork-multi.jet.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
{{ range Artworks }}
|
||||
{{ include "components/artwork" . }}
|
||||
{{ end }}
|
|
@ -1,4 +1,4 @@
|
|||
<div class="container illust">
|
||||
<div class="container illust" id="checkpoint">
|
||||
{{ if !Illust.IsUgoira }}
|
||||
<div class="illust-images">
|
||||
{{ range index := Illust.Images }}
|
||||
|
@ -9,20 +9,23 @@
|
|||
</div>
|
||||
{{ else }}
|
||||
<div class="illust-images">
|
||||
<a href="https://ugoira.com/i/{{ Illust.ID }}" target="_blank">
|
||||
<video
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
disablepictureinpicture
|
||||
playsinline
|
||||
controls
|
||||
poster="{{ Illust.Images[0].Large }}"
|
||||
src="https://ugoira.com/api/mp4/{{ Illust.ID }}"
|
||||
src="/proxy/ugoira.com/{{ Illust.ID }}"
|
||||
>
|
||||
Unable to load ugoira.
|
||||
</video>
|
||||
</a>
|
||||
</div>
|
||||
<a href="/proxy/ugoira.com/{{ Illust.ID }}"
|
||||
>Download</a
|
||||
>
|
||||
<br />
|
||||
<a href="https://ugoira.com/i/{{ Illust.ID }}"
|
||||
>Go to ugoira.com for more options</a
|
||||
>
|
||||
|
@ -51,6 +54,20 @@
|
|||
<br />
|
||||
</div>
|
||||
<div class="illust-tags">
|
||||
<!--
|
||||
To know if this artwork is bookmarked:
|
||||
```
|
||||
{{ if Illust.Bookmarked }}
|
||||
<button>Unbookmark</button>
|
||||
//...
|
||||
{{ else }}
|
||||
<button>Bookmarked</button>
|
||||
//...
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
Same with Illust.Liked
|
||||
-->
|
||||
<span class="illust-tag-attr">
|
||||
<img src="/assets/eye.png" alt="Views" />
|
||||
{{ Illust.Views }}
|
||||
|
@ -92,11 +109,14 @@
|
|||
</div>
|
||||
<br />
|
||||
<div class="illust-other-works">
|
||||
<a href="/users/{{ Illust.User.ID }}"
|
||||
><img src="{{ Illust.User.Avatar }}" alt="{{ Illust.User.Name }}" /> Other
|
||||
works by {{ Illust.User.Name }}
|
||||
<div class="artwork-container-header">
|
||||
<a class="illust-other-works-author" href="/users/{{ Illust.User.ID }}"
|
||||
><img src="{{ Illust.User.Avatar }}" alt="{{ Illust.User.Name }}" /> Other works by {{ Illust.User.Name }}
|
||||
<span class="special-symbol">»</span>
|
||||
</a>
|
||||
{{ combinedUrl := "/artworks-multi/" + joinArtworkIds(Illust.RecentWorks) }}
|
||||
<div class="artwork-actions"><a href="{{combinedUrl}}">View all</a></div>
|
||||
</div>
|
||||
<div class="artwork-container-scroll">
|
||||
{{ range Illust.RecentWorks }}
|
||||
<div class="artwork-small artwork">
|
||||
|
@ -120,8 +140,8 @@
|
|||
{{ if .Stamp }}
|
||||
<img
|
||||
class="stamp"
|
||||
src="https://s.pximg.net/common/images/stamp/generated-stamps/{{ .Stamp }}_s.jpg"
|
||||
alt="https://s.pximg.net/common/images/stamp/generated-stamps/{{ .Stamp }}_s.jpg"
|
||||
src="/proxy/s.pximg.net/common/images/stamp/generated-stamps/{{ .Stamp }}_s.jpg"
|
||||
alt="/proxy/s.pximg.net/common/images/stamp/generated-stamps/{{ .Stamp }}_s.jpg"
|
||||
/>
|
||||
{{ else }} {{ raw: parseEmojis(.Context) }} {{ end }}
|
||||
</p>
|
111
views/pages/components/artwork.jet.html
Normal file
|
@ -0,0 +1,111 @@
|
|||
<div class="container illust">
|
||||
{{ if !.Illust.IsUgoira }}
|
||||
<div class="illust-images">
|
||||
{{ range index := .Illust.Images }}
|
||||
<a href="{{ .Original }}" target="_blank">
|
||||
<img src="{{ .Large }}" alt="Page {{ index }}" />
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ else }}
|
||||
<div class="illust-images">
|
||||
<video
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
disablepictureinpicture
|
||||
playsinline
|
||||
controls
|
||||
poster="{{ .Illust.Images[0].Large }}"
|
||||
src="/proxy/ugoira.com/{{ .Illust.ID }}"
|
||||
>
|
||||
Unable to load ugoira.
|
||||
</video>
|
||||
</div>
|
||||
<a href="/proxy/ugoira.com/{{ .Illust.ID }}"
|
||||
>Download</a
|
||||
>
|
||||
<br />
|
||||
<a href="https://ugoira.com/i/{{ .Illust.ID }}"
|
||||
>Go to ugoira.com for more options</a
|
||||
>
|
||||
{{ end }}
|
||||
|
||||
<div class="illust-attr">
|
||||
<a href="/users/{{ .Illust.User.ID }}"
|
||||
><img
|
||||
src="{{ .Illust.User.Avatar }}"
|
||||
alt="{{ .Illust.User.Name }}"
|
||||
class="illust-avatar"
|
||||
/>
|
||||
</a>
|
||||
<div class="attr-wrap">
|
||||
<div class="illust-title">{{ .Illust.Title }}</div>
|
||||
<div class="illust-author">
|
||||
<a href="/users/{{ .Illust.User.ID }}">{{ .Illust.User.Name }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="https://pixiv.net/i/{{ .Illust.ID }}"
|
||||
>pixiv.net/i/{{ .Illust.ID }}</a
|
||||
>
|
||||
<br />
|
||||
</div>
|
||||
<div class="illust-tags">
|
||||
<!--
|
||||
To know if this artwork is bookmarked:
|
||||
```
|
||||
{{ if .Illust.Bookmarked }}
|
||||
<button>Unbookmark</button>
|
||||
//...
|
||||
{{ else }}
|
||||
<button>Bookmarked</button>
|
||||
//...
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
Same with .Illust.Liked
|
||||
-->
|
||||
<span class="illust-tag-attr">
|
||||
<img src="/assets/eye.png" alt="Views" />
|
||||
{{ .Illust.Views }}
|
||||
</span>
|
||||
<span class="illust-tag-attr">
|
||||
<img src="/assets/heart-solid.png" alt="Bookmarks" />
|
||||
{{ .Illust.Bookmarks }}
|
||||
</span>
|
||||
<span class="illust-tag-attr">
|
||||
<img src="/assets/like.png" alt="Likes" />
|
||||
{{ .Illust.Likes }}
|
||||
</span>
|
||||
<span class="illust-tag-attr">
|
||||
<img src="/assets/calendar.png" alt="Date" />
|
||||
{{ parseTime: .Illust.Date }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="illust-tags">
|
||||
{{ if .Illust.AiType == 2 }}
|
||||
<span class="illust-tag">
|
||||
<span class="illust-tag-name" id="highlight">AI-generated</span>
|
||||
</span>
|
||||
{{ end }} {{ range .Illust.Tags }} {{ if isEmphasize(.Name) }}
|
||||
<span class="illust-tag">
|
||||
<span class="illust-tag-name" id="highlight">{{ .Name }}</span>
|
||||
</span>
|
||||
{{ else }}
|
||||
<span class="illust-tag">
|
||||
<span class="illust-tag-name"
|
||||
><a href="/tags/{{ escapeString(.Name) }}">#{{ .Name }}</a></span
|
||||
><span class="illust-tag-translation">{{ .TranslatedName }}</span>
|
||||
</span>
|
||||
{{ end }} {{ end }}
|
||||
</div>
|
||||
<br />
|
||||
<div class="illust-description">
|
||||
{{ raw: parsePixivRedirect(.Illust.Description) }}
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
23
views/pages/components/ranking-tn.jet.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{{ range . }}
|
||||
<div class="artwork-small artwork">
|
||||
<div class="artwork-additional">
|
||||
<div class="artwork-position">{{ .Rank }}</div>
|
||||
{{ if toInt(.Pages) > 1 }}
|
||||
<div class="artwork-page-count"><span>⧉ {{ .Pages }}</span></div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<a href="/artworks/{{ .ID }}#checkpoint">
|
||||
<img src="{{ .Image }}" alt="{{ .Title }}" />
|
||||
</a>
|
||||
<div class="artwork-title">
|
||||
<a href="/artworks/{{ .ID }}#checkpoint"> {{ .Title }} </a>
|
||||
</div>
|
||||
<div class="artwork-author">
|
||||
<a href="/users/{{ .ArtistID }}"
|
||||
><img src="{{ .ArtistAvatar }}" alt="{{ .ArtistName }}" />
|
||||
<span>{{ .ArtistName }}</span></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|