debug wip

This commit is contained in:
perennial
2024-10-13 18:34:38 +11:00
parent d2735f5684
commit 0c5a2d7360
10 changed files with 158 additions and 61 deletions
+12
View File
@@ -14139,6 +14139,18 @@ h6, .h6 {
display: inline;
}
.follow-spinner {
display: none;
}
.htmx-request .follow-spinner {
display: inline;
}
.htmx-request.follow-spinner {
display: inline;
}
@media (scripting: none) {
.js-required {
display: none !important;
+11
View File
@@ -317,6 +317,17 @@ h6 {
display: inline;
}
.follow-spinner {
display: none;
}
.htmx-request .follow-spinner {
display: inline;
}
.htmx-request.follow-spinner {
display: inline;
}
/// Hide elements that require JS when scripting is disabled
/// NOTE: This only seems to work when JS is actually disabled browser-wide, not via an extension like uBO
/// See the callout on https://developer.mozilla.org/en-US/docs/Web/CSS/@media/scripting
+35 -1
View File
@@ -27,7 +27,41 @@
</div>
<!-- User name and social media links -->
<div class="col-12 col-md-9">
<h1>{{ .User.Name }}</h1>
<!-- Follow and unfollow buttons -->
<div class="d-flex justify-content-start justify-content-xl-between align-items-center">
<h1>{{ .User.Name }}</h1>
{{- if .User.IsFollowed == true && LoggedIn }}
<button type="button" class="custom-btn-secondary text-nowrap me-2" hx-post="/self/unfollowUser/{{ .User.ID }}" hx-target="body" hx-indicator="#follow-spinner" hx-push-url="true">
<div class="d-flex">
<div><i class="bi bi-person-plus-fill me-2"></i>Following</div>
<div class="follow-spinner ms-2" id="follow-spinner">
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</button>
{{- else }}
<button type="button" class="custom-btn-secondary text-nowrap me-2" hx-post="/self/followUser/{{ .User.ID }}" hx-target="body" hx-indicator="#follow-spinner" hx-push-url="true">
<div class="d-flex">
<div><i class="bi bi-person-plus me-2"></i>Follow</div>
<div class="follow-spinner ms-2" id="follow-spinner">
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</button>
{{- end }}
</div>
<p class="text-muted">{{ .User.Following }} Following | {{ .User.MyPixiv }} MyPixiv</p>
<div class="mb-3">
{{- if .User.Webpage }}
+55 -51
View File
@@ -35,6 +35,45 @@ func init() {
retryClient.Logger = nil // Disables the default logger in go-retryablehttp
}
// Helper function to handle common request logic
func makeRequest(ctx context.Context, reqFunc func(context.Context, string) (*retryablehttp.Request, error), token *token_manager.Token, url string) (*SimpleHTTPResponse, error) {
req, err := reqFunc(ctx, token.Value)
if err != nil {
return nil, err
}
start := time.Now()
resp, err := retryClient.Do(req)
end := time.Now()
if err != nil {
return nil, i18n.Errorf("failed to make request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
audit.LogAPIRoundTrip(audit.APIRequestSpan{
StartTime: start,
EndTime: end,
RequestId: request_context.GetFromContext(ctx).RequestId,
Response: resp,
Error: err,
Method: req.Method,
Url: url,
Token: token.Value,
Body: string(body),
})
return &SimpleHTTPResponse{
StatusCode: resp.StatusCode,
Body: string(body),
}, nil
}
// retryRequest performs a request with automatic retries and token management
func retryRequest(
ctx context.Context,
@@ -46,15 +85,22 @@ func retryRequest(
var lastErr error
tokenManager := config.GlobalConfig.TokenManager
if isPost {
// For POST requests, perform the request once without retrying
token := &token_manager.Token{Value: userToken}
return makeRequest(ctx, reqFunc, token, url)
}
// For GET requests, use the retry logic
for i := 0; i < config.GlobalConfig.APIMaxRetries; i++ {
var token *token_manager.Token
if userToken != "" {
token = &token_manager.Token{Value: userToken}
} else if !isPost {
} else {
token = tokenManager.GetToken()
}
if token == nil && !isPost {
if token == nil {
tokenManager.ResetAllTokens()
return nil, i18n.Errorf(
`All tokens (%d) are timed out, resetting all tokens to their initial good state.
@@ -65,62 +111,20 @@ Please refer the following documentation for additional information:
len(config.GlobalConfig.Token))
}
tokenValue := ""
if token != nil {
tokenValue = token.Value
}
req, err := reqFunc(ctx, tokenValue)
resp, err := makeRequest(ctx, reqFunc, token, url)
if err != nil {
return nil, err
}
start := time.Now()
resp, err := retryClient.Do(req)
end := time.Now()
// Unwrap the body here so that we could log stuff correctly
if err != nil {
return nil, i18n.Errorf("failed to make request: %w", err)
}
// Validate response isn't nil
if resp == nil {
lastErr = i18n.Errorf("Received nil response after request: %w", err)
lastErr = err
tokenManager.MarkTokenStatus(token, token_manager.TimedOut)
continue
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil || resp.Body == nil {
return nil, err
}
audit.LogAPIRoundTrip(audit.APIRequestSpan{
StartTime: start,
EndTime: end,
RequestId: request_context.GetFromContext(ctx).RequestId,
Response: resp,
Error: err,
Method: req.Method,
Url: url,
Token: tokenValue,
Body: string(body),
})
if resp.StatusCode == http.StatusOK {
if userToken == "" && !isPost {
tokenManager.MarkTokenStatus(token, token_manager.Good)
}
return &SimpleHTTPResponse{
StatusCode: resp.StatusCode,
Body: string(body),
}, nil
tokenManager.MarkTokenStatus(token, token_manager.Good)
return resp, nil
}
lastErr = i18n.Errorf("HTTP status code: %d", resp.StatusCode)
if userToken == "" && !isPost {
tokenManager.MarkTokenStatus(token, token_manager.TimedOut)
}
tokenManager.MarkTokenStatus(token, token_manager.TimedOut)
select {
case <-ctx.Done():
@@ -130,7 +134,7 @@ Please refer the following documentation for additional information:
}
}
return nil, i18n.Errorf("Max retries reached. Last error: %v", lastErr)
return nil, i18n.Errorf("Max retries reached for GET request. Last error: %v", lastErr)
}
// API_GET performs a GET request to the Pixiv API with automatic retries
+2
View File
@@ -70,6 +70,8 @@ type User struct {
NovelSeries []NovelSeries
MangaSeries []MangaSeries
IsFollowed bool `json:"isFollowed"` // Denotes whether the logged in user currently following the given user
// The following fields are internal to PixivFE, used to display the number of works for a given category
CountInfo CountInfo
}
+1 -2
View File
@@ -19,15 +19,14 @@
"core/ranking.go:Xy1LpykXW4E": "Retrying in %v",
"core/ranking.go:_YcW1wsLOAg": "JSON unmarshalling successful on attempt %d",
"core/ranking.go:sk-7WI2RS-Q": "Attempt %d of %d",
"core/requests.go:0Q07bJLKfvU": "Received nil response after request: %w",
"core/requests.go:0hOvqlK-HwY": "HTTP status code: %d",
"core/requests.go:7heLIvyBB2M": "Invalid JSON: %v",
"core/requests.go:7pvvVOIzWew": "Max retries reached for GET request. Last error: %v",
"core/requests.go:CF6mVT22R2w": "Incompatible request body",
"core/requests.go:Odhyy2SoIGU": "failed to make request: %w",
"core/requests.go:XGjPq4l3s2o": "userToken is required for POST requests",
"core/requests.go:XdMN7Q7DY3k": "failed to proxy request: %w",
"core/requests.go:lLy9SHFUtQQ": "failed to copy response body: %w",
"core/requests.go:o7gE7trSD7c": "Max retries reached. Last error: %v",
"core/requests.go:y3v5nQwdU0s": "All tokens (%d) are timed out, resetting all tokens to their initial good state.\nConsider providing additional tokens in PIXIVFE_TOKEN or reviewing API request level backoff configuration.\nPlease refer the following documentation for additional information:\n- https://pixivfe-docs.pages.dev/hosting/obtaining-pixivfe-token/\n- https://pixivfe-docs.pages.dev/hosting/environment-variables/#exponential-backoff-configuration",
"core/user.go:1itpJBE-3VI": "Invalid page number.",
"core/user.go:T3RwaHcnsYQ": "Invalid work category: %#v. Only \"%s\", \"%s\", \"%s\", \"%s\", \"%s\" and \"%s\" are available",
+3
View File
@@ -329,10 +329,13 @@
"assets/views/tag.jet.html:yHft19aQloM": "Minimum image width",
"assets/views/unauthorized.jet.html:Q84lx6mjuV0": "Unauthorized",
"assets/views/unauthorized.jet.html:SNRVjaimXDQ": "You need to <a href=\"/settings#login\">log in</a> to use this feature.",
"assets/views/user.jet.html:3MldbPX4xm8": "Loading...",
"assets/views/user.jet.html:3_oq3Nw2b2U": "View on pixiv.net",
"assets/views/user.jet.html:5xhgcv5yNyM": "Frequently used tags",
"assets/views/user.jet.html:7i2xxiolkqI": "Follow",
"assets/views/user.jet.html:8ga3WlgRnzQ": "Illustrations and manga",
"assets/views/user.jet.html:9EbrMpLP6_U": "Bookmarks",
"assets/views/user.jet.html:KSE10VbnTzI": "Following",
"assets/views/user.jet.html:P6fO_UTqr7E": "Manga",
"assets/views/user.jet.html:b84CRs16kKI": "View all",
"assets/views/user.jet.html:clIffJhX-50": "{{ .User.Following }} Following | {{ .User.MyPixiv }} MyPixiv",
+8 -3
View File
@@ -12,7 +12,7 @@ import (
var (
optionSaveResponse bool
MaxRecordedCount = 0
logger *zap.Logger
Logger *zap.Logger
)
// Init initializes the audit package and sets up response saving if enabled.
@@ -70,7 +70,7 @@ func Init(saveResponse bool) error {
// Build and assign the logger
var err error
logger, err = zapConfig.Build()
Logger, err = zapConfig.Build()
if err != nil {
return i18n.Errorf("failed to initialize zap logger: %w", err)
}
@@ -82,7 +82,7 @@ func Init(saveResponse bool) error {
// Handle saving responses
MaxRecordedCount = 128
if err := os.MkdirAll(savePath, 0o700); err != nil {
logger.Error("Failed to create response save directory",
Logger.Error("Failed to create response save directory",
zap.Error(err),
zap.String("path", savePath),
)
@@ -91,3 +91,8 @@ func Init(saveResponse bool) error {
return nil
}
// GetLogger returns the initialized zap logger
func GetLogger() *zap.Logger {
return Logger
}
+4 -4
View File
@@ -20,7 +20,7 @@ var RecordedRequestSpans = []Span{}
func LogAndRecord(requestSpan Span) {
duration := float64(Duration(requestSpan)) / float64(time.Second)
logger.Info("Request",
Logger.Info("Request",
// Zap already prefixes the log with a timestamp
// zap.String("timestamp", span.GetStartTime().Format(time.RFC3339)),
zap.String("component", requestSpan.Component()),
@@ -44,7 +44,7 @@ func LogAndRecord(requestSpan Span) {
// It also logs any internal server errors that occurred during the request.
func LogServerRoundTrip(requestSpan ServerRequestSpan) {
if requestSpan.Error != nil {
logger.Error("Internal Server Error",
Logger.Error("Internal Server Error",
zap.Error(requestSpan.Error),
zap.String("requestId", requestSpan.RequestId),
)
@@ -62,7 +62,7 @@ func LogAPIRoundTrip(requestSpan APIRequestSpan) {
var err error
requestSpan.ResponseFilename, err = writeResponseBodyToFile(requestSpan.Body)
if err != nil {
logger.Error("Failed to save response to file",
Logger.Error("Failed to save response to file",
zap.Error(err),
zap.String("requestId", requestSpan.RequestId),
)
@@ -70,7 +70,7 @@ func LogAPIRoundTrip(requestSpan APIRequestSpan) {
}
// Log a warning for non-2xx status codes
if !(300 > requestSpan.Response.StatusCode && requestSpan.Response.StatusCode >= 200) {
logger.Warn("Non-2xx response from pixiv",
Logger.Warn("Non-2xx response from pixiv",
zap.Int("status", requestSpan.Response.StatusCode),
zap.String("requestId", requestSpan.RequestId),
)
+27
View File
@@ -9,6 +9,8 @@ import (
"codeberg.org/vnpower/pixivfe/v2/i18n"
"codeberg.org/vnpower/pixivfe/v2/server/session"
"codeberg.org/vnpower/pixivfe/v2/server/utils"
"codeberg.org/vnpower/pixivfe/v2/server/audit"
"go.uber.org/zap"
)
// NOTE: is the csrf protection by the upstream Pixiv API itself good enough, or do we need to implement our own?
@@ -101,7 +103,12 @@ func LikeRoute(w http.ResponseWriter, r *http.Request) error {
}
func FollowUserRoute(w http.ResponseWriter, r *http.Request) error {
logger := audit.GetLogger()
logger.Debug("FollowUserRoute called")
if r.Method != http.MethodPost {
logger.Debug("Method not allowed", zap.String("method", r.Method))
return i18n.Error("Method not allowed")
}
@@ -109,19 +116,23 @@ func FollowUserRoute(w http.ResponseWriter, r *http.Request) error {
csrf := session.GetCookie(r, session.Cookie_CSRF)
if token == "" || csrf == "" {
logger.Debug("User not logged in or missing CSRF")
return PromptUserToLoginPage(w, r)
}
id := GetPathVar(r, "id")
if id == "" {
logger.Debug("No user ID provided")
return i18n.Error("No user ID provided.")
}
logger.Debug("Following user", zap.String("user_id", id))
isPrivate := r.FormValue("private") == "true"
restrict := "0"
if isPrivate {
restrict = "1"
}
logger.Debug("Follow privacy setting", zap.Bool("isPrivate", isPrivate))
URL := "https://www.pixiv.net/bookmark_add.php"
payload := url.Values{
@@ -133,16 +144,25 @@ func FollowUserRoute(w http.ResponseWriter, r *http.Request) error {
"format": {"json"},
}.Encode()
logger.Debug("Making API call to follow user", zap.String("URL", URL))
if err := core.API_POST(r.Context(), URL, payload, token, csrf, false); err != nil {
logger.Debug("API call failed", zap.Error(err))
return err
}
logger.Debug("API call successful")
logger.Debug("Redirecting user")
utils.RedirectToWhenceYouCame(w, r)
return nil
}
func UnfollowUserRoute(w http.ResponseWriter, r *http.Request) error {
logger := audit.GetLogger()
logger.Debug("UnfollowUserRoute called")
if r.Method != http.MethodPost {
logger.Debug("Method not allowed", zap.String("method", r.Method))
return i18n.Error("Method not allowed")
}
@@ -150,13 +170,16 @@ func UnfollowUserRoute(w http.ResponseWriter, r *http.Request) error {
csrf := session.GetCookie(r, session.Cookie_CSRF)
if token == "" || csrf == "" {
logger.Debug("User not logged in or missing CSRF")
return PromptUserToLoginPage(w, r)
}
id := GetPathVar(r, "id")
if id == "" {
logger.Debug("No user ID provided")
return i18n.Error("No user ID provided.")
}
logger.Debug("Unfollowing user", zap.String("user_id", id))
URL := "https://www.pixiv.net/rpc_group_setting.php"
payload := url.Values{
@@ -165,10 +188,14 @@ func UnfollowUserRoute(w http.ResponseWriter, r *http.Request) error {
"id": {id},
}.Encode()
logger.Debug("Making API call to unfollow user", zap.String("URL", URL))
if err := core.API_POST(r.Context(), URL, payload, token, csrf, false); err != nil {
logger.Debug("API call failed", zap.Error(err))
return err
}
logger.Debug("API call successful")
logger.Debug("Redirecting user")
utils.RedirectToWhenceYouCame(w, r)
return nil
}