/******************************************************************************* * Copyright (c) 2014 Bert De Geyter (https://github.com/TheHolyWaffle). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * Bert De Geyter (https://github.com/TheHolyWaffle) ******************************************************************************/ package com.github.theholywaffle.lolchatapi; import android.content.Context; import android.util.Log; import com.github.theholywaffle.lolchatapi.listeners.ChatListener; import com.github.theholywaffle.lolchatapi.listeners.FriendListener; import com.github.theholywaffle.lolchatapi.wrapper.Friend; import com.github.theholywaffle.lolchatapi.wrapper.FriendGroup; import org.jivesoftware.smack.Chat; import org.jivesoftware.smack.ChatManager; import org.jivesoftware.smack.ChatManagerListener; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.MessageListener; import org.jivesoftware.smack.Roster; import org.jivesoftware.smack.Roster.SubscriptionMode; import org.jivesoftware.smack.RosterEntry; import org.jivesoftware.smack.RosterGroup; import org.jivesoftware.smack.RosterListener; import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.SmackAndroid; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPTCPConnection; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.sasl.SASLErrorException; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.spark.util.DummySSLSocketFactory; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; /** * This and all the files in the module have been developed by Bert De Geyter (https://github * .com/TheHolyWaffle) and are protected by the Apache GPLv3 license. */ public class LoLChat { private final XMPPTCPConnection connection; private final ArrayList<ChatListener> chatListeners = new ArrayList<>(); private final ArrayList<FriendListener> friendListeners = new ArrayList<>(); private boolean stop = false; private String status = ""; private Presence.Type type = Presence.Type.available; private Presence.Mode mode = Presence.Mode.chat; /** * Represents a single connection to a League of Legends chatserver. * * @param server The chatserver of the region you want to connect to * @param acceptFriendRequests True will automatically accept all friend requests. False will * ignore all friend requests. NOTE: automatic accepting of * requests causes the name of the new friend to be null. */ public LoLChat(Context context, String eventBroadcastString, ChatServer server, boolean acceptFriendRequests) throws IOException { Roster.setDefaultSubscriptionMode( acceptFriendRequests ? SubscriptionMode.accept_all : SubscriptionMode.manual); ConnectionConfiguration config = new ConnectionConfiguration(server.host, 5223, "pvp.net"); SASLAuthentication.supportSASLMechanism("PLAIN"); SASLAuthentication.supportSASLMechanism("KERBEROS_V4"); SASLAuthentication.supportSASLMechanism("GSSAPI"); SASLAuthentication.supportSASLMechanism("SKEY"); SASLAuthentication.supportSASLMechanism("EXTERNAL"); SASLAuthentication.supportSASLMechanism("CRAM-MD5"); SASLAuthentication.supportSASLMechanism("ANONYMOUS"); SASLAuthentication.supportSASLMechanism("OTP"); SASLAuthentication.supportSASLMechanism("GSS-SPNEGO"); SASLAuthentication.supportSASLMechanism("SECURID"); SASLAuthentication.supportSASLMechanism("NTLM"); SASLAuthentication.supportSASLMechanism("NMAS_LOGIN"); SASLAuthentication.supportSASLMechanism("NMAS_AUTHEN"); SASLAuthentication.supportSASLMechanism("DIGEST-MD5"); SASLAuthentication.supportSASLMechanism("9798-U-RSA-SHA1-ENC"); SASLAuthentication.supportSASLMechanism("9798-M-RSA-SHA1-ENC"); SASLAuthentication.supportSASLMechanism("9798-U-DSA-SHA1"); SASLAuthentication.supportSASLMechanism("9798-M-DSA-SHA1"); SASLAuthentication.supportSASLMechanism("9798-U-ECDSA-SHA1"); SASLAuthentication.supportSASLMechanism("9798-M-ECDSA-SHA1"); SASLAuthentication.supportSASLMechanism("KERBEROS_V5"); SASLAuthentication.supportSASLMechanism("NMAS-SAMBA-AUTH"); SASLAuthentication.supportSASLMechanism("SCRAM-*"); SASLAuthentication.supportSASLMechanism("SCRAM-SHA-1"); SASLAuthentication.supportSASLMechanism("SCRAM-SHA-1-PLUS"); SASLAuthentication.supportSASLMechanism("GS2-*"); SASLAuthentication.supportSASLMechanism("GS2-KRB5"); SASLAuthentication.supportSASLMechanism("GS2-KRB5-PLUS"); SASLAuthentication.supportSASLMechanism("SPNEGO"); SASLAuthentication.supportSASLMechanism("SPNEGO-PLUS"); SASLAuthentication.supportSASLMechanism("SAML20"); SASLAuthentication.supportSASLMechanism("OPENID20"); SASLAuthentication.supportSASLMechanism("EAP-AES128"); SASLAuthentication.supportSASLMechanism("EAP-AES128-PLUS"); config.setSecurityMode(ConnectionConfiguration.SecurityMode.enabled); config.setSocketFactory(new DummySSLSocketFactory(context, eventBroadcastString)); config.setCompressionEnabled(true); connection = new XMPPTCPConnection(config); try { connection.connect(); } catch (XMPPException | SmackException e) { Log.wtf(getClass().getName(), "Failed to connect to " + connection.getHost(), e); } addListeners(); new Thread(new Runnable() { @Override public void run() { while (!stop) { try { Thread.sleep(500); } catch (InterruptedException ignored) { } } } }).start(); } /** * Adds a ChatListener that listens to messages from all your friends. */ public void addChatListener(ChatListener chatListener) { chatListeners.add(chatListener); } /** * Adds a FriendListener that listens to changes from all your friends. Such * as logging in, starting games, ... */ public void addFriendListener(FriendListener friendListener) { friendListeners.add(friendListener); } private synchronized void addListeners() { connection.getRoster().addRosterListener(new RosterListener() { private HashMap<String, Presence.Type> typeUsers = new HashMap<>(); private HashMap<String, Presence.Mode> modeUsers = new HashMap<>(); private HashMap<String, String> statusUsers = new HashMap<>(); public void entriesAdded(Collection<String> e) { } public void entriesDeleted(Collection<String> e) { } public void entriesUpdated(Collection<String> e) { } public void presenceChanged(Presence p) { String from = p.getFrom(); if (from != null) { Friend friend = getFriendById(from); if (friend != null) { for (FriendListener l : friendListeners) { if (typeUsers.containsKey(from)) { Presence.Type previous = typeUsers.get(from); if (p.getType() == Presence.Type.available && previous != Presence.Type.available) { l.onFriendJoin(friend); } else if (p.getType() == Presence.Type.unavailable && previous != Presence.Type.unavailable) { l.onFriendLeave(friend); } } else if (p.getType() == Presence.Type.available) { l.onFriendJoin(friend); } if (modeUsers.containsKey(from)) { Presence.Mode previous = modeUsers.get(from); if (p.getMode() == Presence.Mode.chat && previous != Presence.Mode.chat) { l.onFriendAvailable(friend); } else if (p.getMode() == Presence.Mode.away && previous != Presence.Mode.away) { l.onFriendAway(friend); } else if (p.getMode() == Presence.Mode.dnd && previous != Presence.Mode.dnd) { l.onFriendBusy(friend); } } if (statusUsers.containsKey(from)) { String previous = statusUsers.get(from); if (!p.getStatus().equals(previous)) { l.onFriendStatusChange(friend); } } typeUsers.put(from, p.getType()); modeUsers.put(from, p.getMode()); statusUsers.put(from, p.getStatus()); } } } } }); ChatManager.getInstanceFor(connection).addChatListener(new ChatManagerListener() { @Override public void chatCreated(Chat c, boolean locally) { final Friend friend = getFriendById(c.getParticipant()); if (friend != null) { c.addMessageListener(new MessageListener() { @Override public void processMessage(Chat chat, Message msg) { for (ChatListener c : chatListeners) { if (msg.getType() == Message.Type.chat) { c.onMessage(friend, msg.getBody()); } } } }); } else { Log.wtf(((LoLChat) LoLChat.this).getClass().getName(), "Friend is null in chat creation"); } } }); } /** * Disconnects from chatserver and releases all resources. */ public void disconnect() throws SmackException.NotConnectedException { connection.disconnect(); stop = true; } public static SmackAndroid init(Context context) { return SmackAndroid.init(context); } /** * @return default FriendGroup */ public FriendGroup getDefaultFriendGroup() { return getFriendGroupByName("**Default"); } /** * Gets your friend based on his XMPPAddress * * @param xmppAddress For example sum12345678@pvp.net * @return The corresponding Friend or null if user is not found or he is * not a friend of you */ public Friend getFriendById(String xmppAddress) { return new Friend(this, connection, connection.getRoster().getEntry(StringUtils.parseBareAddress(xmppAddress))); } /** * Gets a friend based on his name. The name is case insensitive. * * @param name The name of your friend, for example "Dyrus" * @return The corresponding Friend object or null if user is not found or * he is not a friend of you */ public Friend getFriendByName(String name) { for (Friend f : getFriends()) { if (f.getName().equalsIgnoreCase(name)) { return f; } } return null; } /** * Get a FriendGroup by name, for example "Duo Partners". The name is case * sensitive! * * @param name The name of your group * @return The corresponding FriendGroup or null if not found */ public FriendGroup getFriendGroupByName(String name) { RosterGroup g = connection.getRoster().getGroup(name); if (g != null) { return new FriendGroup(this, connection, g); } return null; } /** * Get all your FriendGroups * * @return A List of all your FriendGroups */ public List<FriendGroup> getFriendGroups() { ArrayList<FriendGroup> groups = new ArrayList<>(); for (RosterGroup g : connection.getRoster().getGroups()) { groups.add(new FriendGroup(this, connection, g)); } return groups; } /** * Get all your friends, both online and offline * * @return A List of all your Friends */ public List<Friend> getFriends() { ArrayList<Friend> friends = new ArrayList<>(); for (RosterEntry e : connection.getRoster().getEntries()) { friends.add(new Friend(this, connection, e)); } return friends; } /** * Get all your friends who are offline. * * @return A list of all your offline Friends */ public List<Friend> getOfflineFriends() { List<Friend> f = getFriends(); Iterator<Friend> i = f.iterator(); while (i.hasNext()) { Friend friend = i.next(); if (friend.isOnline()) { i.remove(); } } return f; } /** * Get all your friends who are online. * * @return A list of all your online Friends */ public List<Friend> getOnlineFriends() { List<Friend> f = getFriends(); Iterator<Friend> i = f.iterator(); while (i.hasNext()) { Friend friend = i.next(); if (!friend.isOnline()) { i.remove(); } } return f; } /** * Logs in to the chat server without replacing the official connection of * the League of Legends client. This call is asynchronous. * * @return true if login is successful, otherwise false */ public boolean login(String username, String password) throws IOException { return login(username, password, false); } /** * Logs in to the chat server. This call is asynchronous. * * @param replaceLeague True will disconnect you account from the League of Legends * client. False allows you to have another connection next to * the official connection in the League of Legends client. * @return True if login was successful */ public boolean login(String username, String password, boolean replaceLeague) throws IOException { connection.setPacketReplyTimeout(60000); try { if (replaceLeague) { connection.login(username, "AIR_" + password, "xiff"); } else { connection.login(username, "AIR_" + password); } } catch (SASLErrorException e) { return Boolean.FALSE; //Wrong credentials } catch (XMPPException | SmackException e) { Log.wtf(getClass().getName(), e); } return connection.isAuthenticated(); } /** * Removes the ChatListener from the list and will no longer be called. */ public void removeChatListener(ChatListener chatListener) { chatListeners.remove(chatListener); } /** * Removes the FriendListener from the list and will no longer be called. */ public void removeFriendListener(FriendListener friendListener) { friendListeners.remove(friendListener); } /** * Changes your ChatMode (e.g. ingame, away, available) * * @see com.github.theholywaffle.lolchatapi.ChatMode */ public void setChatMode(ChatMode chatMode) { this.mode = chatMode.mode; updateStatus(); } /** * Change your appearance to offline. */ public void setOffline() { this.type = Presence.Type.unavailable; updateStatus(); } /** * Change your appearance to online. */ public void setOnline() { this.type = Presence.Type.available; updateStatus(); } /** * Update your own status with current level, ranked wins... * <p/> * Create an Status object (without constructor arguments) and call the * several ".set" methods on it to customise it. Finally pass this Status * object back to this method * * @param status Your custom Status object * @see LolStatus */ public void setStatus(LolStatus status) { this.status = status.toString(); updateStatus(); } private void updateStatus() { Presence newPresence = new Presence(type, status, 1, mode); try { connection.sendPacket(newPresence); } catch (SmackException.NotConnectedException e) { Log.wtf(getClass().getName(), "e"); } } public void reloadRoster() { try { connection.getRoster().reload(); } catch (SmackException.NotLoggedInException | SmackException.NotConnectedException e) { Log.wtf(getClass().getName(), e); } } }