160 lines
4.0 KiB
Go
160 lines
4.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"seanime/internal/api/mal"
|
|
"seanime/internal/constants"
|
|
"seanime/internal/database/models"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/goccy/go-json"
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
type MalAuthResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
ExpiresIn int32 `json:"expires_in"`
|
|
TokenType string `json:"token_type"`
|
|
}
|
|
|
|
// HandleMALAuth
|
|
//
|
|
// @summary fetches the access and refresh tokens for the given code.
|
|
// @desc This is used to authenticate the user with MyAnimeList.
|
|
// @desc It will save the info in the database, effectively logging the user in.
|
|
// @desc The client should re-fetch the server status after this.
|
|
// @route /api/v1/mal/auth [POST]
|
|
// @returns handlers.MalAuthResponse
|
|
func (h *Handler) HandleMALAuth(c echo.Context) error {
|
|
|
|
type body struct {
|
|
Code string `json:"code"`
|
|
State string `json:"state"`
|
|
CodeVerifier string `json:"code_verifier"`
|
|
}
|
|
|
|
b := new(body)
|
|
if err := c.Bind(b); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
client := &http.Client{}
|
|
|
|
// Build URL
|
|
urlData := url.Values{}
|
|
urlData.Set("client_id", constants.MalClientId)
|
|
urlData.Set("grant_type", "authorization_code")
|
|
urlData.Set("code", b.Code)
|
|
urlData.Set("code_verifier", b.CodeVerifier)
|
|
encodedData := urlData.Encode()
|
|
|
|
req, err := http.NewRequest("POST", "https://myanimelist.net/v1/oauth2/token", strings.NewReader(encodedData))
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
req.Header.Add("Content-Length", strconv.Itoa(len(urlData.Encode())))
|
|
|
|
// Response
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
ret := MalAuthResponse{}
|
|
if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Save
|
|
malInfo := models.Mal{
|
|
BaseModel: models.BaseModel{
|
|
ID: 1,
|
|
UpdatedAt: time.Now(),
|
|
},
|
|
Username: "",
|
|
AccessToken: ret.AccessToken,
|
|
RefreshToken: ret.RefreshToken,
|
|
TokenExpiresAt: time.Now().Add(time.Duration(ret.ExpiresIn) * time.Second),
|
|
}
|
|
|
|
_, err = h.App.Database.UpsertMalInfo(&malInfo)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
return h.RespondWithData(c, ret)
|
|
}
|
|
|
|
// HandleEditMALListEntryProgress
|
|
//
|
|
// @summary updates the progress of a MAL list entry.
|
|
// @route /api/v1/mal/list-entry/progress [POST]
|
|
// @returns bool
|
|
func (h *Handler) HandleEditMALListEntryProgress(c echo.Context) error {
|
|
|
|
type body struct {
|
|
MediaId *int `json:"mediaId"`
|
|
Progress *int `json:"progress"`
|
|
}
|
|
|
|
b := new(body)
|
|
if err := c.Bind(b); err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
if b.MediaId == nil || b.Progress == nil {
|
|
return h.RespondWithError(c, errors.New("mediaId and progress is required"))
|
|
}
|
|
|
|
// Get MAL info
|
|
_malInfo, err := h.App.Database.GetMalInfo()
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Verify MAL auth
|
|
malInfo, err := mal.VerifyMALAuth(_malInfo, h.App.Database, h.App.Logger)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
// Get MAL Wrapper
|
|
malWrapper := mal.NewWrapper(malInfo.AccessToken, h.App.Logger)
|
|
|
|
// Update MAL list entry
|
|
err = malWrapper.UpdateAnimeProgress(&mal.AnimeListProgressParams{
|
|
NumEpisodesWatched: b.Progress,
|
|
}, *b.MediaId)
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
h.App.Logger.Debug().Msgf("mal: Updated MAL list entry for mediaId %d", *b.MediaId)
|
|
|
|
return h.RespondWithData(c, true)
|
|
}
|
|
|
|
// HandleMALLogout
|
|
//
|
|
// @summary logs the user out of MyAnimeList.
|
|
// @desc This will delete the MAL info from the database, effectively logging the user out.
|
|
// @desc The client should re-fetch the server status after this.
|
|
// @route /api/v1/mal/logout [POST]
|
|
// @returns bool
|
|
func (h *Handler) HandleMALLogout(c echo.Context) error {
|
|
|
|
err := h.App.Database.DeleteMalInfo()
|
|
if err != nil {
|
|
return h.RespondWithError(c, err)
|
|
}
|
|
|
|
return h.RespondWithData(c, true)
|
|
}
|