Skip to content

Java 11+ client for the APIs used by Epic Games Launcher and the Fortnite client

License

Notifications You must be signed in to change notification settings

RobertoGraham/fortnite-2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Maven Central

fortnite-2

A Java 11+ client for the APIs used by Epic Games Launcher and the Fortnite client

Features

  • Authentication with Epic's APIs is managed internally
  • Fetching and filtering (by platform, party type, and time window) of an account's Battle Royale statistics
  • Fetching of all-time win leader boards using different combinations of platform and party type
  • Fetching of a single account via its username or many accounts via their IDs
  • Adding and removing friends, accepting and declining friend requests
  • Fortnite EULA auto-accepting
  • Two-factor authentication

Installation

Maven

<properties>
  ...
  <!-- Use the latest version whenever possible. -->
  <fortnite-2.version>2.0.0</fortnite-2.version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>io.github.robertograham</groupId>
    <artifactId>fortnite-2</artifactId>
    <version>${fortnite-2.version}</version>
  </dependency>
  ...
</dependencies>

Usage

Instantiating a client

This is the simplest way to instantiate a client:

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        final var fortnite = builder.build();
    }
}

If Epic Games ever deprecate this library's default launcher and client tokens, you may provide your own like this:

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword")
            .setEpicGamesLauncherToken("launcherToken")
            .setFortniteClientToken("clientToken");
        final var fortnite = builder.build();
    }
}

Fortnite's EULA can be automatically accepted and access can be granted to Fortnite's APIs like so:

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

public final class Main {

    public static void main(final String[] args) {
        try (final var fortnite = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword")
            .setAutoAcceptEulaAndGrantAccess(true)
            .build()) {
        }
    }
}

Cleaning up

When you no longer need your client instance, remember to terminate your Epic Games authentication session and release the client's underlying resources with a call to Fortnite.close(). Usage examples further in this document will make this call implicitly using try-with-resources statements.

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        final var fortnite = builder.build();
        fortnite.close();
    }
}

Fetching an account using its username

import io.github.robertograham.fortnite2.domain.Account;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var optionalAccount = fortnite.account()
                .findOneByDisplayName("RobertoGraham");
            // nothing printed if the response was empty
            optionalAccount.map(Account::accountId)
                .ifPresent(System.out::println);
            optionalAccount.map(Account::displayName)
                .ifPresent(System.out::println);
        } catch (final IOException exception) {
            // findOneByDisplayName unexpected response
        }
    }
}

Fetching an account using the session's account ID

import io.github.robertograham.fortnite2.domain.Account;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var optionalAccount = fortnite.account()
                .findOneBySessionAccountId();
            // nothing printed if the response was empty
            optionalAccount.map(Account::accountId)
                .ifPresent(System.out::println);
            optionalAccount.map(Account::displayName)
                .ifPresent(System.out::println);
        } catch (final IOException exception) {
            // findOneBySessionAccountId unexpected response
        }
    }
}

Fetching many accounts using their IDs

import io.github.robertograham.fortnite2.domain.Account;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;
import java.util.Collections;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var accountId1String = fortnite.account()
                .findOneByDisplayName("RobertoGraham")
                .map(Account::accountId)
                .orElse("");
            final var accountId2String = fortnite.account()
                .findOneByDisplayName("Ninja")
                .map(Account::accountId)
                .orElse("");
            // accountSet will be empty if the response was empty
            // OR if every account ID was invalid
            final var accountSet = fortnite.account()
                .findAllByAccountIds(accountId1String, accountId2String)
                .orElseGet(Collections::emptySet);
        } catch (final IOException exception) {
            // findOneByDisplayName unexpected response
            // OR findAllByAccountIds unexpected response
        }
    }
}

Statistic filtering API

Most basic filtering - by time windows

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var account = fortnite.account()
                .findOneByDisplayName("RobertoGraham")
                .orElseThrow();
            final var accountIdString = account.accountId();
            // if any null then we received an empty response
            final var fromAccountAllTimeFilterableStatistic = fortnite.statistic()
                .findAllByAccountForAllTime(account)
                .orElse(null);
            final var fromAccountIdAllTimeFilterableStatistic = fortnite.statistic()
                .findAllByAccountIdForAllTime(accountIdString)
                .orElse(null);
            final var fromAccountCurrentSeasonFilterableStatistic = fortnite.statistic()
                .findAllByAccountForCurrentSeason(account)
                .orElse(null);
            final var fromAccountIdCurrentSeasonFilterableStatistic = fortnite.statistic()
                .findAllByAccountIdForCurrentSeason(accountIdString)
                .orElse(null);
            final var fromSessionAccountIdAllTimeFilterableStatistic = fortnite.statistic()
                .findAllBySessionAccountIdForAllTime()
                .orElse(null);
            final var fromSessionAccountIdCurrentSeasonFilterableStatistic = fortnite.statistic()
                .findAllBySessionAccountIdForCurrentSeason()
                .orElse(null);
        } catch (final IOException exception) {
            // findOneByDisplayName unexpected response
            // OR findAllByAccountForAllTime unexpected response
            // OR findAllByAccountIdForAllTime unexpected response
            // OR findAllByAccountForCurrentSeason unexpected response
            // OR findAllByAccountIdForCurrentSeason unexpected response
            // OR findAllBySessionAccountIdForAllTime unexpected response
            // OR findAllBySessionAccountIdForCurrentSeason unexpected response
        }
    }
}

Filtering by platform and then party type

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.SQUAD;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PC;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var account = fortnite.account()
                .findOneByDisplayName("RobertoGraham")
                .orElseThrow();
            final var partyTypeFilterableStatistic = fortnite.statistic()
                .findAllByAccountForAllTime(account)
                .map(filterableStatistic -> filterableStatistic.byPlatform(PC))
                .orElseThrow();
            final var statistic = partyTypeFilterableStatistic.byPartyType(SQUAD);
        } catch (final IOException exception) {
            // findOneByDisplayName unexpected response
            // OR findAllByAccountForAllTime unexpected response
        }
    }
}

Filtering by party type and then platform

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.SOLO;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PS4;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var account = fortnite.account()
                .findOneByDisplayName("RobertoGraham")
                .orElseThrow();
            final var platformFilterableStatistic = fortnite.statistic()
                .findAllByAccountForAllTime(account)
                .map(filterableStatistic -> filterableStatistic.byPartyType(SOLO))
                .orElseThrow();
            final var statistic = platformFilterableStatistic.byPlatform(PS4);
        } catch (final IOException exception) {
            // findOneByDisplayName unexpected response
            // OR findAllByAccountForAllTime unexpected response
        }
    }
}

Inline platform and party type chained filter

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.DUO;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.XB1;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var account = fortnite.account()
                .findOneByDisplayName("RobertoGraham")
                .orElseThrow();
            final var statistic = fortnite.statistic()
                .findAllByAccountForAllTime(account)
                .map((final var filterableStatistic) ->
                    filterableStatistic
                        .byPlatform(XB1)
                        .byPartyType(DUO)
                )
                .orElse(null);
        } catch (final IOException exception) {
            // findOneByDisplayName unexpected response
            // OR findAllByAccountForAllTime unexpected response
        }
    }
}

FilterableStatistic, PartyTypeFilterableStatistic, and PlatformFilterableStatistic all extend Statistic. This means that you can make calls like Statistic.kills(), Statistic.wins(), etc. at each filtering stage to get narrower and narrower scoped values

import io.github.robertograham.fortnite2.domain.FilterableStatistic;
import io.github.robertograham.fortnite2.domain.PartyTypeFilterableStatistic;
import io.github.robertograham.fortnite2.domain.PlatformFilterableStatistic;
import io.github.robertograham.fortnite2.domain.Statistic;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.SOLO;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PC;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PS4;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var account = fortnite.account()
                .findOneByDisplayName("RobertoGraham")
                .orElseThrow();
            final var filterableStatisticOptional = fortnite.statistic()
                .findAllByAccountForAllTime(account);
            // prints 761 at time of writing
            filterableStatisticOptional.map(FilterableStatistic::kills)
                .ifPresent(System.out::println);
            // prints 5 at time of writing
            filterableStatisticOptional.map((final var filterableStatistic) -> filterableStatistic.byPlatform(PC))
                .map(PartyTypeFilterableStatistic::kills)
                .ifPresent(System.out::println);
            // prints 580 at time of writing
            filterableStatisticOptional.map((final var filterableStatistic) -> filterableStatistic.byPartyType(SOLO))
                .map(PlatformFilterableStatistic::kills)
                .ifPresent(System.out::println);
            // prints 575 at time of writing
            filterableStatisticOptional.map((final var filterableStatistic) ->
                filterableStatistic
                    .byPlatform(PS4)
                    .byPartyType(SOLO)
            )
                .map(Statistic::kills)
                .ifPresent(System.out::println);
        } catch (final IOException exception) {
            // findOneByDisplayName unexpected response
            // OR findAllByAccountForAllTime unexpected response
        }
    }
}

Leader board API

Fetch the current season's wins leader board and print the accounts' names and wins. The example prints the following at the time of writing:

Name: JohnPitterTV, Wins: 350
Name: TTV SwitchUMG, Wins: 251
Name: Twitch TheBigOCE, Wins: 201
Name: WE_桃子, Wins: 197
Name: BlossoM Tsunami, Wins: 182
import io.github.robertograham.fortnite2.domain.Account;
import io.github.robertograham.fortnite2.domain.LeaderBoardEntry;
import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;
import java.util.function.Function;
import java.util.stream.Collectors;

import static io.github.robertograham.fortnite2.domain.enumeration.PartyType.SOLO;
import static io.github.robertograham.fortnite2.domain.enumeration.Platform.PC;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var leaderBoardEntryList = fortnite.leaderBoard()
                .findHighestWinnersByPlatformAndByPartyTypeForCurrentSeason(PC, SOLO, 5)
                .orElseThrow();
            final var accountIdStringToAccountMap = fortnite.account()
                .findAllByAccountIds(leaderBoardEntryList.stream()
                    .map(LeaderBoardEntry::accountId)
                    .map((final var accountIdString) -> accountIdString.replaceAll("-", ""))
                    .toArray(String[]::new))
                .map((final var accountSet) ->
                    accountSet.stream()
                        .collect(Collectors.toMap(Account::accountId, Function.identity()))
                )
                .orElseThrow();
            leaderBoardEntryList.stream()
                .map((final var leaderBoardEntry) ->
                    String.format(
                        "Name: %s, Wins: %d",
                        accountIdStringToAccountMap.get(leaderBoardEntry.accountId().replaceAll("-", "")).displayName(),
                        leaderBoardEntry.value()
                    )
                )
                .forEach(System.out::println);
        } catch (final IOException exception) {
            // findHighestWinnersByPlatformAndByPartyTypeForCurrentSeason unexpected response
            // OR findAllByAccountIds unexpected response
        }
    }
}

Friend API

Fetch both accepted and pending friend requests for the authenticated user

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            fortnite.friend()
                .findAllRequestsBySessionAccountId()
                .ifPresent(System.out::println);
        } catch (final IOException exception) {
            // findAllRequestsBySessionAccountId unexpected response
        }
    }
}

Fetch only accepted friend requests for the authenticated user

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            fortnite.friend()
                .findAllNonPendingRequestsBySessionAccountId()
                .ifPresent(System.out::println);
        } catch (final IOException exception) {
            // findAllNonPendingRequestsBySessionAccountId unexpected response
        }
    }
}

Delete a friend or a friend request using an account or an account ID

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var account = fortnite.account()
                .findOneByDisplayName("RobertoGraham")
                .orElseThrow();
            fortnite.friend()
                .deleteOneByAccount(account);
            fortnite.friend()
                .deleteOneByAccountId(account.accountId());
        } catch (final IOException exception) {
            // findOneByDisplayName unexpected response
            // OR deleteOneByAccount unexpected response
            // OR deleteOneByAccountId unexpected response
        }
    }
}

Add a friend or accept a friend request using an account or an account ID

import io.github.robertograham.fortnite2.implementation.DefaultFortnite.Builder;

import java.io.IOException;

public class Main {

    public static void main(final String[] args) {
        final var builder = Builder.newInstance("epicGamesEmailAddress", "epicGamesPassword");
        try (final var fortnite = builder.build()) {
            final var account = fortnite.account()
                .findOneByDisplayName("RobertoGraham")
                .orElseThrow();
            fortnite.friend()
                .addOneByAccount(account);
            fortnite.friend()
                .addOneByAccountId(account.accountId());
        } catch (final IOException exception) {
            // findOneByDisplayName unexpected response
            // OR addOneByAccount unexpected response
            // OR addOneByAccountId unexpected response
        }
    }
}