This commit is contained in:
Eccentric 2024-02-10 00:34:12 +00:00
parent 69e30bf10f
commit b0d2d6fd2c
12 changed files with 318 additions and 198 deletions

View File

@ -43,3 +43,13 @@ func ReverseString(input string) string {
} }
return str return str
} }
func ToHex(number int) string {
inta := strconv.FormatInt(int64(number), 16)
if len(inta) == 1 {
return "0" + inta
}
return inta
}

View File

@ -1,9 +1,11 @@
[accounts] [accounts]
; comma separated list of usernames that are considered administrators or server accounts ; comma separated list of usernames that are considered server accounts
; these accounts will be created upon the first run of snow ; these accounts will be created upon the first run of snow
; they will have max permissions with snow ; they will have max permissions with snow and will have no discord account linked
; to disable this, remove the line
gods=god,snow gods=god,snow
; comma separated list of usernames that are considered owner accounts
; these accounts will have max permissions with snow and can have a discord account linked
owners=ectrc
[database] [database]
; connect string ; connect string
@ -56,15 +58,15 @@ host="127.0.0.1"
secret="secret" secret="secret"
[fortnite] [fortnite]
; used for account creation + lobby ; fortnite build version
build=5.41 build=5.41
; own every cosmetic in the game. this applies to all accounts ; on every account creation, all cosmetics will be added to the account
; if you want to disable this, set this to false
everything=true everything=true
; enable or disable the requirement of password to login to an account ; enable or disable the requirement of password to login to an account
; if this is set to false, you can login to any account with just the username ; if this is set to false, you can login to any account with just the username
; if this is true you must login using an exchange code given by the bot ; if this is true you must login using an exchange code given by the bot
password=true disable_password=true
; if you recieve lots of /account/api/oauth/token requests, set this to true ; if you recieve lots of /account/api/oauth/token requests, set this to true
; this will disable the client credentials grant type ; this will disable the client credentials grant type
; however this will also disable a user to get the hotfixes before login ; however this will also disable a user to get the hotfixes before login

View File

@ -54,7 +54,7 @@ func whoHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
return return
} }
player := getPersonFromOptions(i.ApplicationCommandData(), s) player := getPersonFromOptions(i.ApplicationCommandData().Options, s)
if player == nil { if player == nil {
s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord) s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord)
return return
@ -119,7 +119,7 @@ func banHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
return return
} }
player := getPersonFromOptions(i.ApplicationCommandData(), s) player := getPersonFromOptions(i.ApplicationCommandData().Options, s)
if player == nil { if player == nil {
s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord) s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord)
return return
@ -146,7 +146,7 @@ func unbanHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
return return
} }
player := getPersonFromOptions(i.ApplicationCommandData(), s) player := getPersonFromOptions(i.ApplicationCommandData().Options, s)
if player == nil { if player == nil {
s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord) s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord)
return return
@ -168,12 +168,12 @@ func giveItemHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
return return
} }
if !looker.HasPermission(person.PermissionGiveItem) { if !looker.HasPermission(person.PermissionItemControl) {
s.InteractionRespond(i.Interaction, &ErrorNoPermission) s.InteractionRespond(i.Interaction, &ErrorNoPermission)
return return
} }
player := getPersonFromOptions(i.ApplicationCommandData(), s) player := getPersonFromOptions(i.ApplicationCommandData().Options, s)
if player == nil { if player == nil {
s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord) s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord)
return return
@ -229,12 +229,12 @@ func takeItemHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
return return
} }
if !looker.HasPermission(person.PermissionTakeItem) { if !looker.HasPermission(person.PermissionItemControl) {
s.InteractionRespond(i.Interaction, &ErrorNoPermission) s.InteractionRespond(i.Interaction, &ErrorNoPermission)
return return
} }
player := getPersonFromOptions(i.ApplicationCommandData(), s) player := getPersonFromOptions(i.ApplicationCommandData().Options, s)
if player == nil { if player == nil {
s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord) s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord)
return return
@ -301,22 +301,17 @@ func giveEverythingHandler(s *discordgo.Session, i *discordgo.InteractionCreate)
return return
} }
if !looker.HasPermission(person.PermissionGiveItem) { if !looker.HasPermission(person.PermissionItemControl) || !looker.HasPermission(person.PermissionLockerControl) {
s.InteractionRespond(i.Interaction, &ErrorNoPermission) s.InteractionRespond(i.Interaction, &ErrorNoPermission)
return return
} }
player := getPersonFromOptions(i.ApplicationCommandData(), s) player := getPersonFromOptions(i.ApplicationCommandData().Options, s)
if player == nil { if player == nil {
s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord) s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord)
return return
} }
if !player.HasPermission(person.PermissionFullLocker) {
s.InteractionRespond(i.Interaction, &ErrorNoPermission)
return
}
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseDeferredChannelMessageWithSource, Type: discordgo.InteractionResponseDeferredChannelMessageWithSource,
}) })
@ -328,3 +323,78 @@ func giveEverythingHandler(s *discordgo.Session, i *discordgo.InteractionCreate)
Content: &str, Content: &str,
}) })
} }
func permissionHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
looker := person.FindByDiscord(i.Member.User.ID)
if looker == nil {
s.InteractionRespond(i.Interaction, &ErrorNoPermission)
return
}
if !looker.HasPermission(person.PermissionPermissionControl) {
s.InteractionRespond(i.Interaction, &ErrorNoPermission)
return
}
if len(i.ApplicationCommandData().Options) <= 0 {
s.InteractionRespond(i.Interaction, &ErrorInvalidArguments)
return
}
subCommand := i.ApplicationCommandData().Options[0]
if len(subCommand.Options) <= 0 {
s.InteractionRespond(i.Interaction, &ErrorInvalidArguments)
return
}
player := getPersonFromOptions(subCommand.Options, s)
if player == nil {
s.InteractionRespond(i.Interaction, &ErrorInvalidDisplayOrDiscord)
return
}
permission := person.IntToPermission(subCommand.Options[0].IntValue())
if permission == 0 {
s.InteractionRespond(i.Interaction, &ErrorInvalidArguments)
return
}
if permission == person.PermissionAll && !looker.HasPermission(person.PermissionOwner) {
s.InteractionRespond(i.Interaction, &ErrorNoPermission)
return
}
if player.HasPermission(person.PermissionOwner) && !looker.HasPermission(person.PermissionOwner) {
s.InteractionRespond(i.Interaction, &ErrorNoPermission)
return
}
if player.HasPermission(person.PermissionAll) && !looker.HasPermission(person.PermissionOwner) {
s.InteractionRespond(i.Interaction, &ErrorNoPermission)
return
}
switch subCommand.Name {
case "add":
player.AddPermission(permission)
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: player.DisplayName + " has been given permission `" + permission.GetName() + "`.",
},
})
case "remove":
player.RemovePermission(permission)
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: player.DisplayName + " has had permission `" + permission.GetName() + "` removed.",
},
})
default:
s.InteractionRespond(i.Interaction, &ErrorInvalidArguments)
return
}
s.InteractionRespond(i.Interaction, &ErrorInvalidArguments)
}

View File

@ -89,6 +89,7 @@ func (c *DiscordClient) RegisterCommands() {
for _, command := range c.Commands { for _, command := range c.Commands {
if command.AdminOnly { if command.AdminOnly {
command.Command.DefaultMemberPermissions = &adminPermission command.Command.DefaultMemberPermissions = &adminPermission
command.Command.Description += " (admin only)"
} }
update = append(update, command.Command) update = append(update, command.Command)

View File

@ -18,6 +18,21 @@ func addCommands() {
panic("StaticClient is nil") panic("StaticClient is nil")
} }
personOptions := []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionUser,
Name: "discord",
Description: "The discord account of the player.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "display",
Description: "The display name of the player.",
Required: false,
},
}
addCommand(&DiscordCommand{ addCommand(&DiscordCommand{
Command: &discordgo.ApplicationCommand{ Command: &discordgo.ApplicationCommand{
Name: "create", Name: "create",
@ -58,7 +73,7 @@ func addCommands() {
addCommand(&DiscordCommand{ addCommand(&DiscordCommand{
Command: &discordgo.ApplicationCommand{ Command: &discordgo.ApplicationCommand{
Name: "information", Name: "information",
Description: "Useful information about this server's activity! Admin Only.", Description: "Useful information about this server's activity!",
}, },
Handler: informationHandler, Handler: informationHandler,
AdminOnly: true, AdminOnly: true,
@ -68,20 +83,7 @@ func addCommands() {
Command: &discordgo.ApplicationCommand{ Command: &discordgo.ApplicationCommand{
Name: "who", Name: "who",
Description: "Lookup a player's information.", Description: "Lookup a player's information.",
Options: []*discordgo.ApplicationCommandOption{ Options: personOptions,
{
Type: discordgo.ApplicationCommandOptionString,
Name: "display",
Description: "The display name of the player.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionUser,
Name: "discord",
Description: "The discord account of the player.",
Required: false,
},
},
}, },
Handler: whoHandler, Handler: whoHandler,
AdminOnly: true, AdminOnly: true,
@ -91,20 +93,7 @@ func addCommands() {
Command: &discordgo.ApplicationCommand{ Command: &discordgo.ApplicationCommand{
Name: "ban", Name: "ban",
Description: "Ban a player from using the bot.", Description: "Ban a player from using the bot.",
Options: []*discordgo.ApplicationCommandOption{ Options: personOptions,
{
Type: discordgo.ApplicationCommandOptionUser,
Name: "discord",
Description: "The discord account of the player.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "display",
Description: "The display name of the player.",
Required: false,
},
},
}, },
Handler: banHandler, Handler: banHandler,
AdminOnly: true, AdminOnly: true,
@ -114,40 +103,23 @@ func addCommands() {
Command: &discordgo.ApplicationCommand{ Command: &discordgo.ApplicationCommand{
Name: "unban", Name: "unban",
Description: "Unban a player from using the bot.", Description: "Unban a player from using the bot.",
Options: []*discordgo.ApplicationCommandOption{ Options: personOptions,
{
Type: discordgo.ApplicationCommandOptionUser,
Name: "discord",
Description: "The discord account of the player.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "display",
Description: "The display name of the player.",
Required: false,
},
},
}, },
Handler: unbanHandler, Handler: unbanHandler,
AdminOnly: true, AdminOnly: true,
}) })
addCommand(&DiscordCommand{ grantOptions := append([]*discordgo.ApplicationCommandOption{
Command: &discordgo.ApplicationCommand{
Name: "give",
Description: "Grant a player an item in the game.",
Options: []*discordgo.ApplicationCommandOption{
{ {
Type: discordgo.ApplicationCommandOptionString, Type: discordgo.ApplicationCommandOptionString,
Name: "template_id", Name: "template_id",
Description: "The item id of the cosmetic to give.", Description: "The item id of the cosmetic to give/take.",
Required: true, Required: true,
}, },
{ {
Type: discordgo.ApplicationCommandOptionInteger, Type: discordgo.ApplicationCommandOptionInteger,
Name: "quantity", Name: "quantity",
Description: "The amount of the item to give.", Description: "The amount of the item to give/take.",
Required: true, Required: true,
}, },
{ {
@ -156,19 +128,13 @@ func addCommands() {
Description: "common_core, athena, common_public, profile0, collections, creative", Description: "common_core, athena, common_public, profile0, collections, creative",
Required: true, Required: true,
}, },
{ }, personOptions...)
Type: discordgo.ApplicationCommandOptionUser,
Name: "discord", addCommand(&DiscordCommand{
Description: "The discord account of the player.", Command: &discordgo.ApplicationCommand{
Required: false, Name: "give",
}, Description: "Grant a player an item in the game.",
{ Options: grantOptions,
Type: discordgo.ApplicationCommandOptionString,
Name: "display",
Description: "The display name of the player.",
Required: false,
},
},
}, },
Handler: giveItemHandler, Handler: giveItemHandler,
AdminOnly: true, AdminOnly: true,
@ -178,38 +144,7 @@ func addCommands() {
Command: &discordgo.ApplicationCommand{ Command: &discordgo.ApplicationCommand{
Name: "take", Name: "take",
Description: "Take an item from a player in the game.", Description: "Take an item from a player in the game.",
Options: []*discordgo.ApplicationCommandOption{ Options: grantOptions,
{
Type: discordgo.ApplicationCommandOptionString,
Name: "template_id",
Description: "The item id of the cosmetic to take.",
Required: true,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "quantity",
Description: "The amount of the item to take.",
Required: true,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "profile",
Description: "common_core, athena, common_public, profile0, collections, creative",
Required: true,
},
{
Type: discordgo.ApplicationCommandOptionUser,
Name: "discord",
Description: "The discord account of the player.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "display",
Description: "The display name of the player.",
Required: false,
},
},
}, },
Handler: takeItemHandler, Handler: takeItemHandler,
AdminOnly: true, AdminOnly: true,
@ -219,34 +154,89 @@ func addCommands() {
Command: &discordgo.ApplicationCommand{ Command: &discordgo.ApplicationCommand{
Name: "everything", Name: "everything",
Description: "Give a player full locker", Description: "Give a player full locker",
Options: []*discordgo.ApplicationCommandOption{ Options: personOptions,
{
Type: discordgo.ApplicationCommandOptionUser,
Name: "discord",
Description: "The discord account of the player.",
Required: false,
},
{
Type: discordgo.ApplicationCommandOptionString,
Name: "display",
Description: "The display name of the player.",
Required: false,
},
},
}, },
Handler: giveEverythingHandler, Handler: giveEverythingHandler,
AdminOnly: true, AdminOnly: true,
}) })
permissionOptionChoices := []*discordgo.ApplicationCommandOptionChoice{
{
Name: "All",
Value: person.PermissionAll,
},
{
Name: "Lookup",
Value: person.PermissionLookup,
},
{
Name: "Information",
Value: person.PermissionInformation,
},
{
Name: "Donator",
Value: person.PermissionDonator,
},
{
Name: "ItemControl",
Value: person.PermissionItemControl,
},
{
Name: "LockerControl",
Value: person.PermissionLockerControl,
},
{
Name: "Owner",
Value: person.PermissionOwner,
},
{
Name: "PermissionControl",
Value: person.PermissionPermissionControl,
},
} }
func getPersonFromOptions(data discordgo.ApplicationCommandInteractionData, s *discordgo.Session) *person.Person { permissionOptions := append([]*discordgo.ApplicationCommandOption{
options := data.Options {
Type: discordgo.ApplicationCommandOptionInteger,
Name: "permission",
Description: "The permission to add/take.",
Required: true,
Choices: permissionOptionChoices,
},
}, personOptions...)
if len(options) <= 0 { permissionSubCommands := []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "add",
Description: "Add a permission to a player.",
Options: permissionOptions,
},
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "remove",
Description: "Rake a permission from a player.",
Options: permissionOptions,
},
}
addCommand(&DiscordCommand{
Command: &discordgo.ApplicationCommand{
Name: "permission",
Description: "Give or take permissions from a player.",
Options: permissionSubCommands,
},
Handler: permissionHandler,
AdminOnly: true,
})
}
func getPersonFromOptions(opts []*discordgo.ApplicationCommandInteractionDataOption, s *discordgo.Session) *person.Person {
if len(opts) <= 0 {
return nil return nil
} }
for _, option := range options { for _, option := range opts {
switch option.Type { switch option.Type {
case discordgo.ApplicationCommandOptionUser: case discordgo.ApplicationCommandOptionUser:
if option.Name != "discord" { if option.Name != "discord" {

View File

@ -58,15 +58,44 @@ func getAverageColour(img image.Image) colours {
} }
} }
func GetAverageHexColour(img image.Image) string {
colour := getAverageColour(img)
return "#" + aid.ToHex(int(colour.averageRed)) + aid.ToHex(int(colour.averageGreen)) + aid.ToHex(int(colour.averageBlue))
}
func colorDifference(c1, c2 colours) float64 { func colorDifference(c1, c2 colours) float64 {
diffRed := int(c1.averageRed) - int(c2.averageRed) diffRed := int(c1.averageRed) - int(c2.averageRed)
diffGreen := int(c1.averageGreen) - int(c2.averageGreen) diffGreen := int(c1.averageGreen) - int(c2.averageGreen)
diffBlue := int(c1.averageBlue) - int(c2.averageBlue) diffBlue := int(c1.averageBlue) - int(c2.averageBlue)
// Using Euclidean distance to calculate color difference
return math.Sqrt(float64(diffRed*diffRed + diffGreen*diffGreen + diffBlue*diffBlue)) return math.Sqrt(float64(diffRed*diffRed + diffGreen*diffGreen + diffBlue*diffBlue))
} }
func GetCharacterImage(characterId string) image.Image {
character, ok := Cosmetics.Items[characterId]
if !ok {
return getRandomCharacterImage()
}
response, err := http.Get(character.Images.Featured)
if err != nil {
return getRandomCharacterImage()
}
defer response.Body.Close()
b, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
image, _, err := image.Decode(bytes.NewReader(b))
if err != nil {
panic(err)
}
return image
}
func getRandomCharacterImage() image.Image { func getRandomCharacterImage() image.Image {
found := false found := false
var character FAPI_Cosmetic var character FAPI_Cosmetic

View File

@ -29,22 +29,22 @@ func PostFortniteToken(c *fiber.Ctx) error {
var body FortniteTokenBody var body FortniteTokenBody
if err := c.BodyParser(&body); err != nil { if err := c.BodyParser(&body); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Request Body")) return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Request Body"))
} }
if action, ok := oauthTokenGrantTypes[body.GrantType]; ok { if action, ok := oauthTokenGrantTypes[body.GrantType]; ok {
return action(c, &body) return action(c, &body)
} }
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Grant Type")) return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Grant Type"))
} }
func PostTokenClientCredentials(c *fiber.Ctx, body *FortniteTokenBody) error { func PostTokenClientCredentials(c *fiber.Ctx, body *FortniteTokenBody) error {
if aid.Config.Fortnite.DisableClientCredentials { if aid.Config.Fortnite.DisableClientCredentials {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Client Credentials is disabled.")) return c.Status(400).JSON(aid.ErrorBadRequest("Client Credentials is disabled."))
} }
return c.Status(fiber.StatusOK).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
"access_token": "snow", "access_token": "snow",
"token_type": "bearer", "token_type": "bearer",
"client_id": aid.Hash([]byte(c.IP())), "client_id": aid.Hash([]byte(c.IP())),
@ -57,37 +57,37 @@ func PostTokenClientCredentials(c *fiber.Ctx, body *FortniteTokenBody) error {
func PostTokenExchangeCode(c *fiber.Ctx, body *FortniteTokenBody) error { func PostTokenExchangeCode(c *fiber.Ctx, body *FortniteTokenBody) error {
if body.ExchangeCode == "" { if body.ExchangeCode == "" {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Exchange Code is empty")) return c.Status(400).JSON(aid.ErrorBadRequest("Exchange Code is empty"))
} }
codeParts := strings.Split(body.ExchangeCode, ".") codeParts := strings.Split(body.ExchangeCode, ".")
if len(codeParts) != 2 { if len(codeParts) != 2 {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Exchange Code")) return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Exchange Code"))
} }
code, failed := aid.KeyPair.DecryptAndVerifyB64(codeParts[0], codeParts[1]) code, failed := aid.KeyPair.DecryptAndVerifyB64(codeParts[0], codeParts[1])
if failed { if failed {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Exchange Code")) return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Exchange Code"))
} }
personParts := strings.Split(string(code), "=") personParts := strings.Split(string(code), "=")
if len(personParts) != 2 { if len(personParts) != 2 {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Exchange Code")) return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Exchange Code"))
} }
personId := personParts[0] personId := personParts[0]
expire, err := time.Parse("2006-01-02T15:04:05.999Z", personParts[1]) expire, err := time.Parse("2006-01-02T15:04:05.999Z", personParts[1])
if err != nil { if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Exchange Code")) return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Exchange Code"))
} }
if expire.Add(time.Hour).Before(time.Now()) { if expire.Add(time.Hour).Before(time.Now()) {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Exchange Code")) return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Exchange Code"))
} }
person := p.Find(personId) person := p.Find(personId)
if person == nil { if person == nil {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Invalid Exchange Code")) return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Exchange Code"))
} }
access, err := aid.JWTSign(aid.JSON{ access, err := aid.JWTSign(aid.JSON{
@ -108,7 +108,7 @@ func PostTokenExchangeCode(c *fiber.Ctx, body *FortniteTokenBody) error {
return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer) return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer)
} }
return c.Status(fiber.StatusOK).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
"access_token": "eg1~" + access, "access_token": "eg1~" + access,
"account_id": person.ID, "account_id": person.ID,
"client_id": c.IP(), "client_id": c.IP(),
@ -117,9 +117,9 @@ func PostTokenExchangeCode(c *fiber.Ctx, body *FortniteTokenBody) error {
"device_id": "default", "device_id": "default",
"display_name": person.DisplayName, "display_name": person.DisplayName,
"expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"), "expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"),
"expires_in": 86400, "expires_in": 86200,
"internal_client": true, "internal_client": true,
"refresh_expires": 86400, "refresh_expires": 86200,
"refresh_expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"), "refresh_expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"),
"refresh_token": "eg1~" + refresh, "refresh_token": "eg1~" + refresh,
"token_type": "bearer", "token_type": "bearer",
@ -130,16 +130,16 @@ func PostTokenExchangeCode(c *fiber.Ctx, body *FortniteTokenBody) error {
func PostTokenPassword(c *fiber.Ctx, body *FortniteTokenBody) error { func PostTokenPassword(c *fiber.Ctx, body *FortniteTokenBody) error {
if aid.Config.Fortnite.Password { if aid.Config.Fortnite.Password {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Username and password authentication is disabled for security reasons. Please use an exchange code given by the discord bot.")) return c.Status(400).JSON(aid.ErrorBadRequest("Username and password authentication is disabled for security reasons. Please use an exchange code given by the discord bot."))
} }
if body.Username == "" || body.Password == "" { if body.Username == "" || body.Password == "" {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("Username/Password is empty")) return c.Status(400).JSON(aid.ErrorBadRequest("Username/Password is empty"))
} }
person := p.FindByDisplay(strings.Split(body.Username, "@")[0]) person := p.FindByDisplay(strings.Split(body.Username, "@")[0])
if person == nil { if person == nil {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("No Account Found")) return c.Status(400).JSON(aid.ErrorBadRequest("No Account Found"))
} }
access, err := aid.JWTSign(aid.JSON{ access, err := aid.JWTSign(aid.JSON{
@ -160,7 +160,7 @@ func PostTokenPassword(c *fiber.Ctx, body *FortniteTokenBody) error {
return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer) return c.Status(fiber.StatusInternalServerError).JSON(aid.ErrorInternalServer)
} }
return c.Status(fiber.StatusOK).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
"access_token": "eg1~" + access, "access_token": "eg1~" + access,
"account_id": person.ID, "account_id": person.ID,
"client_id": c.IP(), "client_id": c.IP(),
@ -169,9 +169,9 @@ func PostTokenPassword(c *fiber.Ctx, body *FortniteTokenBody) error {
"device_id": "default", "device_id": "default",
"display_name": person.DisplayName, "display_name": person.DisplayName,
"expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"), "expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"),
"expires_in": 86400, "expires_in": 86200,
"internal_client": true, "internal_client": true,
"refresh_expires": 86400, "refresh_expires": 86200,
"refresh_expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"), "refresh_expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"),
"refresh_token": "eg1~" + refresh, "refresh_token": "eg1~" + refresh,
"token_type": "bearer", "token_type": "bearer",
@ -191,12 +191,12 @@ func GetTokenVerify(c *fiber.Ctx) error {
return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token")) return c.Status(fiber.StatusForbidden).JSON(aid.ErrorBadRequest("Invalid Access Token"))
} }
return c.Status(fiber.StatusOK).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
"app": "fortnite", "app": "fortnite",
"token": strings.ReplaceAll(c.Get("Authorization"), "bearer eg1~", ""), "token": strings.ReplaceAll(c.Get("Authorization"), "bearer eg1~", ""),
"token_type": "bearer", "token_type": "bearer",
"expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"), "expires_at": time.Now().Add(time.Hour * 24).Format("2006-01-02T15:04:05.999Z"),
"expires_in": 86400, "expires_in": 86200,
"client_id": c.IP(), "client_id": c.IP(),
"session_id": "0", "session_id": "0",
"device_id": "default", "device_id": "default",
@ -211,7 +211,7 @@ func GetTokenVerify(c *fiber.Ctx) error {
} }
func DeleteToken(c *fiber.Ctx) error { func DeleteToken(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(aid.JSON{}) return c.Status(200).JSON(aid.JSON{})
} }
func MiddlewareFortnite(c *fiber.Ctx) error { func MiddlewareFortnite(c *fiber.Ctx) error {
@ -247,10 +247,10 @@ func MiddlewareWeb(c *fiber.Ctx) error {
func GetPublicAccount(c *fiber.Ctx) error { func GetPublicAccount(c *fiber.Ctx) error {
person := p.Find(c.Params("accountId")) person := p.Find(c.Params("accountId"))
if person == nil { if person == nil {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("No Account Found")) return c.Status(400).JSON(aid.ErrorBadRequest("No Account Found"))
} }
return c.Status(fiber.StatusOK).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
"id": person.ID, "id": person.ID,
"displayName": person.DisplayName, "displayName": person.DisplayName,
"externalAuths": []aid.JSON{}, "externalAuths": []aid.JSON{},
@ -274,27 +274,37 @@ func GetPublicAccounts(c *fiber.Ctx) error {
}) })
} }
return c.Status(fiber.StatusOK).JSON(response) return c.Status(200).JSON(response)
} }
func GetPublicAccountExternalAuths(c *fiber.Ctx) error { func GetPublicAccountExternalAuths(c *fiber.Ctx) error {
person := p.Find(c.Params("accountId")) person := p.Find(c.Params("accountId"))
if person == nil { if person == nil {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("No Account Found")) return c.Status(400).JSON(aid.ErrorBadRequest("No Account Found"))
} }
return c.Status(fiber.StatusOK).JSON([]aid.JSON{}) return c.Status(200).JSON([]aid.JSON{})
} }
func GetPublicAccountByDisplayName(c *fiber.Ctx) error { func GetPublicAccountByDisplayName(c *fiber.Ctx) error {
person := p.FindByDisplay(c.Params("displayName")) person := p.FindByDisplay(c.Params("displayName"))
if person == nil { if person == nil {
return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest("No Account Found")) return c.Status(400).JSON(aid.ErrorBadRequest("No Account Found"))
} }
return c.Status(fiber.StatusOK).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
"id": person.ID, "id": person.ID,
"displayName": person.DisplayName, "displayName": person.DisplayName,
"externalAuths": []aid.JSON{}, "externalAuths": []aid.JSON{},
}) })
} }
func GetPrivacySettings(c *fiber.Ctx) error {
return c.Status(200).JSON(aid.JSON{
"privacySettings": aid.JSON{
"playRegion": "PUBLIC",
"badges": "PUBLIC",
"languages": "PUBLIC",
},
})
}

View File

@ -32,7 +32,6 @@ func GetLightswitchBulkStatus(c *fiber.Ctx) error {
"banned": isBanned, "banned": isBanned,
"launcherInfoDTO": aid.JSON{ "launcherInfoDTO": aid.JSON{
"appName":"Fortnite", "appName":"Fortnite",
"catalogItemId":"4fe75bbc5a674f4f9b356b5c90567da5",
"namespace":"fn", "namespace":"fn",
}, },
}}) }})
@ -65,34 +64,34 @@ func GetFortniteTimeline(c *fiber.Ctx) error {
switch season { switch season {
case 2: case 2:
events = append(events, aid.JSON{ events = append(events, aid.JSON{
"activeUntil": "9999-12-31T23:59:59.999Z", "activeUntil": "9999-01-01T00:00:00.000Z",
"activeSince": "0001-01-01T00:00:00Z", "activeSince": "9999-01-01T00:00:00.000Z",
"eventType": "EventFlag.LobbyWinterDecor", "eventType": "EventFlag.LobbyWinterDecor",
}) })
case 6: case 6:
events = append(events, aid.JSON{ events = append(events, aid.JSON{
"activeUntil": "9999-01-01T00:00:00.000Z", "activeUntil": "9999-01-01T00:00:00.000Z",
"activeSince": "0001-01-01T00:00:00Z", "activeSince": "9999-01-01T00:00:00.000Z",
"eventType": "EventFlag.LobbySeason6Halloween", "eventType": "EventFlag.LobbySeason6Halloween",
}) })
case 11: case 11:
events = append(events, aid.JSON{ events = append(events, aid.JSON{
"activeUntil": "9999-01-01T00:00:00.000Z", "activeUntil": "9999-01-01T00:00:00.000Z",
"activeSince": "0001-01-01T00:00:00Z", "activeSince": "9999-01-01T00:00:00.000Z",
"eventType": "EventFlag.LTE_WinterFest2019", "eventType": "EventFlag.LTE_WinterFest2019",
}, aid.JSON{ }, aid.JSON{
"activeUntil": "9999-01-01T00:00:00.000Z", "activeUntil": "9999-01-01T00:00:00.000Z",
"activeSince": "0001-01-01T00:00:00Z", "activeSince": "9999-01-01T00:00:00.000Z",
"eventType": "EventFlag.LTE_WinterFest", "eventType": "EventFlag.LTE_WinterFest",
}, aid.JSON{ }, aid.JSON{
"activeUntil": "9999-01-01T00:00:00.000Z", "activeUntil": "9999-01-01T00:00:00.000Z",
"activeSince": "0001-01-01T00:00:00Z", "activeSince": "9999-01-01T00:00:00.000Z",
"eventType": "EventFlag.Winterfest.Tree", "eventType": "EventFlag.Winterfest.Tree",
}) })
default: default:
events = append(events, aid.JSON{ events = append(events, aid.JSON{
"activeUntil": "9999-12-31T23:59:59.999Z", "activeUntil": "9999-01-01T00:00:00.000Z",
"activeSince": "0001-01-01T00:00:00Z", "activeSince": "9999-01-01T00:00:00.000Z",
"eventType": "EventFlag.LobbySeason" + strings.Split(build, ".")[0], "eventType": "EventFlag.LobbySeason" + strings.Split(build, ".")[0],
}) })
} }

23
main.go
View File

@ -55,14 +55,21 @@ func init() {
found = fortnite.NewFortnitePersonWithId(username, username, true) found = fortnite.NewFortnitePersonWithId(username, username, true)
} }
for _, perm := range found.Permissions { found.AddPermission(person.PermissionAllWithRoles)
found.RemovePermission(perm)
}
found.AddPermission("all")
aid.Print("(snow) max account " + username + " loaded") aid.Print("(snow) max account " + username + " loaded")
} }
for _, username := range aid.Config.Accounts.Owners {
found := person.FindByDisplay(username)
if found == nil {
continue
} }
found.AddPermission(person.PermissionOwner)
aid.Print("(snow) owner account " + username + " loaded")
}
}
func main() { func main() {
r := fiber.New(fiber.Config{ r := fiber.New(fiber.Config{
DisableStartupMessage: true, DisableStartupMessage: true,
@ -70,7 +77,6 @@ func main() {
JSONDecoder: json.Unmarshal, JSONDecoder: json.Unmarshal,
}) })
r.Use(aid.FiberLogger()) r.Use(aid.FiberLogger())
r.Use(aid.FiberLimiter()) r.Use(aid.FiberLimiter())
r.Use(aid.FiberCors()) r.Use(aid.FiberCors())
@ -78,9 +84,12 @@ func main() {
r.Get("/region", handlers.GetRegion) r.Get("/region", handlers.GetRegion)
r.Get("/content/api/pages/fortnite-game", handlers.GetContentPages) r.Get("/content/api/pages/fortnite-game", handlers.GetContentPages)
r.Get("/waitingroom/api/waitingroom", handlers.GetWaitingRoomStatus) r.Get("/waitingroom/api/waitingroom", handlers.GetWaitingRoomStatus)
r.Get("/affiliate/api/public/affiliates/slug/:slug", handlers.GetAffiliate)
r.Get("/api/v1/search/:accountId", handlers.GetPersonSearch) r.Get("/api/v1/search/:accountId", handlers.GetPersonSearch)
r.Post("/api/v1/assets/Fortnite/:versionId/:assetName", handlers.PostAssets) r.Post("/api/v1/assets/Fortnite/:versionId/:assetName", handlers.PostAssets)
r.Get("/affiliate/api/public/affiliates/slug/:slug", handlers.GetAffiliate)
r.Get("/profile/privacy_settings", handlers.MiddlewareFortnite, handlers.GetPrivacySettings)
r.Put("/profile/play_region", handlers.AnyNoContent) r.Put("/profile/play_region", handlers.AnyNoContent)
r.Get("/", handlers.RedirectSocket) r.Get("/", handlers.RedirectSocket)

View File

@ -8,4 +8,4 @@ run:
go run main.go go run main.go
test: test:
go test -v ./... go test

View File

@ -5,7 +5,7 @@ import "github.com/ectrc/snow/storage"
type PersonSnapshot struct { type PersonSnapshot struct {
ID string ID string
DisplayName string DisplayName string
Permissions []string Permissions int64
AthenaProfile ProfileSnapshot AthenaProfile ProfileSnapshot
CommonCoreProfile ProfileSnapshot CommonCoreProfile ProfileSnapshot
CommonPublicProfile ProfileSnapshot CommonPublicProfile ProfileSnapshot

View File

@ -19,9 +19,9 @@
- Interaction with a Game Server to handle **Event Tracking** for player statistics and challenges. This will be a very large task as a new specialised game server will need to be created. - Interaction with a Game Server to handle **Event Tracking** for player statistics and challenges. This will be a very large task as a new specialised game server will need to be created.
- After the game server addition, a **Matchmaking System** will be added to match players together for a game. It will use a bin packing algorithm to ensure that games are filled as much as possible. - After the game server addition, a **Matchmaking System** will be added to match players together for a game. It will use a bin packing algorithm to ensure that games are filled as much as possible.
And finally, the biggest task of all... And once battle royale is completed ...
- **Save The World**. This is a very large task and will require a lot of work. It is not a priority at the moment and might be done after the Battle Royale experience is complete. - **Save The World**
## Supported MCP Actions ## Supported MCP Actions