package jp.juggler.subwaytooter.api; import android.content.Context; import android.net.Uri; import android.support.annotation.NonNull; import android.text.TextUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.UUID; import jp.juggler.subwaytooter.table.AccessToken; import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.util.CancelChecker; import jp.juggler.subwaytooter.util.HTTPClient; import jp.juggler.subwaytooter.util.LogCategory; import jp.juggler.subwaytooter.R; import jp.juggler.subwaytooter.util.Utils; import jp.juggler.subwaytooter.table.ClientInfo; public class TootApiClient { private static final LogCategory log = new LogCategory( "TootApiClient" ); public interface Callback { boolean isCancelled(); void publishProgress( String s ); } private final Context context; public final Callback callback; public TootApiClient( @NonNull Context context, @NonNull Callback callback ){ this.context = context; this.callback = callback; } private String instance; private String user_mail; private String password; public void setUserInfo( String instance, String user_mail, String password ){ this.instance = instance; this.user_mail = user_mail; this.password = password; } public void setAccessInfo( SavedAccount access_info ){ this.instance = access_info.host; this.user_mail = access_info.user_mail; } public TootApiResult get( String path ){ final HTTPClient client = new HTTPClient( 60000, 10, "account", new CancelChecker() { @Override public boolean isCancelled(){ return callback.isCancelled(); } } ); JSONObject client_info = null; JSONObject token_info = null; for( ; ; ){ if( callback.isCancelled() ) return null; if( client_info == null ){ // DBにあるならそれを使う client_info = ClientInfo.load( instance ); if( client_info != null ) continue; callback.publishProgress( context.getString( R.string.register_app_to_server, instance ) ); // OAuth2 クライアント登録 String client_name = "jp.juggler.subwaytooter." + UUID.randomUUID().toString(); client.post_content = Utils.encodeUTF8( "client_name=" + Uri.encode( client_name ) + "&redirect_uris=urn:ietf:wg:oauth:2.0:oob" + "&scopes=read write follow" ); byte[] data = client.getHTTP( log, "https://" + instance + "/api/v1/apps" ); if( callback.isCancelled() ) return null; if( data == null ){ return new TootApiResult( context.getString( R.string.network_error, client.last_error ) ); } try{ String result = Utils.decodeUTF8( data ); // {"id":999,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"******","client_secret":"******"} client_info = new JSONObject( result ); String error = Utils.optStringX( client_info, "error" ); if( ! TextUtils.isEmpty( error ) ){ return new TootApiResult( context.getString( R.string.api_error, error ) ); } ClientInfo.save( instance, result ); continue; }catch( JSONException ex ){ ex.printStackTrace(); return new TootApiResult( Utils.formatError( ex, "API data error" ) ); } } if( token_info == null ){ // DBにあるならそれを使う token_info = AccessToken.load( instance, user_mail ); if( token_info != null ) continue; if( password == null ){ // 手動でアクセストークンを再取得しなければいけない return new TootApiResult( context.getString( R.string.login_required ) ); } callback.publishProgress( context.getString( R.string.request_access_token ) ); // アクセストークンの取得 // client.post_content = Utils.encodeUTF8( "client_id=" + Uri.encode( Utils.optStringX( client_info , "client_id" ) ) + "&client_secret=" + Uri.encode( Utils.optStringX( client_info, "client_secret" ) ) + "&grant_type=password" + "&username=" + Uri.encode( user_mail ) + "&password=" + Uri.encode( password ) ); byte[] data = client.getHTTP( log, "https://" + instance + "/oauth/token" ); if( callback.isCancelled() ) return null; // TODO: アプリIDが無効な場合はどんなエラーが出る? if( data == null ){ return new TootApiResult( context.getString( R.string.network_error, client.last_error ) ); } try{ String result = Utils.decodeUTF8( data ); // {"access_token":"******","token_type":"bearer","scope":"read","created_at":1492334641} token_info = new JSONObject( result ); String error = Utils.optStringX( client_info, "error" ); if( ! TextUtils.isEmpty( error ) ){ return new TootApiResult( context.getString( R.string.api_error, error ) ); } AccessToken.save( instance, user_mail, result ); continue; }catch( JSONException ex ){ ex.printStackTrace(); return new TootApiResult( Utils.formatError( ex, "API data error" ) ); } } // アクセストークンを使ってAPIを呼び出す { callback.publishProgress( context.getString( R.string.request_api, path ) ); client.post_content = null; client.extra_header = new String[]{ "Authorization", "Bearer "+ Utils.optStringX( token_info,"access_token") }; byte[] data = client.getHTTP( log, "https://" + instance + path ); if( callback.isCancelled() ) return null; // TODO: アクセストークンが無効な場合はどうなる? // TODO: アプリIDが無効な場合はどうなる? if( data == null ){ return new TootApiResult( context.getString( R.string.network_error, client.last_error ) ); } try{ String result = Utils.decodeUTF8( data ); if( result.startsWith( "[" ) ){ JSONArray array = new JSONArray( result ); return new TootApiResult( result,array ); }else{ JSONObject json = new JSONObject( result ); String error = Utils.optStringX( client_info, "error" ); if( ! TextUtils.isEmpty( error ) ){ return new TootApiResult( context.getString( R.string.api_error, error ) ); } return new TootApiResult( result,json ); } }catch( JSONException ex ){ ex.printStackTrace(); return new TootApiResult( Utils.formatError( ex, "API data error" ) ); } } } } }