SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/api/TootApiClient.java

239 lines
8.2 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.io.IOException;
import java.util.UUID;
import jp.juggler.subwaytooter.App1;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.CancelChecker;
import jp.juggler.subwaytooter.util.LogCategory;
import jp.juggler.subwaytooter.R;
import jp.juggler.subwaytooter.util.Utils;
import jp.juggler.subwaytooter.table.ClientInfo;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class TootApiClient {
private static final LogCategory log = new LogCategory( "TootApiClient" );
static final OkHttpClient ok_http_client = App1.ok_http_client;
public interface Callback {
boolean isApiCancelled();
void publishApiProgress( 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;
private SavedAccount account;
public void setUserInfo( String instance, String user_mail, String password ){
this.instance = instance;
this.user_mail = user_mail;
this.password = password;
}
public void setAccount( SavedAccount account ){
this.instance = account.host;
this.account = account;
}
public static final MediaType MEDIA_TYPE_FORM_URL_ENCODED = MediaType.parse( "application/x-www-form-urlencoded" );
public TootApiResult request( String path ){
return request(path,new Request.Builder() );
}
public TootApiResult request( String path, Request.Builder request_builder ){
JSONObject client_info = null;
JSONObject token_info = ( account == null ? null : account.token_info );
for( ; ; ){
if( callback.isApiCancelled() ) return null;
if( token_info == null ){
if( client_info == null ){
// DBにあるならそれを使う
client_info = ClientInfo.load( instance );
if( client_info != null ) continue;
callback.publishApiProgress( context.getString( R.string.register_app_to_server, instance ) );
// OAuth2 クライアント登録
String client_name = "jp.juggler.subwaytooter." + UUID.randomUUID().toString();
Request request = new Request.Builder()
.url( "https://" + instance + "/api/v1/apps" )
.post( RequestBody.create( MEDIA_TYPE_FORM_URL_ENCODED
, "client_name=" + Uri.encode( client_name )
+ "&redirect_uris=urn:ietf:wg:oauth:2.0:oob"
+ "&scopes=read write follow"
) )
.build();
Call call = ok_http_client.newCall( request );
Response response;
try{
response = call.execute();
}catch( Throwable ex ){
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
if( ! response.isSuccessful() ){
return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
}
try{
String json = response.body().string();
if( TextUtils.isEmpty( json ) || json.startsWith( "<" ) ){
return new TootApiResult( context.getString( R.string.response_not_json ) + "\n" + json );
}
// {"id":999,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"******","client_secret":"******"}
client_info = new JSONObject( json );
String error = Utils.optStringX( client_info, "error" );
if( ! TextUtils.isEmpty( error ) ){
return new TootApiResult( context.getString( R.string.api_error, error ) );
}
ClientInfo.save( instance, json );
continue;
}catch( Throwable ex ){
ex.printStackTrace();
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
}
}
if( password == null ){
// 手動でアクセストークンを再取得しなければいけない
return new TootApiResult( context.getString( R.string.login_required ) );
}
callback.publishApiProgress( context.getString( R.string.request_access_token ) );
// アクセストークンの取得
Request request = new Request.Builder()
.url( "https://" + instance + "/oauth/token" )
.post( RequestBody.create(
MEDIA_TYPE_FORM_URL_ENCODED
,"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 )
+ "&scope=read write follow"
+ "&scopes=read write follow"
))
.build();
Call call = ok_http_client.newCall( request );
Response response;
try{
response = call.execute();
}catch( Throwable ex ){
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
// TODO: アプリIDが無効な場合はどんなエラーが出る
if( ! response.isSuccessful() ){
return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
}
try{
String json = response.body().string();
// {"access_token":"******","token_type":"bearer","scope":"read","created_at":1492334641}
if( TextUtils.isEmpty( json ) || json.charAt( 0 ) == '<' ){
return new TootApiResult( context.getString( R.string.login_failed ) );
}
token_info = new JSONObject( json );
String error = Utils.optStringX( client_info, "error" );
if( ! TextUtils.isEmpty( error ) ){
return new TootApiResult( context.getString( R.string.api_error, error ) );
}
if( account != null ){
account.updateTokenInfo( token_info );
}
continue;
}catch( Throwable ex ){
ex.printStackTrace();
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
}
}
// アクセストークンを使ってAPIを呼び出す
{
callback.publishApiProgress( context.getString( R.string.request_api, path ) );
Request request = request_builder
.url("https://" + instance + path)
.header( "Authorization", "Bearer " + Utils.optStringX( token_info, "access_token" ) )
.build();
Call call = ok_http_client.newCall( request );
Response response;
try{
response = call.execute();
}catch( Throwable ex ){
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
// TODO: アクセストークンが無効な場合はどうなる?
// TODO: アプリIDが無効な場合はどうなる
if( ! response.isSuccessful() ){
return new TootApiResult( context.getString( R.string.network_error_arg, response ) );
}
try{
String json = response.body().string();
if( TextUtils.isEmpty( json ) || json.startsWith( "<" ) ){
return new TootApiResult( context.getString( R.string.response_not_json ) + "\n" + json );
}else if( json.startsWith( "[" ) ){
JSONArray array = new JSONArray( json );
return new TootApiResult( token_info, json, array );
}else{
JSONObject object = new JSONObject( json );
String error = Utils.optStringX( object, "error" );
if( ! TextUtils.isEmpty( error ) ){
return new TootApiResult( context.getString( R.string.api_error, error ) );
}
return new TootApiResult( token_info, json, object );
}
}catch( Throwable ex ){
ex.printStackTrace();
return new TootApiResult( Utils.formatError( ex, "API data error" ) );
}
}
}
}
}