public interface AuthStorage {
	public void setCredentials(Credentials credentials);
	public Credentials getCredentials();
} public class Credentials {
	public String username;
	public String password;
	public String steamguard_token;

	public Credentials(String username, String password, String steamguard_token) {
		this.username = username;
		this.password = password;
		this.steamguard_token = steamguard_token;
	}

	public Credentials() {
		this(null, null, null);
	}
} public class FlatFileAuthStorage extends ReaderWriterAuthStorage {
	private File file;
	public FlatFileAuthStorage(File file) {
		this.file = file;
	}

	@Override
	public void setCredentials(Credentials credentials) {
		try {
			this.writeCredentialsTo(credentials, new FileWriter(file));
		} catch(Exception e) {
			System.out.println("Writing authentication file failed because:");
			e.printStackTrace();
		}
	}

	@Override
	public Credentials getCredentials() {
		try {
			return this.readCredentialsFrom(new FileReader(file));
		} catch(FileNotFoundException e) {
			//If we don't find the file, just fall through and return null
		} catch(Exception e) {
			System.out.println("Reading authentication file failed because:");
			e.printStackTrace();
		}
		return null;
	}
} public class MessageHandler {
	private final SteamConnection connection;
	private final DefaultMessageListener defaultMessageListener;

	private String lastMessageID;

	private boolean isLoggedOn = false;

	public MessageHandler(SteamConnection connection) {
		this.connection = connection;
		this.defaultMessageListener = new DefaultMessageListener();
		registerListener(this.defaultMessageListener);
	}

	public void logon() throws RequestException {
		if(isLoggedOn) return;
		Logon msg = new Logon(connection);
		lastMessageID = msg.send().get("message").toString();
		isLoggedOn = true;
	}
	
	public void logoff() throws RequestException {
		if(!isLoggedOn) return;
		Logoff msg = new Logoff(connection);
		msg.send();
		isLoggedOn = false;
	}
	
	public void sendMessage(Message message) throws RequestException {
		if(!isLoggedOn) return;
		de.doridian.steammobile.methods.api.ISteamWebUserPresenceOAuth.Message msg = new de.doridian.steammobile.methods.api.ISteamWebUserPresenceOAuth.Message(connection);
		msg.setMessage(message);
		msg.send();
	}

	class DefaultMessageListener implements MessageListener {
		@MessageListener.Handler
		public void onPersonaStateChanged(PersonaStateMessage msg) {
			Friend friend;
			if(!connection.friends.containsKey(msg.steamid_other)) {
				return;
			} else {
				friend = connection.friends.get(msg.steamid_other);
			}
			friend.personaname = msg.persona_name;
			friend.personastate = msg.persona_state;
		}

		@MessageListener.Handler
		public void onPersonaRelationshipChanged(PersonaRelationshipMessage msg) {
			Friend friend;
			if(!connection.friends.containsKey(msg.steamid_other)) {
				friend = new Friend(connection, msg.steamid_other, ""+msg.persona_state, System.currentTimeMillis() / 1000);
				connection.friends.put(msg.steamid_other, friend);
			} else {
				friend = connection.friends.get(msg.steamid_other);
			}
			friend.relationship = ""+msg.persona_state;
			friend.personaname = msg.persona_name;
		}
	}

	private int mstimeout = 5000;
	private boolean isThreaded = false;
	private boolean isThreadPollable = false;
	private List<Message> messageQueue = new ArrayList<Message>();
	private final Object threadLock = new Object();

	private static class MessageListenerMethod {
		private Method method;
		private MessageListener listener;
		private MessageListenerMethod(MessageListener listener, Method method) {
			this.method = method;
			this.listener = listener;
		}
	}
	private final HashSet<MessageListenerMethod> globalListeners = new HashSet<MessageListenerMethod>();
	private final HashMap<Class<? extends Message>, HashSet<MessageListenerMethod>> messageListeners = new HashMap<Class<? extends Message>, HashSet<MessageListenerMethod>>();

	public void registerListener(MessageListener listener) {
		Method[] methods = listener.getClass().getMethods();
		for(Method method : methods) {
			if(!method.isAnnotationPresent(MessageListener.Handler.class)) continue;
			Class[] params = method.getParameterTypes();
			if(params.length != 1) continue;
			Class paramOne = params[0];
			if(!Message.class.isAssignableFrom(paramOne)) continue;
			Class<? extends Message> forMessage = (Class<? extends Message>)paramOne;

			try {
				method.setAccessible(true); //So we can use internal classes, thanks reflection!
			} catch(Exception e) { }
			if(!method.isAccessible()) continue; //We failed to make it accessible, so ditch it!

			HashSet<MessageListenerMethod> list;
			if(forMessage.equals(Message.class)) {
				list = globalListeners;
			} else if(messageListeners.containsKey(forMessage)) {
				list = messageListeners.get(forMessage);
			} else {
				list = new HashSet<MessageListenerMethod>();
				messageListeners.put(forMessage, list);
			}
			list.add(new MessageListenerMethod(listener, method));
		}
	}

	public List<Message> getMessages() throws RequestException {
		if(!isLoggedOn) return null;
		if(isThreaded) {
			if(isThreadPollable) {
				List<Message> queue;
				synchronized(threadLock) {
					queue = messageQueue;
					messageQueue = new ArrayList<Message>();
				}
				return queue;
			} else {
				return null;
			}
		} else {
			return __getMessages();
		}
	}

	public void stopGetMessagesLoop() {
		isThreaded = false;
	}

	public void startGetMessagesLoop() {
		startGetMessagesLoop(false);
	}

	//usePolling == true means you still want to use getMessages just without it being blocking
	//usePolling == false means you want to use the event based system (causing getMessages() to ALWAYS return null!)
	public void startGetMessagesLoop(final boolean usePolling) {
		isThreaded = true;
		isThreadPollable = usePolling;
		new Thread() {
			@Override
			public void run() {
				while(isThreaded) {
					try {
						if(isLoggedOn) {
							List<Message> msgs = __getMessages();
							if(usePolling) {
								synchronized(threadLock) {
									messageQueue.addAll(msgs);
								}
							} else {
								for(Message msg : msgs) {
									//Global listeners
									for(MessageListenerMethod item : globalListeners) {
										item.method.invoke(item.listener, msg);
									}

									//Specific listeners
									Class<? extends Message> clazz = msg.getClass();
									if(!messageListeners.containsKey(clazz)) continue;
									HashSet<MessageListenerMethod> methods = messageListeners.get(clazz);
									for(MessageListenerMethod item : methods) {
										item.method.invoke(item.listener, msg);
									}
								}
							}
						}
						Thread.sleep(mstimeout);
					} catch(Exception e) {
						e.printStackTrace();
					}
				}
				synchronized (threadLock) {
					messageQueue.clear();
				}
			}
		}.start();
	}

	private List<Message> __getMessages() throws RequestException {
		Poll msg = new Poll(connection);
		msg.setLastMessageID(lastMessageID);
		List<Message> ret = new ArrayList<Message>();
		try {
			JSONObject obj = msg.send();

			if(obj.containsKey("messagelast")) {
				lastMessageID = obj.get("messagelast").toString();
			}

			if(!obj.containsKey("messages")) return ret;

			JSONArray arr = (JSONArray)obj.get("messages");
			for(Object ent : arr) {
				JSONObject entj = (JSONObject)ent;
				Message recvd = Message.craftMessageFromJSON(entj);
				if(recvd != null) {
					ret.add(recvd);
				} else {
					System.out.println("Unknown message: " + entj.toJSONString());
				}
			}

			if(obj.containsKey("sectimeout")) {
				mstimeout = Integer.valueOf(obj.get("sectimeout").toString()) * 1000;
				if(mstimeout > 5000) mstimeout = 5000;
			}

			return ret;
		} catch(RequestException e) {
			if(e.error.equalsIgnoreCase("Timeout")) {
				return ret;
			}
			throw e;
		}
	}
} public interface MessageListener {
	@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Handler { }
} public class SteamConnection {
	private String steamid;
	private String access_token;
	private String umqid;

	public static final Random random = new Random();

	public Map<String, Friend> friends = new HashMap<String, Friend>();
	public Map<String, Group> groups = new HashMap<String, Group>();

	public SteamConnection() {
		long x = random.nextLong();
		if(x < 0) x = -x;
		umqid = String.valueOf(x);
	}

	public void loadFriendList() throws RequestException {
		GetFriendList msg = new GetFriendList(this);
		try {
			JSONObject ret = msg.send();
			JSONArray arr = (JSONArray)ret.get("friends");
			friends.clear();
			for(Object ent : arr) {
				JSONObject entj = (JSONObject)ent;
				Friend friend = new Friend(this, entj.get("steamid").toString(), entj.get("relationship").toString(), Long.valueOf(entj.get("friend_since").toString()));
				friends.put(friend.steamid, friend);
			}
		} catch(RequestException e) {
			throw e;
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

	public void loadGroupList() throws RequestException {
		GetGroupList msg = new GetGroupList(this);
		try {
			JSONObject ret = msg.send();
			JSONArray arr = (JSONArray)ret.get("groups");
			groups.clear();
			for(Object ent : arr) {
				JSONObject entj = (JSONObject)ent;
				Group group = new Group(this, entj.get("steamid").toString());
				groups.put(group.steamid, group);
			}
		} catch(RequestException e) {
			throw e;
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

	private static final int MAX_DETAILS_PER_REQUEST = 90;

	public void loadFriendDetails() throws RequestException {
		GetUserSummaries msg = new GetUserSummaries(this);
		try {
			Set<String> tsids = friends.keySet();
			String[] sids = tsids.toArray(new String[tsids.size()]);
			for(int i = 0; i < sids.length; i += MAX_DETAILS_PER_REQUEST) {
				tsids = new HashSet<String>();
				for(int j = 0; j < MAX_DETAILS_PER_REQUEST; j++) {
					int k = i + j;
					if(k >= sids.length) break;
					tsids.add(sids[k]);
				}
				msg.setSteamIDs(tsids);
				JSONArray res = (JSONArray)msg.send().get("players");
				for(Object ent : res) {
					JSONObject entj = (JSONObject)ent;
					friends.get(entj.get("steamid").toString()).setFullStats(entj);
				}
			}
		} catch(RequestException e) {
			throw e;
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

	public void loadGroupDetails() throws RequestException {
		GetGroupSummaries msg = new GetGroupSummaries(this);
		try {
			Set<String> tsids = groups.keySet();
			String[] sids = tsids.toArray(new String[tsids.size()]);
			for(int i = 0; i < sids.length; i += MAX_DETAILS_PER_REQUEST) {
				tsids = new HashSet<String>();
				for(int j = 0; j < MAX_DETAILS_PER_REQUEST; j++) {
					int k = i + j;
					if(k >= sids.length) break;
					tsids.add(sids[k]);
				}
				msg.setSteamIDs(tsids);
				JSONArray res = (JSONArray)msg.send().get("groups");
				for(Object ent : res) {
					JSONObject entj = (JSONObject)ent;
					groups.get(entj.get("steamid").toString()).setFullStats(entj);
				}
			}
		} catch(RequestException e) {
			throw e;
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

	public void tryLogin(Credentials credentials) throws LoginException {
		GetTokenWithCredentials request = new GetTokenWithCredentials(credentials.username, credentials.password, credentials.steamguard_token);

		JSONObject ret;
		try {
			ret = request.send();
			if(ret == null) return;
		} catch(RequestException e) {
			if(e.errorCode.equalsIgnoreCase("invalid_steamguard_code")) {
				throw new InvalidSteamguardTokenException();
			} else if(e.errorCode.equalsIgnoreCase("steamguard_code_required")) {
				throw new RequireSteamguardTokenException();
			} else {
				throw new LoginException(e.errorCode);
			}
		}

		steamid = ret.get("x_steamid").toString();
		access_token = ret.get("access_token").toString();
	}

	public void tryLogin(AuthStorage storage) throws LoginException {
		Credentials credentials = storage.getCredentials();
		if(credentials == null) {
			throw new LoginException("credentials_in_storage_null");
		}
		tryLogin(credentials, storage);
	}

	public void tryLogin(Credentials credentials, AuthStorage storage) throws LoginException {
		tryLogin(credentials);
		storage.setCredentials(credentials);
	}

	public void addUmqid(BaseAPIMethod method) {
		method.setUmqid(umqid);
	}

	public void addAuthData(BaseMethod method, boolean addSteamid, boolean addToken) {
		if(addSteamid)
			method.setSteamID(steamid);
		if(addToken)
			method.setAccessToken(access_token);
	}
} public class WebConnection {
	private String sessionid;

	private final SteamConnection connection;

	public WebConnection(SteamConnection connection) {
		this.connection = connection;
	}

	public void login() {
		GetManifest msg = new GetManifest(connection);
		try {
			JSONObject response = msg.send();
			String sid = response.get("cookie").toString();
			int x = sid.indexOf("sessionid=") + "sessionid=".length();
			sessionid = sid.substring(x, sid.indexOf(';', x));
		} catch(RequestException e) {
			e.printStackTrace();
		}
	}

	public void addSessionID(BaseWebMethod method) {
		method.setSessionID(sessionid);
	}

	public void addAuthData(BaseMethod method, boolean addSteamid, boolean addToken) {
		connection.addAuthData(method, addSteamid, addToken);
	}
} public class InvalidSteamguardTokenException extends LoginException {
	public InvalidSteamguardTokenException() {
		super("Invalid steam guard token");
	}
} public class LoginException extends Exception {
	public LoginException(String msg) {
		super(msg);
	}
} public class RequireSteamguardTokenException extends LoginException {
	public RequireSteamguardTokenException() {
		super("Invalid steam guard token");
	}
} public class Friend {
	public enum State {
		OFFLINE(0),
		ONLINE(1),
		BUSY(2),
		AWAY(3),
		SNOOZE(4),
		INVALID(-1);

		int sid;
		State(int id) {
			sid = id;
		}

		public static State getByID(int id) {
			for(State s : State.values()) {
				if(s.sid == id) return s;
			}
			return State.INVALID;
		}
		
		public int getID() {
			return sid;
		}
	}

	private final SteamConnection connection;

	//Filled always
	public final String steamid;
	public String relationship;
	public long friend_since;

	//Only filled by getFullStats
	public Avatar avatar = null;

	public String personaname = null;
	public String profileurl = null;
	
	public State personastate = null;
	
	public Group primarygroup = null;
	public long timecreated = 0;

	public HashMap<String, Object> infos = new HashMap<String, Object>();
	
	public Friend(SteamConnection connection, String steamid, String relationship, long friend_since) {
		this.steamid = steamid;
		this.relationship = relationship;
		this.friend_since = friend_since;
		this.connection = connection;
	}

	public void getFullStats() throws RequestException {
		getFullStats(false);
	}

	public void getFullStats(boolean force) throws RequestException {
		if(personaname != null && !force) {
			return;
		}

		GetGroupSummaries msg = new GetGroupSummaries(connection);
		msg.setSteamIDs(this.steamid);
		setFullStats((JSONObject)(((JSONArray)msg.send().get("players")).get(0)));
	}

	public void setFullStats(JSONObject friend) {
		personaname = friend.get("personaname").toString();
		if(friend.containsKey("personastate")) {
			personastate = State.getByID(Integer.valueOf(friend.get("personastate").toString()));
		}

		if(friend.containsKey("profileurl")) {
			profileurl = friend.get("profileurl").toString();
		}

		if(friend.containsKey("avatar")) {
			avatar = new Avatar(friend);
		}

		if(friend.containsKey("primaryclanid")) {
			primarygroup = new Group(connection, friend.get("primaryclanid").toString());
		}
		if(friend.containsKey("timecreated")) {
			timecreated = Long.valueOf(friend.get("timecreated").toString());
		}

		infos.clear();
		for(Map.Entry<Object, Object> ent : (Set<Map.Entry<Object, Object>>)friend.entrySet()) {
			infos.put(ent.getKey().toString(), ent.getValue());
		}
	}
} public class Group {
	private final SteamConnection connection;

	public final String steamid;

	public String name;
	public Avatar avatar;
	
	public HashMap<String, Object> infos = new HashMap<String, Object>();

	public Group(SteamConnection connection, String gid) {
		steamid = gid;
		this.connection = connection;
	}

	public void getFullStats() throws RequestException {
			getFullStats(false);
	}

	public void getFullStats(boolean force) throws RequestException {
		if(name != null && !force) {
			return;
		}

		GetGroupSummaries msg = new GetGroupSummaries(connection);
		msg.setSteamIDs(this.steamid);
		setFullStats((JSONObject)(((JSONArray)msg.send().get("groups")).get(0)));
	}

	public void setFullStats(JSONObject group) {
		if(group.containsKey("avatar")) {
			avatar = new Avatar(group);
		}

		name = group.get("name").toString();

		infos.clear();
		for(Map.Entry<Object, Object> ent : (Set<Map.Entry<Object, Object>>)group.entrySet()) {
			infos.put(ent.getKey().toString(), ent.getValue());
		}
	}
} public abstract class Message {
	@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) protected @interface Type { String value(); }

	public final long timestamp;
	public final String steamid_other;

	public Message(JSONObject json) {
		this.steamid_other = json.get("steamid_from").toString();
		this.timestamp = Long.valueOf(json.get("timestamp").toString());
	}

	public Message(String steamid_other) {
		this.steamid_other = steamid_other;
		this.timestamp = System.currentTimeMillis() / 1000;
	}

	public void addParameters(Map<String, String> params) { }

	public final String getType() {
		return this.getClass().getAnnotation(Type.class).value();
	}
	
	//Methods to get the classes by type etc..
	private static Map<String, Class<? extends Message>> messages;

	static {
		messages = new HashMap<String, Class<? extends Message>>();

		addMessage(TextMessage.class);
		addMessage(EmoteMessage.class);

		addMessage(MyEmoteMessage.class);
		addMessage(MyTextMessage.class);

		addMessage(PersonaStateMessage.class);
		addMessage(PersonaRelationshipMessage.class);
		addMessage(TypingMessage.class);
		addMessage(LeftConversationMessage.class);
	}
	
	private static void addMessage(Class<? extends Message> clazz) {
		if(!clazz.isAnnotationPresent(Type.class)) return;
		messages.put(clazz.getAnnotation(Type.class).value().toLowerCase(), clazz);
	}
	
	public static Message craftMessageFromJSON(JSONObject json) {
		try {
			String type = json.get("type").toString().toLowerCase();
			if(!messages.containsKey(type)) {
				return null;
			}
			Class<? extends Message> clazz = messages.get(type);
			Constructor<? extends Message> ctor = clazz.getConstructor(JSONObject.class);
			return ctor.newInstance(json);
		} catch(Exception e) {
			return null;
		}
	}
} @Message.Type("saytext")
public class TextMessage extends Message {
	public final String text;

	public TextMessage(JSONObject json) {
		super(json);
		this.text = json.get("text").toString();
	}

	public TextMessage(String steamid_other, String text) {
		super(steamid_other);
		this.text = text;
	}

	@Override
	public void addParameters(Map<String, String> params) {
		params.put("text", text);
	}
} public class RequestException extends Exception {
	public RequestException(String error, String errorCode, String message) {
		super(message);
		this.error = error;
		this.errorCode = errorCode;
	}
	
	public final String error;
	public final String errorCode;
}
import de.doridian.steammobile.auth.base.AuthStorage;
import de.doridian.steammobile.auth.Credentials;
import de.doridian.steammobile.auth.impl.FlatFileAuthStorage;
import de.doridian.steammobile.connection.MessageHandler;
import de.doridian.steammobile.connection.MessageListener;
import de.doridian.steammobile.connection.SteamConnection;
import de.doridian.steammobile.connection.WebConnection;
import de.doridian.steammobile.connection.exceptions.InvalidSteamguardTokenException;
import de.doridian.steammobile.connection.exceptions.LoginException;
import de.doridian.steammobile.connection.exceptions.RequireSteamguardTokenException;
import de.doridian.steammobile.friend.Friend;
import de.doridian.steammobile.friend.Group;
import de.doridian.steammobile.messages.Message;
import de.doridian.steammobile.messages.TextMessage;
import de.doridian.steammobile.methods.RequestException;
import java.io.File;
import java.util.Scanner;
package de.doridian.steammobile.test;



public class Main {
	public static final Scanner input = new Scanner(System.in);

	private static final String AUTHFILE = "auth.cfg";

	public static SteamConnection connection;
	public static MessageHandler handler;
	public static WebConnection webConnection;

	public static boolean isRunning = true;
	
	public static void main(String[] args) {
		connect();
	}

	private static void tryLogin(Credentials credentials, AuthStorage storage) {