247 lines
6.0 KiB
Go
247 lines
6.0 KiB
Go
package arn
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/animenotifier/notify.moe/arn/validate"
|
|
|
|
"github.com/aerogo/aero"
|
|
"github.com/aerogo/api"
|
|
"github.com/aerogo/http/client"
|
|
"github.com/akyoto/color"
|
|
"github.com/animenotifier/notify.moe/arn/autocorrect"
|
|
)
|
|
|
|
// Force interface implementations
|
|
var (
|
|
_ PostParent = (*User)(nil)
|
|
_ api.Editable = (*User)(nil)
|
|
)
|
|
|
|
// Authorize returns an error if the given API POST request is not authorized.
|
|
func (user *User) Authorize(ctx aero.Context, action string) error {
|
|
editor := GetUserFromContext(ctx)
|
|
|
|
if editor == nil {
|
|
return errors.New("Not authorized")
|
|
}
|
|
|
|
if editor.ID != ctx.Get("id") && editor.Role != "admin" {
|
|
return errors.New("Can not modify data from other users")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Edit updates the user object.
|
|
func (user *User) Edit(ctx aero.Context, key string, value reflect.Value, newValue reflect.Value) (bool, error) {
|
|
switch key {
|
|
case "Nick":
|
|
newNick := newValue.String()
|
|
err := user.SetNick(newNick)
|
|
return true, err
|
|
|
|
case "Email":
|
|
newEmail := newValue.String()
|
|
err := user.SetEmail(newEmail)
|
|
return true, err
|
|
|
|
case "Gender":
|
|
newGender := newValue.String()
|
|
|
|
if newGender != "male" && newGender != "female" {
|
|
return true, errors.New("Invalid gender")
|
|
}
|
|
|
|
user.Gender = newGender
|
|
return true, nil
|
|
|
|
case "Website":
|
|
newSite := newValue.String()
|
|
|
|
if newSite == "" {
|
|
user.Website = newSite
|
|
return true, nil
|
|
}
|
|
|
|
if autocorrect.IsTrackerLink(newSite) {
|
|
return true, errors.New("Not an actual personal website or homepage")
|
|
}
|
|
|
|
newSite = autocorrect.Website(newSite)
|
|
|
|
if !validate.URI("https://" + newSite) {
|
|
return true, errors.New("Not a valid website link")
|
|
}
|
|
|
|
response, err := client.Get("https://" + newSite).End()
|
|
|
|
if err != nil || response.StatusCode() >= 400 {
|
|
return true, fmt.Errorf("https://%s seems to be inaccessible", newSite)
|
|
}
|
|
|
|
user.Website = newSite
|
|
return true, nil
|
|
|
|
case "BirthDay":
|
|
newBirthDay := newValue.String()
|
|
|
|
if AgeInYears(newBirthDay) <= 0 {
|
|
return true, errors.New("Invalid birthday (make sure to use YYYY-MM-DD format, e.g. 2000-01-17)")
|
|
}
|
|
|
|
user.BirthDay = newBirthDay
|
|
return true, nil
|
|
|
|
case "ProExpires", "Avatar.Extension", "Avatar.LastModified":
|
|
editor := GetUserFromContext(ctx)
|
|
|
|
if editor == nil || editor.Role != "admin" {
|
|
return true, errors.New("Not authorized to edit")
|
|
}
|
|
|
|
case "Accounts.Discord.Nick":
|
|
newNick := newValue.String()
|
|
|
|
if newNick == "" {
|
|
value.SetString(newNick)
|
|
user.Accounts.Discord.Verified = false
|
|
return true, nil
|
|
}
|
|
|
|
if !validate.DiscordNick(newNick) {
|
|
return true, errors.New("Discord username must include your name and the 4-digit Discord tag (e.g. Yandere#1234)")
|
|
}
|
|
|
|
// Trim spaces
|
|
parts := strings.Split(newNick, "#")
|
|
parts[0] = strings.TrimSpace(parts[0])
|
|
parts[1] = strings.TrimSpace(parts[1])
|
|
newNick = strings.Join(parts, "#")
|
|
|
|
if value.String() != newNick {
|
|
value.SetString(newNick)
|
|
user.Accounts.Discord.Verified = false
|
|
}
|
|
|
|
return true, nil
|
|
|
|
case "Accounts.Overwatch.BattleTag":
|
|
newBattleTag := newValue.String()
|
|
value.SetString(newBattleTag)
|
|
|
|
if newBattleTag == "" {
|
|
user.Accounts.Overwatch.SkillRating = 0
|
|
user.Accounts.Overwatch.Tier = ""
|
|
} else {
|
|
// Refresh Overwatch info if the battletag changed
|
|
go func() {
|
|
err := user.RefreshOverwatchInfo()
|
|
|
|
if err != nil {
|
|
color.Red("Error refreshing Overwatch info of user '%s' with Overwatch battle tag '%s': %v", user.Nick, newBattleTag, err)
|
|
return
|
|
}
|
|
|
|
color.Green("Refreshed Overwatch info of user '%s' with Overwatch battle tag '%s': %v", user.Nick, newBattleTag, user.Accounts.Overwatch.SkillRating)
|
|
user.Save()
|
|
}()
|
|
}
|
|
|
|
return true, nil
|
|
|
|
case "Accounts.FinalFantasyXIV.Nick", "Accounts.FinalFantasyXIV.Server":
|
|
newValue := newValue.String()
|
|
value.SetString(newValue)
|
|
|
|
if newValue == "" {
|
|
user.Accounts.FinalFantasyXIV.Class = ""
|
|
user.Accounts.FinalFantasyXIV.Level = 0
|
|
user.Accounts.FinalFantasyXIV.ItemLevel = 0
|
|
} else if user.Accounts.FinalFantasyXIV.Nick != "" && user.Accounts.FinalFantasyXIV.Server != "" {
|
|
// Refresh FinalFantasyXIV info if the name or server changed
|
|
go func() {
|
|
err := user.RefreshFFXIVInfo()
|
|
|
|
if err != nil {
|
|
color.Red("Error refreshing FinalFantasy XIV info of user '%s' with nick '%s' on server '%s': %v", user.Nick, user.Accounts.FinalFantasyXIV.Nick, user.Accounts.FinalFantasyXIV.Server, err)
|
|
return
|
|
}
|
|
|
|
user.Save()
|
|
}()
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Automatically correct account nicks
|
|
if strings.HasPrefix(key, "Accounts.") && strings.HasSuffix(key, ".Nick") {
|
|
newNick := newValue.String()
|
|
newNick = autocorrect.AccountNick(newNick)
|
|
value.SetString(newNick)
|
|
|
|
// Refresh osu info if the name changed
|
|
if key == "Accounts.Osu.Nick" {
|
|
if newNick == "" {
|
|
user.Accounts.Osu.PP = 0
|
|
user.Accounts.Osu.Level = 0
|
|
user.Accounts.Osu.Accuracy = 0
|
|
} else {
|
|
go func() {
|
|
err := user.RefreshOsuInfo()
|
|
|
|
if err != nil {
|
|
color.Red("Error refreshing osu info of user '%s' with osu nick '%s': %v", user.Nick, newNick, err)
|
|
return
|
|
}
|
|
|
|
color.Green("Refreshed osu info of user '%s' with osu nick '%s': %v", user.Nick, newNick, user.Accounts.Osu.PP)
|
|
user.Save()
|
|
}()
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// Save saves the user object in the database.
|
|
func (user *User) Save() {
|
|
DB.Set("User", user.ID, user)
|
|
}
|
|
|
|
// Filter removes privacy critical fields from the user object.
|
|
func (user *User) Filter() {
|
|
user.Email = ""
|
|
user.Gender = ""
|
|
user.FirstName = ""
|
|
user.LastName = ""
|
|
user.IP = ""
|
|
user.UserAgent = ""
|
|
user.LastLogin = ""
|
|
user.Accounts.Facebook.ID = ""
|
|
user.Accounts.Google.ID = ""
|
|
user.Accounts.Twitter.ID = ""
|
|
user.BirthDay = ""
|
|
user.Location = &Location{}
|
|
user.Browser = UserBrowser{}
|
|
user.OS = UserOS{}
|
|
}
|
|
|
|
// ShouldFilter tells whether data needs to be filtered in the given context.
|
|
func (user *User) ShouldFilter(ctx aero.Context) bool {
|
|
ctxUser := GetUserFromContext(ctx)
|
|
|
|
if ctxUser != nil && ctxUser.Role == "admin" {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|