package com.geel9.facepunch; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.StringBufferInputStream; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.jsoup.Jsoup; import org.jsoup.select.Elements; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.util.Log; import android.widget.Toast; import com.crittercism.app.Crittercism; import com.geel9.facepunch.R; import com.geel9.facepunch.activities.FPActivity; import com.geel9.facepunch.misc.FPPostSection; public class APISession { // State public String username; protected String hashedPassword; public int bb_userid = -1; public boolean loggedIn = false; public String sessionID = "0", lastPost, lastThread, postHash, postStartTime; public boolean loggedIn() { return loggedIn; } public int userId() { return bb_userid; } public String username() { return username; } public String password(){ return hashedPassword; } public enum ErrorResult{ NOERROR, NOAUTH, WRONGACTION } /** * The possible return code for logging in * * @author Geel9 */ public enum LoginResult{ SUCCESS, FAILED_WRONGPASSWORD, FAILED_FAILLIMIT, FAILED_NOSOURCE, FAILED_BADMD5 } /** * The user ranks on Facepunch. * * @author Overv */ public enum Rank { BANNED, REGULAR, GOLD, MODERATOR } /** * The available post ratings. * * @author Overv */ public enum Rating { AGREE( "Agree", 1, 1 ), DISAGREE( "Disagree", 2, 2 ), FUNNY( "Funny", 3, 3 ), WINNER( "Winner", 18, 4 ), ZING( "Zing", 13, 5 ), INFORMATIVE( "Informative", 4, 6 ), FRIENDLY( "Friendly", 5, 7 ), USEFUL( "Useful", 6, 8 ), OPTIMISTIC( "Optimistic", 7, 9 ), ARTISTIC( "Artistic", 8, 10 ), LATE( "Late", 9, 11 ), DUMB( "Dumb", 12, 12 ), PROGRAMMINGKING( "Programming King", 14, 8 ), MAPPINGKING( "Mapping King", 17, 8 ), LUAKING( "Lua King", 16, 8 ), LUAHELPER( "Lua Helper", 19, 8 ), SMARKED( "Smarked", 15, 8 ), MOUSTACHE( "Moustache", 21, 8 ); String ratingName; int ratingId; int ratingOrder; Rating( String name, int id, int order) { ratingName = name; ratingId = id; ratingOrder = order; } @Override public String toString() { return ratingName; } public int ID(){ return ratingId; } public int Order(){ return ratingOrder; } public static Rating fromString( String htmlName ) { for( Rating r : values() ) if( r.name().equalsIgnoreCase( htmlName ) ) return r; return null; } public static LinkedHashMap<Rating, String> SortRatings(LinkedHashMap<Rating, String> ratings){ LinkedHashMap<Rating, String> ret = new LinkedHashMap<Rating, String>(); List<Rating> keys = new ArrayList(ratings.keySet()); Collections.sort(keys, new Comparator<Rating>() { public int compare(Rating r1, Rating r2) { return r1.Order() - r2.Order(); } }); for(Rating r : keys){ ret.put(r, ratings.get(r)); } return ret; } } public class RatingPair implements Comparable{ public Rating rating; public int numRatings; public RatingPair() {} public int compareTo(Object arg0) { RatingPair rp = (RatingPair)arg0; return rp.numRatings - numRatings; } } /** * Class that holds information about a specific user on Facepunch. * The actual amount of available information depends on the function that returned the structure. * * @author Overv */ public class FPUser { private FPUser() {} private int id; private String name; private String name_html; public String userTitle = ""; public Rank rank = Rank.REGULAR; private boolean online; private int joinYear; private String joinMonth; private int postCount; public int getId() { return id; } public String getName() { return name; } public Rank getRank() { return rank; } public String getStyledName() { return name_html; } public boolean isOnline() { return online; } public int getJoinYear() { return joinYear; } public String getJoinMonth() { return joinMonth; } public int getPostCount() { return postCount; } } /** * Class that holds information about a specific thread on Facepunch. * * @author Overv */ public class FPThread { private FPThread() {} private int id; private String icon, title, lastAuthor, lastAuthorTime; private FPUser author; private boolean read, locked, sticky; private int readers; private int pages; private int posts, views, newPosts; public int getId() { return id; } public String getIcon() { return icon; } public String getTitle() { return title; } public String getLastAuthor(){ return lastAuthor; } public String getLastAuthorTime(){ return lastAuthorTime; } public FPUser getAuthor() { return author; } public boolean isRead() { return read; } public boolean isLocked() { return locked; } public boolean isSticky() { return sticky; } public int readerCount() { return readers; } public int pageCount() { return pages; } public int postCount() { return posts; } public int viewCount() { return views; } public int unreadPostCount() { return newPosts; } } /** * Class used to store PMs. * * @author Geel9 */ public class FPPM{ private FPPM(){} private int id; private String title, sender, senderTime, senderDate, icon; private boolean read; public int getId(){ return id; } public String getTitle(){ return title; } public String getAuthor(){ return sender; } public String getDate(){ return senderDate; } public String getTime(){ return senderTime; } public String getIcon(){ return icon; } public boolean getRead(){ return read; } } /** * Class that holds information about a specific post on Facepunch. * * @author Overv */ public class FPPost { private FPPost() {} private int id; private String date; private String title; private FPUser author; private boolean isNew = false; private String message; public ArrayList<FPPostSection> sections = new ArrayList<FPPostSection>(); private String os, browser, flagdog, BBCode; public HashMap<Rating, Integer> ratings = new HashMap<Rating, Integer>(); public LinkedHashMap<Rating, String> ratingCodes = new LinkedHashMap<Rating, String>(); public int getId() { return id; } public String getDate() { return date; } public FPUser getAuthor() { return author; } public String getMessageHTML() { return message; } public String getTitle(){ return title; } public String getBBCode() { return BBCode; } public String getOperatingSystem() { return os; } public String getBrowser() { return browser; } public boolean getNew() { return isNew; } public int getRatingCount( Rating rating ) { return ratings.containsKey( rating ) ? ratings.get( rating ) : 0; } public void loadRatingKeys(JSONObject ratingKeys){ for(int i = 0; i < Rating.values().length; i++){ Rating rating = Rating.values()[i]; String ratingName = rating.toString().toLowerCase().replace(" ","_"); String key = ratingKeys.optString(ratingName); if(!key.equals("")){ ratingCodes.put(rating, key); } ratingCodes = Rating.SortRatings(ratingCodes); } } public void loadRatingNums(JSONObject ratings){ for(int i = 0; i < Rating.values().length; i++){ Rating rating = Rating.values()[i]; String ratingName = rating.toString().toLowerCase().replace(" ","_"); int ratingNum = ratings.optInt(ratingName); if(ratingNum != 0){ this.ratings.put(rating, ratingNum); } } } } /** * Classes for managing frontpage forums * * @author Overv */ public class Forum { public Forum() {} private int id; private String name; public int getId() { return id; } public String getName() { return name; } private ArrayList<Forum> subforums = new ArrayList<Forum>(); public Forum[] getSubForums(){ Forum[] arr = new Forum[subforums.size()]; subforums.toArray( arr ); return arr; } public String[] getSubForumsString(){ ArrayList<String> ret = new ArrayList<String>(); for(Forum f : subforums){ ret.add(String.valueOf(f.id) + "\n" + f.name); } String[] ret2 = new String[ret.size()]; ret.toArray(ret2); return ret2; } public ArrayList<Forum> loadSubForums(String[] subs){ ArrayList<Forum> subforums2 = new ArrayList<Forum>(); for(String forum : subs){ String[] contents = forum.split("\n"); Forum f = new Forum(); f.id = Integer.parseInt(contents[0]); f.name = contents[1]; subforums2.add(f); } return subforums2; } } public class Category { private Category() {} private int id; private String name; private ArrayList<Forum> forums = new ArrayList<Forum>(); public int getId() { return id; } public String getName() { return name; } public Forum[] getForums() { Forum[] arr = new Forum[forums.size()]; forums.toArray( arr ); return arr; } } public ErrorResult checkErrors(JSONObject error){ if(error == null) return ErrorResult.WRONGACTION; String errorCode = error.optString("error"); if(errorCode == null) return ErrorResult.NOERROR; else if(errorCode.contains("No session for that")) return ErrorResult.NOAUTH; else if(errorCode.contains("Invalid rating type")) return ErrorResult.WRONGACTION; return ErrorResult.NOERROR; } // Authentication public interface LoginCallback { public void onResult( LoginResult returnCode ); } /** * Sign in as the specified user within this session. * * @author Geel9 * * @param username Username to sign in with. * @param password Associated passsword. * @param callback Function to pass the result to. */ public void login( String username, String password, final LoginCallback callback ) { final String pwdHash = MD5( password ); if(pwdHash == null){ callback.onResult(LoginResult.FAILED_BADMD5); return; } this.username = username; asyncAPIRequest( "action=authenticate&username=" + username + "&password=" + pwdHash, "", true, new WebRequestCallback() { public void onResult( String ret, JSONObject json ) { if(json == null) { callback.onResult(LoginResult.FAILED_NOSOURCE); return; } //Will equal nologin if there is no login field, which only occurs when there is an error. String login = json.optString("login", "nologin"); if(login.equals("nologin")){ //Generic catch-all for right now. //Replace with all possible responses later. String error = json.optString("error"); callback.onResult(LoginResult.FAILED_WRONGPASSWORD); return; } bb_userid = json.optInt("userid"); hashedPassword = pwdHash; sessionID = json.optString("session_id"); loggedIn = true; callback.onResult(LoginResult.SUCCESS); } } ); } // Logging out public interface LogoutCallback { public void onResult( boolean success ); } /** * Sign out as the currently logged in user. * * @author Overv * * @param callback Function to pass the result to. */ public void logout( final LogoutCallback callback ) { loggedIn = false; username = ""; hashedPassword = ""; sessionID = "0"; callback.onResult(true); } public interface GetPMCallback { public void onResult(boolean success, FPPM[] pms); } /** * Retrieve the PMs of a user * * @author Geel9 */ public void listPMs(final FPActivity caller, int page, final GetPMCallback callback){ } public interface ReplyPMCallback{ public void onResult(boolean success); } public void ReplyPM(int id, String title, String recipients, String message, final ReplyPMCallback callback){ } public interface GetSinglePMCallback{ public void onResult(boolean success, FPPost PM); } public void GetPM(final FPActivity caller, int id, final GetSinglePMCallback callback){ } // Forum listing public interface ForumCallback { public void onResult( boolean success, Category[] categories ); } /** * Retrieve the categories and forums visible on the frontpage. * * @author Overv * * @param callback Function to pass the thread list to. */ public void listForums(final FPActivity caller, final ForumCallback callback ) { asyncAPIRequest("action=getforums&session_id=" + sessionID, "", caller.loadNitro(), new WebRequestCallback(){ public void onResult(String ret, JSONObject json) { ErrorResult error = checkErrors(json); if(error != ErrorResult.NOERROR){ callback.onResult(false, null); return; } JSONArray jCategories = json.optJSONArray("categories"); Category[] categories = new Category[jCategories.length()]; for(int i = 0; i < jCategories.length(); i++){ JSONObject jCategory; try{ jCategory = (JSONObject) jCategories.getJSONObject(i); } catch(JSONException e){ callback.onResult(false, null); return; } Category category = new Category(); category.name = jCategory.optString("name"); category.id = 0; category.forums = new ArrayList<Forum>(); JSONArray forums = jCategory.optJSONArray("forums"); for(int k = 0; k < forums.length(); k++){ JSONObject jForum; try{ jForum = (JSONObject) forums.getJSONObject(k); } catch(JSONException e){ continue; } Forum forum = new Forum(); forum.id = jForum.optInt("id"); forum.name = jForum.optString("name"); forum.subforums = new ArrayList<Forum>(); JSONArray jSubForums = jForum.optJSONArray("forums"); if(jSubForums != null){ for(int j = 0; j < jSubForums.length(); j++){ JSONObject jSubForum; try{ jSubForum = (JSONObject) jSubForums.getJSONObject(j); } catch(JSONException e){ continue; } Forum subForum = new Forum(); subForum.id = jSubForum.optInt("id"); subForum.name = jSubForum.optString("name"); forum.subforums.add(subForum); } } category.forums.add(forum); } categories[i] = category; } callback.onResult(true, categories); } }); } // Thread listing public interface ThreadCallback { public void onResult( boolean success, FPThread[] threads ); } private FPThread[] ExtractThreadsFromJSON(JSONObject json){ JSONArray jThreads = json.optJSONArray("threads"); if(jThreads == null) return new FPThread[0]; FPThread[] threads = new FPThread[jThreads.length()]; for(int i = 0; i < jThreads.length(); i++){ JSONObject jThread; try{ jThread = jThreads.getJSONObject(i); }catch(JSONException e){ return null; } String status = jThread.optString("status"); FPThread thread = new FPThread(); thread.id = jThread.optInt("id"); thread.title = jThread.optString("title"); thread.icon = jThread.optString("icon"); thread.sticky = status.contains("sticky"); thread.locked = jThread.optBoolean("locked"); thread.pages = jThread.optInt("pages"); thread.readers = jThread.optInt("reading"); thread.read = status.contains("new"); thread.views = jThread.optInt("views"); thread.posts = jThread.optInt("replies"); FPUser author = new FPUser(); author.name = jThread.optString("author"); author.id = jThread.optInt("authorid"); thread.author = author; thread.lastAuthor = jThread.optString("lastpostauthorname"); thread.lastAuthorTime = jThread.optString("lastposttime"); //Hexxeh does some weird shit with newposts if(loggedIn) thread.newPosts = jThread.optInt("newposts", 0); threads[i] = thread; } return threads; } /** * Retrieve read threads with new posts * * @author Overv * * @param forumId Forum identifier. * @param page Page of thread list. * @param callback Function to pass the thread list to. */ public void listThreads(final FPActivity caller, int forumId, int page, final ThreadCallback callback ) { asyncAPIRequest("action=getthreads&forum_id=" + forumId + "&page=" + page + "&session_id=" + sessionID, "", caller.loadNitro(), new WebRequestCallback(){ public void onResult(String ret, JSONObject json) { ErrorResult error = checkErrors(json); if(error != ErrorResult.NOERROR){ callback.onResult(false, null); return; } FPThread[] threads = ExtractThreadsFromJSON(json); callback.onResult(threads != null, threads); } }); } /** * Retrieve read threads with new posts. * * @author Geel9 * * @param callback Function to pass the thread list to. */ public void listReadThreads(final FPActivity caller, final ThreadCallback callback ) { if(!loggedIn()){ callback.onResult(false, null); return; } asyncAPIRequest("action=getreadthreads&session_id=" + sessionID, "", caller.loadNitro(), new WebRequestCallback(){ public void onResult(String ret, JSONObject json) { ErrorResult error = checkErrors(json); if(error != ErrorResult.NOERROR || json == null){ callback.onResult(false, null); return; } FPThread[] threads = ExtractThreadsFromJSON(json); callback.onResult(threads != null, threads); } }); } /* Retrieve popular threads. * * @author Geel9 * * @param callback Function to pass the thread list to. */ public void listPopularThreads(final FPActivity caller, final ThreadCallback callback ) { asyncAPIRequest("action=getpopularthreads&session_id=" + sessionID, "", caller.loadNitro(), new WebRequestCallback(){ public void onResult(String ret, JSONObject json) { ErrorResult error = checkErrors(json); if(error != ErrorResult.NOERROR || json == null){ callback.onResult(false, null); return; } FPThread[] threads = ExtractThreadsFromJSON(json); callback.onResult(threads != null, threads); } }); } // Post listing public interface PostCallback { public void onResult( boolean success, FPPost[] posts, String threadTitle, int numPages, int curPage ); } /** * Retrieve the posts in a thread on the specified page. * * @author Geel9 * * @param threadId Thread identifier. * @param page Thread page. * @param callback The function to pass the post list to. */ public void listPosts(final FPActivity caller, final int threadId, final int page, final PostCallback callback ) { String action = "&action=getposts&thread_id=" + threadId + "&page=" + page; //Simple way of going to "new posts": pass in a page number less than 0. if(page <= 0){ action = "&action=getnewposts&thread_id=" + threadId; } asyncAPIRequest("session_id=" + sessionID + action, "", caller.loadNitro(), new WebRequestCallback(){ public void onResult(String ret, JSONObject json) { ErrorResult error = checkErrors(json); if(error != ErrorResult.NOERROR || json == null){ callback.onResult(false, null, "", 0, 0); return; } JSONArray jPosts = json.optJSONArray("posts"); if(jPosts == null){ callback.onResult(false, null, "", 0, 0); return; } FPPost[] posts = new FPPost[jPosts.length()]; String title = json.optString("title"); int pages = json.optInt("numpages"); int curPage = json.optInt("currpage", page); for(int i = 0; i < jPosts.length(); i++){ JSONObject jPost; try { jPost = jPosts.getJSONObject(i); } catch (JSONException e) { callback.onResult(false, null, "", 0, 0); return; } int id = jPost.optInt("id"); int userid = jPost.optInt("userid"); String username = jPost.optString("username"); String postdate = jPost.optString("time"); String usertitle = jPost.optString("usertitle"); String status = jPost.optString("status", "old"); String message = jPost.optString("message"); String usergroup = jPost.optString("usergroup"); int postCount = jPost.optInt("postcount"); String username_html = jPost.optString("username_html"); JSONObject ratingKeys = jPost.optJSONObject("ratingkeys"); JSONObject ratings = jPost.optJSONObject("ratings"); if(usergroup.equals("moderator")){ username_html = "<strong><font color=\"#00aa00\">" + username + "</font></strong>"; } FPPost post = new FPPost(); FPUser user = new FPUser(); user.id = userid; user.name = username; user.postCount = postCount; user.name_html = username_html; user.userTitle = usertitle; post.author = user; post.id = id; post.date = postdate; post.message = message; post.isNew = status.contains("new"); if(ratingKeys != null) post.loadRatingKeys(ratingKeys); if(ratings != null) post.loadRatingNums(ratings); posts[i] = post; } callback.onResult(true, posts, title, pages, curPage); } }); } // Rating public interface RateCallback { public void onResult( boolean success ); } /** * Rate a post on the last retrieved thread page. * * @author Overv * * @param post Post to rate. * @param rating Rating to rate. * @param callback The function to pass the result to. */ public void rate( FPPost post, Rating rating, final RateCallback callback ) { if ( !post.ratingCodes.containsKey( rating ) || !loggedIn() ) { callback.onResult( false ); return; } String ratingKey = post.ratingCodes.get(rating); String ratingName = rating.ratingName.toLowerCase().replace(" ", "_"); asyncAPIRequest( "action=rate&session_id=" + sessionID +"&post_id=" + post.id + "&key=" + ratingKey + "&rating=" + ratingName, "", true, new WebRequestCallback() { public void onResult(String ret, JSONObject obj ) { callback.onResult( checkErrors(obj) == ErrorResult.NOERROR ); } } ); } public interface QuoteCallback{ public void onResult(boolean success, String quote); } public void quote(FPPost post, final QuoteCallback callback){ if(!loggedIn()){ callback.onResult(false, ""); return; } asyncAPIRequest( "action=getquote&session_id=" + sessionID + "&post_id=" + post.id, "", true, new WebRequestCallback() { public void onResult(String ret, JSONObject json ) { try{ ErrorResult error = checkErrors(json); if(error == ErrorResult.NOAUTH){ callback.onResult(false, ""); return; } else if(error == ErrorResult.NOERROR){ String quote = json.optString("quote"); callback.onResult(true, quote); } }catch(Exception e){ callback.onResult(false, ""); } } } ); } public interface EditCallback{ public void onResult(boolean success); } public void edit(FPPost post, String newMessage, final EditCallback callback){ if(!loggedIn()){ callback.onResult(false); return; } long unixTime = System.currentTimeMillis() / 1000L; asyncAPIRequest( "action=doedit&session_id=" + sessionID, "post_id=" + post.id + "&message=" + URLEncoder.encode(newMessage), true, new WebRequestCallback(){ public void onResult(String ret, JSONObject json) { try{ ErrorResult error = checkErrors(json); if(error == ErrorResult.NOAUTH){ callback.onResult(false); return; } else if(error == ErrorResult.NOERROR){ callback.onResult(true); } }catch(Exception e){ callback.onResult(false); } } }); } public void getEdit(FPPost post, final QuoteCallback callback){ if(!loggedIn()){ callback.onResult(false, ""); return; } asyncAPIRequest( "action=getedit&session_id=" + sessionID + "&post_id=" + post.id,"", true, new WebRequestCallback(){ public void onResult(String ret, JSONObject obj) { try{ ErrorResult error = checkErrors(obj); if(error == ErrorResult.NOAUTH){ callback.onResult(false, ""); return; } else if(error == ErrorResult.NOERROR){ String edit = obj.optString("edit"); callback.onResult(true, edit); } }catch(Exception e){ callback.onResult(false, ""); } } }); } private class RateData { public int id; public String code; } // Reply to latest retrieved thread public interface ReplyCallback { public void onResult( boolean success ); } /** * Reply to the last retrieved thread. * * @author Overv * * @param message Message to reply with. * @param callback The function to pass the result to. */ public void reply( String message, int threadID, final ReplyCallback callback ) { if(!loggedIn()){ callback.onResult(false); return; } asyncAPIRequest("action=newreply&session_id=" + sessionID, "thread_id=" + threadID + "&message=" + URLEncoder.encode(message), true, new WebRequestCallback() { public void onResult(String ret, JSONObject json) { callback.onResult(true); } } ); } // Retrieve avatar public interface AvatarCallback { public void onResult( boolean success, Bitmap avatar ); } public void getImage( String url, Context context, final AvatarCallback callback ) { asyncImageRequest( url, new ImageRequestCallback() { public void onResult( Bitmap image ) { if ( image == null ) callback.onResult( false, null ); else callback.onResult( true, image ); } } ); //} } /** * Retrieve the avatar image of the specified user. * * @author Overv * * @param userId User identifier. * @param callback The function to pass the avatar image to. */ public void getAvatar( int userId, Context context, boolean downloadAvatar, final AvatarCallback callback ) { try{ File cacheDir = context.getCacheDir(); cacheDir.mkdir(); File avatarFile = new File(cacheDir, userId + ".png"); if(avatarFile.exists()){ BitmapFactory bf = new BitmapFactory(); Bitmap b = bf.decodeFile(avatarFile.getAbsolutePath()); if(b != null){ callback.onResult(true, b); } } else if(downloadAvatar){ asyncImageRequest( "http://facepunch.com/image.php?u=" + userId, new ImageRequestCallback() { public void onResult( Bitmap image ) { if ( image == null ) callback.onResult( false, null ); else callback.onResult( true, image ); } } ); } else callback.onResult(false, null); }catch(OutOfMemoryError e){ callback.onResult(false, null); } } public void trimCache(Context context, int megabytes){ File cacheDir = context.getCacheDir(); cacheDir.mkdir(); long bytesTotal = 0; for(File f : cacheDir.listFiles()){ bytesTotal += f.length(); } float MB = (float)((float)bytesTotal / 1024f) / 1024f; if(MB >= megabytes){ int numFiles = cacheDir.listFiles().length / 2; List<File> toDelete = new ArrayList<File>(); for(int i = 0; i < numFiles; i++){ File f = cacheDir.listFiles()[i]; toDelete.add(f); } for(File del : toDelete){ del.delete(); } } } public void cacheAvatar(Bitmap b, int userID, Context context){ File cacheDir = context.getCacheDir(); cacheDir.mkdir(); File avatarFile = new File(cacheDir, userID + ".png"); if(!avatarFile.exists()){ try { FileOutputStream out = new FileOutputStream(avatarFile); b.compress(Bitmap.CompressFormat.PNG, 90, out); } catch (Exception e) { e.printStackTrace(); } } } // Retrieve flag icon public interface FlagCallback { public void onResult( boolean success, Bitmap flag ); } /** * Retrieve the flag associated with the specified flagdog code. * * @author Overv * * @param post The post to retrieve the flag from. * @param callback The function to pass the flag image to. */ public void getFlag( FPPost post, final FlagCallback callback ) { asyncImageRequest( "http://flagdog.facepunchstudios.com/?ipe=" + post.flagdog, new ImageRequestCallback() { public void onResult( Bitmap image ) { if ( image == null ) callback.onResult( false, null ); else callback.onResult( true, image ); } } ); } // Calculate MD5 hash static String MD5( String str ) { //DAMMIT OVERV WHY IS EVERYTHING YOU WROTE BROKEN MessageDigest md; try { md = MessageDigest.getInstance("MD5"); md.update(str.getBytes("UTF-8")); byte byteData[] = md.digest(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < byteData.length; i++) { sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1)); } String ret = sb.toString(); return ret; } catch ( NoSuchAlgorithmException e ) { return null; } catch ( UnsupportedEncodingException e ) { return null; } } // Quick single regex matcher public static String quickMatch( String regex, String target ) { Matcher m = Pattern.compile( regex, Pattern.DOTALL ).matcher( target ); if ( m.find() ) return m.group( 1 ); else return ""; } public static String quickMatch( String regex, String target, int index ) { Matcher m = Pattern.compile( regex, Pattern.DOTALL ).matcher( target ); if ( m.find() ) return m.group( index ); else return null; } // Retrieve image from URL private interface ImageRequestCallback { public void onResult( Bitmap image ); } private void asyncImageRequest( String url, ImageRequestCallback callback ) { new ImageRequest().execute( url, callback ); } private class ImageRequest extends AsyncTask<Object, Void, Object[]> { protected Object[] doInBackground( Object... params ) { Bitmap img = null; try { URL url = new URL( (String)params[0] ); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setInstanceFollowRedirects( true ); conn.setDoInput( true ); conn.connect(); img = BitmapFactory.decodeStream( conn.getInputStream() ); } catch ( IOException e ){} catch(OutOfMemoryError e){} return new Object[]{ img, params[1] }; } protected void onPostExecute( Object[] params ) { ImageRequestCallback callback = (ImageRequestCallback)params[1]; if ( params[0] == null ) { callback.onResult( null ); return; } callback.onResult( (Bitmap)params[0] ); } } // Perform Facepunch web request private interface WebRequestCallback { public void onResult( String ret, JSONObject json ); } private void asyncWebRequest( String action, String postBody, WebRequestCallback callback ) { new WebRequest().execute( action, postBody, callback, false ); } private void asyncAPIRequest( String action, String postBody, boolean nitro, WebRequestCallback callback) { new WebRequest().execute( "http://api.facepun.ch/?nitro=" + nitro + "&" + action, postBody, callback, true ); } class WebRequest extends AsyncTask<Object, Void, Object[]> { protected Object[] doInBackground( Object... params ) { String request = (String)params[0]; String postBody = (String)params[1]; Boolean api = (Boolean)params[3]; WebRequestCallback callback = (WebRequestCallback)params[2]; Crittercism.leaveBreadcrumb("Loading page: " + request); StringBuilder response = new StringBuilder(); try { URL url = new URL( request ); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setInstanceFollowRedirects( true ); // Send POST body if ( postBody != null && postBody != "") { conn.setDoOutput( true ); OutputStreamWriter out = new OutputStreamWriter( conn.getOutputStream() ); out.write( postBody ); out.close(); } // Get response text InputStreamReader reader = new InputStreamReader( conn.getInputStream() ); char[] buffer = new char[10*1024]; int n = 0; while ( n >= 0 ) { n = reader.read( buffer, 0, buffer.length ); if ( n > 0 ) response.append( buffer, 0, n ); } reader.close(); return new Object[] { callback, response.toString(), api}; } catch ( Exception e ) { return new Object[] { callback }; } } protected void onPostExecute( Object[] params ) { if ( params.length == 1 ) { ( (WebRequestCallback)params[0] ).onResult( null, null ); return; } WebRequestCallback callback = (WebRequestCallback)params[0]; String response = (String)params[1]; Boolean isAPI = (Boolean)params[2]; JSONObject json = null; if(isAPI) try { json = new JSONObject(response); } catch (JSONException e) { e.printStackTrace(); } callback.onResult( response, json ); } } public interface unReadCallback{ public void onResult(boolean success); } public void unReadThread(int id, final unReadCallback callback) { } }