All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.robertograham.fortnite2.xmpp.implementation.DefaultFortniteXmpp Maven / Gradle / Ivy

package io.github.robertograham.fortnite2.xmpp.implementation;

import io.github.robertograham.fortnite2.client.Fortnite;
import io.github.robertograham.fortnite2.xmpp.client.FortniteXmpp;
import io.github.robertograham.fortnite2.xmpp.domain.Session;
import io.github.robertograham.fortnite2.xmpp.domain.enumeration.Application;
import io.github.robertograham.fortnite2.xmpp.domain.enumeration.Platform;
import io.github.robertograham.fortnite2.xmpp.domain.enumeration.Status;
import io.github.robertograham.fortnite2.xmpp.listener.OnChatMessageReceivedListener;
import io.github.robertograham.fortnite2.xmpp.listener.OnFriendPresenceReceivedListener;
import io.github.robertograham.fortnite2.xmpp.listener.OnFriendsListReceivedListener;
import io.github.robertograham.fortnite2.xmpp.resource.ChatResource;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.StanzaTypeFilter;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.ping.PingManager;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Domainpart;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.jid.parts.Resourcepart;

import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Default implementation of {@link FortniteXmpp} created using {@link DefaultFortniteXmpp.Builder}
 */
public final class DefaultFortniteXmpp implements FortniteXmpp {

    private final Domainpart xmppDomainpart;
    private final Fortnite fortnite;
    private final XMPPTCPConnection prodServiceXmppTcpConnection;
    private final StanzaListener rosterPacketListener;
    private final StanzaListener presenceListener;
    private final OnFriendsListReceivedListener onFriendsListReceivedListener;
    private final OnFriendPresenceReceivedListener onFriendPresenceReceivedListener;
    private final DefaultChatResource chatResource;
    private final PingManager pingManager;

    private DefaultFortniteXmpp(final Builder builder) throws InterruptedException, XMPPException, SmackException, IOException {
        fortnite = builder.fortnite;
        onFriendsListReceivedListener = builder.onFriendsListReceivedListener;
        onFriendPresenceReceivedListener = builder.onFriendPresenceReceivedListener;
        SmackConfiguration.DEBUG = builder.debugXmppConnections;
        xmppDomainpart = Domainpart.fromOrThrowUnchecked("prod.ol.epicgames.com");
        prodServiceXmppTcpConnection = buildProdServiceXmppTcpConnection();
        rosterPacketListener = createRosterPacketListener();
        presenceListener = createPresenceListener();
        prodServiceXmppTcpConnection.addAsyncStanzaListener(rosterPacketListener, new StanzaTypeFilter(RosterPacket.class));
        prodServiceXmppTcpConnection.addAsyncStanzaListener(presenceListener, new StanzaTypeFilter(Presence.class));
        chatResource = DefaultChatResource.newInstance(builder.onChatMessageReceivedListener, prodServiceXmppTcpConnection);
        prodServiceXmppTcpConnection.connect()
            .login(
                fortnite.session().accountId(),
                fortnite.session().accessToken(),
                Resourcepart.from(String.format(
                    "V2:%s:%s",
                    builder.application.code(),
                    builder.platform.code()
                ))
            );
        pingManager = PingManager.getInstanceFor(prodServiceXmppTcpConnection);
        pingManager.setPingInterval(Math.toIntExact(TimeUnit.MINUTES.toSeconds(4) + 30L));
    }

    private XMPPTCPConnection buildProdServiceXmppTcpConnection() {
        return new XMPPTCPConnection(XMPPTCPConnectionConfiguration.builder()
            .setXmppDomain(JidCreate.domainBareFrom(xmppDomainpart))
            .setHost("xmpp-service-prod.ol.epicgames.com")
            .setPort(5222)
            .build());
    }

    private StanzaListener createRosterPacketListener() {
        return (final var stanza) -> Optional.of(stanza)
            .filter((final var nonNullStanza) -> nonNullStanza instanceof RosterPacket)
            .map((final var nonNullStanza) -> (RosterPacket) nonNullStanza)
            .map((final var rosterPacket) -> rosterPacket.getRosterItems().stream()
                .map(RosterPacket.Item::getJid)
                .map(Jid::getLocalpartOrNull)
                .filter(Objects::nonNull)
                .map(Localpart::asUnescapedString)
                .collect(Collectors.toSet()))
            .ifPresent(onFriendsListReceivedListener::onFriendsListReceived);
    }

    private StanzaListener createPresenceListener() {
        return (final var stanza) -> Optional.of(stanza)
            .filter((final var nonNullStanza) -> nonNullStanza instanceof Presence)
            .map((final var nonNullStanza) -> (Presence) nonNullStanza)
            .filter((final var presence) -> presence.isAvailable()
                || Presence.Type.unavailable == presence.getType())
            .ifPresent((final var presence) -> Optional.ofNullable(presence.getFrom().getLocalpartOrNull())
                .map(Localpart::asUnescapedString)
                .filter((final var accountId) -> !fortnite.session().accountId().equals(accountId))
                .ifPresent((final var accountId) -> {
                    final var status = presence.isAvailable() ?
                        presence.isAway() ?
                            Status.AWAY
                            : Status.ONLINE
                        : Status.OFFLINE;
                    final var sessionOptional = JsonToOptionalOfClassParser.INSTANCE.parseJsonToOptionalOfClass(DefaultSession.class, presence.getStatus())
                        .map((final var defaultSession) -> (Session) defaultSession);
                    onFriendPresenceReceivedListener.onFriendPresenceReceived(accountId, status, sessionOptional);
                }));
    }

    @Override
    public void close() {
        chatResource.close();
        prodServiceXmppTcpConnection.removeAsyncStanzaListener(rosterPacketListener);
        prodServiceXmppTcpConnection.removeAsyncStanzaListener(presenceListener);
        pingManager.setPingInterval(-1);
        prodServiceXmppTcpConnection.disconnect();
    }

    @Override
    public ChatResource chat() {
        return chatResource;
    }

    @Override
    public XMPPTCPConnection prodService() {
        return prodServiceXmppTcpConnection;
    }

    /**
     * Used to create {@link FortniteXmpp} instances
     * Instantiated using {@link #newInstance(Fortnite)}
     */
    public static final class Builder {

        private final Fortnite fortnite;
        private OnChatMessageReceivedListener onChatMessageReceivedListener = (final var accountId, final var messageBody) -> {
        };
        private OnFriendsListReceivedListener onFriendsListReceivedListener = (final var accountIds) -> {
        };
        private OnFriendPresenceReceivedListener onFriendPresenceReceivedListener = (final var accountId, final var status, final var sessionOptional) -> {
        };
        private boolean debugXmppConnections = false;
        private Application application = Application.FORTNITE_CLIENT;
        private Platform platform = Platform.WINDOWS;

        private Builder(final Fortnite fortnite) {
            this.fortnite = fortnite;
        }

        /**
         * @param fortnite the {@link Fortnite} instance to get authentication session details from
         * @return a new {@link Builder} instance
         * @throws NullPointerException if {@code fortnite} is {@code null}
         */
        public static Builder newInstance(final Fortnite fortnite) {
            Objects.requireNonNull(fortnite, "fortnite cannot be null");
            return new Builder(fortnite);
        }

        /**
         * @param onChatMessageReceivedListener the {@link OnChatMessageReceivedListener} to call
         *                                      {@link OnChatMessageReceivedListener#onChatMessageReceived(String, String)}
         *                                      on when an incoming chat message is intercepted
         * @return the {@link Builder} instance this was called on
         * @throws NullPointerException if {@code onChatMessageReceivedListener} is {@code null}
         */
        public Builder setOnChatMessageReceivedListener(final OnChatMessageReceivedListener onChatMessageReceivedListener) {
            this.onChatMessageReceivedListener = Objects.requireNonNull(onChatMessageReceivedListener, "onChatMessageReceivedListener cannot be null");
            return this;
        }

        /**
         * @param onFriendsListReceivedListener the {@link OnFriendsListReceivedListener} to call
         *                                      {@link OnFriendsListReceivedListener#onFriendsListReceived(Set)}
         *                                      on when the authenticated user's friends list is received after log in
         * @return the {@link Builder} instance this was called on
         * @throws NullPointerException if {@code onFriendsListReceivedListener} is {@code null}
         */
        public Builder setOnFriendsListReceivedListener(final OnFriendsListReceivedListener onFriendsListReceivedListener) {
            this.onFriendsListReceivedListener = Objects.requireNonNull(onFriendsListReceivedListener, "onFriendsListReceivedListener cannot be null");
            return this;
        }

        /**
         * @param onFriendPresenceReceivedListener the {@link OnFriendPresenceReceivedListener} to call
         *                                         {@link OnFriendPresenceReceivedListener#onFriendPresenceReceived(String, Status, Optional)}
         *                                         on a friend's presence is received
         * @return the {@link Builder} instance this was called on
         * @throws NullPointerException if {@code onFriendPresenceReceivedListener} is {@code null}
         */
        public Builder setOnFriendPresenceReceivedListener(final OnFriendPresenceReceivedListener onFriendPresenceReceivedListener) {
            this.onFriendPresenceReceivedListener = Objects.requireNonNull(onFriendPresenceReceivedListener, "onFriendPresenceReceivedListener cannot be null");
            return this;
        }

        /**
         * @param debugXmppConnections whether or not to log XMPP connection traffic
         *                             {@code true} to log and {@code} to not log
         * @return the {@link Builder} instance this was called on
         */
        public Builder setDebugXmppConnections(final boolean debugXmppConnections) {
            this.debugXmppConnections = debugXmppConnections;
            return this;
        }

        /**
         * @param application depending on the value, the authenticated user will appear to
         *                    either be playing Fortnite or in the EpicGames Launcher to friends
         * @return the {@link Builder} instance this was called on
         * @throws NullPointerException if {@code application} is {@code null}
         */
        public Builder setApplication(final Application application) {
            this.application = Objects.requireNonNull(application, "application cannot be null");
            return this;
        }

        /**
         * @param platform the icon representing this will be shown to friends of the
         *                 authenticated user
         * @return the {@link Builder} instance this was called on
         * @throws NullPointerException if {@code platform} is {@code null}
         */
        public Builder setPlatform(final Platform platform) {
            this.platform = Objects.requireNonNull(platform, "platform cannot be null");
            return this;
        }

        /**
         * @return a new instance of {@link FortniteXmpp}
         * @throws IllegalStateException if there's a problem establishing any XMPP connections
         */
        public FortniteXmpp build() {
            try {
                return new DefaultFortniteXmpp(this);
            } catch (final InterruptedException | XMPPException | SmackException | IOException exception) {
                throw new IllegalStateException("Error occurred when establishing an XMPP connection", exception);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy