アカウントだけを指定した通報は動作していませんでした。メニューから削除されます。投稿時にIdempotency-Key ヘッダを付与。

This commit is contained in:
tateisu 2017-05-01 18:18:03 +09:00
parent c668bb5d7d
commit dad9a200a4
7 changed files with 158 additions and 61 deletions

View File

@ -8,6 +8,7 @@
<w>favourited</w>
<w>hashtag</w>
<w>hashtags</w>
<w>idempotency</w>
<w>noto</w>
<w>nsfw</w>
<w>reblog</w>

View File

@ -10,6 +10,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.customtabs.CustomTabsIntent;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.os.AsyncTaskCompat;
@ -141,6 +142,7 @@ public class ActMain extends AppCompatActivity
}
ColumnViewHolder.ListItemPopup list_item_popup;
void closeListItemPopup(){
if( list_item_popup != null ){
list_item_popup.dismiss();
@ -328,7 +330,7 @@ public class ActMain extends AppCompatActivity
}else if( id == R.id.nav_add_blocks ){
performAddTimeline( Column.TYPE_BLOCKS );
}else if( id == R.id.nav_follow_requests ){
performAddTimeline( Column.TYPE_FOLLOW_REQUESTS );
@ -1660,7 +1662,7 @@ public class ActMain extends AppCompatActivity
) );
return client.request(
"/api/v1/follow_requests/" + who.id+ ( bAllow ? "/authorize" : "/reject" )
"/api/v1/follow_requests/" + who.id + ( bAllow ? "/authorize" : "/reject" )
, request_builder );
}
@ -1680,7 +1682,7 @@ public class ActMain extends AppCompatActivity
column.removeFollowRequest( access_info, who.id );
}
Utils.showToast( ActMain.this, false,( bAllow ? R.string.follow_request_authorized : R.string.follow_request_rejected),who.display_name );
Utils.showToast( ActMain.this, false, ( bAllow ? R.string.follow_request_authorized : R.string.follow_request_rejected ), who.display_name );
}else{
Utils.showToast( ActMain.this, false, result.error );
}
@ -1739,7 +1741,9 @@ public class ActMain extends AppCompatActivity
}
private void callReport(
@NonNull final SavedAccount account
@NonNull final SavedAccount access_info
, @NonNull final TootAccount who
, @NonNull final TootStatus status
, @NonNull final String comment
, final ReportCompleteCallback callback
@ -1756,16 +1760,20 @@ public class ActMain extends AppCompatActivity
}
} );
client.setAccount( account );
client.setAccount( access_info );
StringBuilder sb = new StringBuilder();
sb.append( "account_id=" )
.append( Long.toString( who.id ) )
.append( "&comment=" )
.append( Uri.encode( comment ) );
String sb = "account_id=" + Long.toString( status.account.id )
+ "&comment=" + Uri.encode( comment )
+ "&status_ids[]=" + Long.toString( status.id );
sb.append( "&status_ids[]=" )
.append( Long.toString( status.id ) );
Request.Builder request_builder = new Request.Builder().post(
RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
, sb
, sb.toString()
) );
return client.request( "/api/v1/reports", request_builder );
@ -1789,13 +1797,13 @@ public class ActMain extends AppCompatActivity
}.execute();
}
private void openReportForm( final SavedAccount account, final TootAccount who, final TootStatus status ){
private void openReportForm( @NonNull final SavedAccount account, @NonNull final TootAccount who, @NonNull final TootStatus status ){
ReportForm.showReportForm( this, who, status, new ReportForm.ReportFormCallback() {
@Override public void startReport( final Dialog dialog, String comment ){
// レポートの送信を開始する
callReport( account, status, comment, new ReportCompleteCallback() {
callReport( account, who, status, comment, new ReportCompleteCallback() {
@Override public void onReportComplete( TootApiResult result ){
// 成功したらダイアログを閉じる
@ -1957,17 +1965,19 @@ public class ActMain extends AppCompatActivity
callBlock( access_info, status.account, false, null );
}
} );
dialog.addAction( getString( R.string.report ), new Runnable() {
@Override public void run(){
openReportForm( access_info, status.account, status );
}
} );
if( access_info.isMe( status.account ) ){
dialog.addAction( getString( R.string.delete ), new Runnable() {
@Override public void run(){
deleteStatus( access_info, status.id );
}
} );
}else{
dialog.addAction( getString( R.string.report ), new Runnable() {
@Override public void run(){
openReportForm( access_info, status.account, status );
}
} );
}
if( column_type == Column.TYPE_CONVERSATION && status.application != null ){
@ -1991,12 +2001,12 @@ public class ActMain extends AppCompatActivity
if( column_type == Column.TYPE_FOLLOW_REQUESTS ){
dialog.addAction( getString( R.string.follow_request_ok ), new Runnable() {
@Override public void run(){
callFollowRequestAuthorize( access_info, who ,true);
callFollowRequestAuthorize( access_info, who, true );
}
} );
dialog.addAction( getString( R.string.follow_request_ng ), new Runnable() {
@Override public void run(){
callFollowRequestAuthorize( access_info, who ,false);
callFollowRequestAuthorize( access_info, who, false );
}
} );
@ -2044,15 +2054,14 @@ public class ActMain extends AppCompatActivity
callBlock( access_info, who, false, null );
}
} );
dialog.addAction( getString( R.string.report ), new Runnable() {
@Override public void run(){
openReportForm( access_info, who, null );
}
} );
// dialog.addAction( getString( R.string.report ), new Runnable() {
// @Override public void run(){
// openReportForm( access_info, who, null );
// }
// } );
dialog.show( this, null );
}
private void openOSSLicense(){
startActivity( new Intent( this, ActOSSLicense.class ) );
}

View File

@ -940,19 +940,14 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
TootStatus status;
@Override
protected TootApiResult doInBackground( Void... params ){
@Override protected TootApiResult doInBackground( Void... params ){
TootApiClient client = new TootApiClient( ActPost.this, new TootApiClient.Callback() {
@Override
public boolean isApiCancelled(){
@Override public boolean isApiCancelled(){
return isCancelled();
}
@Override
public void publishApiProgress( final String s ){
@Override public void publishApiProgress( final String s ){
Utils.runOnMainThread( new Runnable() {
@Override
public void run(){
@Override public void run(){
progress.setMessage( s );
}
} );
@ -960,12 +955,16 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
} );
client.setAccount( target_account );
String post_content = sb.toString();
String digest = Utils.digestSHA256( post_content + target_account.acct );
Request.Builder request_builder = new Request.Builder()
.post( RequestBody.create(
TootApiClient.MEDIA_TYPE_FORM_URL_ENCODED
, sb.toString()
) );
, post_content
) )
.header( "Idempotency-Key" ,digest)
;
TootApiResult result = client.request( "/api/v1/statuses", request_builder );
if( result.object != null ){
@ -986,7 +985,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
if( status != null ){
ActMain.update_at_resume = true;
ActPost.this.finish();
//DEBUG ActPost.this.finish();
}else{
if( result != null ){
Utils.showToast( ActPost.this, true, result.error );

View File

@ -17,6 +17,9 @@ import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
import java.util.ArrayList;
import java.util.Collections;
import jp.juggler.subwaytooter.table.AcctSet;
import jp.juggler.subwaytooter.table.MutedApp;
import jp.juggler.subwaytooter.table.ClientInfo;
@ -26,7 +29,11 @@ import jp.juggler.subwaytooter.table.MediaShown;
import jp.juggler.subwaytooter.table.NotificationTracking;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.table.UserRelation;
import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.TlsVersion;
import okhttp3.internal.Util;
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
import uk.co.chrisjenx.calligraphy.TypefaceUtils;
@ -153,7 +160,8 @@ public class App1 extends Application {
}
public static final OkHttpClient ok_http_client = new OkHttpClient();
public static OkHttpClient ok_http_client ;
public static Typeface typeface_emoji ;
@ -179,6 +187,28 @@ public class App1 extends Application {
typeface_emoji = TypefaceUtils.load(getAssets(), "emojione_android.ttf");
}
if( ok_http_client == null ){
// ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS)
// .tlsVersions( TlsVersion.TLS_1_2)
// .cipherSuites(
// CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
// CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
// .build();
//
// ArrayList<ConnectionSpec> spec_list = new ArrayList<>( );
// spec_list.add(ConnectionSpec.MODERN_TLS );
// // spec_list.add(ConnectionSpec.COMPATIBLE_TLS );
// spec_list.add(ConnectionSpec.CLEARTEXT );
// ok_http_client = new OkHttpClient.Builder()
// .connectionSpecs( spec_list )
// .build();
ok_http_client = new OkHttpClient();
}
if( db_open_helper == null ){
db_open_helper = new DBOpenHelper( getApplicationContext() );

View File

@ -111,6 +111,7 @@ public class TootApiClient {
try{
response = call.execute();
}catch( Throwable ex ){
ex.printStackTrace( );
return new TootApiResult(
Utils.formatError( ex, context.getResources(), R.string.network_error )
);
@ -183,6 +184,7 @@ public class TootApiClient {
try{
response = call.execute();
}catch( Throwable ex ){
ex.printStackTrace( );
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
@ -266,6 +268,7 @@ public class TootApiClient {
try{
response = call.execute();
}catch( Throwable ex ){
ex.printStackTrace( );
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}
if( callback.isApiCancelled() ) return null;
@ -306,6 +309,7 @@ public class TootApiClient {
try{
response = call.execute();
}catch( Throwable ex ){
ex.printStackTrace( );
return new TootApiResult( Utils.formatError( ex, context.getResources(), R.string.network_error ) );
}

View File

@ -74,3 +74,47 @@ public class TootAttachment {
}
}
// v1.3 から 添付ファイルの画像のピクセルサイズが取得できるようになった
// https://github.com/tootsuite/mastodon/issues/1985
// "media_attachments" : [
// {
// "id" : 4,
// "type" : "image",
// "remote_url" : "",
// "meta" : {
// "original" : {
// "width" : 600,
// "size" : "600x400",
// "height" : 400,
// "aspect" : 1.5
// },
// "small" : {
// "aspect" : 1.49812734082397,
// "height" : 267,
// "size" : "400x267",
// "width" : 400
// }
// },
// "url" : "http://127.0.0.1:3000/system/media_attachments/files/000/000/004/original/3416fc5188c656da.jpg?1493138517",
// "preview_url" : "http://127.0.0.1:3000/system/media_attachments/files/000/000/004/small/3416fc5188c656da.jpg?1493138517",
// "text_url" : "http://127.0.0.1:3000/media/4hfW3Kt4U9UxDvV_xug"
// },
// {
// "text_url" : "http://127.0.0.1:3000/media/0vTH_B1kjvIvlUBhGBw",
// "preview_url" : "http://127.0.0.1:3000/system/media_attachments/files/000/000/003/small/23519a5e64064e32.png?1493138030",
// "meta" : {
// "fps" : 15,
// "duration" : 5.06,
// "width" : 320,
// "size" : "320x180",
// "height" : 180,
// "length" : "0:00:05.06",
// "aspect" : 1.77777777777778
// },
// "url" : "http://127.0.0.1:3000/system/media_attachments/files/000/000/003/original/23519a5e64064e32.mp4?1493138030",
// "remote_url" : "",
// "type" : "gifv",
// "id" : 3
// }
// ],

View File

@ -134,7 +134,7 @@ public class Utils {
try{
return str.getBytes( "UTF-8" );
}catch( Throwable ex ){
return new byte[0]; // 入力がnullの場合のみ発生
return new byte[ 0 ]; // 入力がnullの場合のみ発生
}
}
@ -156,39 +156,38 @@ public class Utils {
}
}
public static String optStringX( JSONObject src, String key){
return src.isNull( key ) ? null : src.optString( key );
public static String optStringX( JSONObject src, String key ){
return src.isNull( key ) ? null : src.optString( key );
}
public static String optStringX( JSONArray src, int key){
return src.isNull( key ) ? null : src.optString( key );
public static String optStringX( JSONArray src, int key ){
return src.isNull( key ) ? null : src.optString( key );
}
public static ArrayList< String > parseStringArray( LogCategory log, JSONArray array ){
ArrayList< String > dst_list = new ArrayList<>( );
ArrayList< String > dst_list = new ArrayList<>();
if( array != null ){
for(int i=0,ie=array.length();i<ie;++i){
String sv = Utils.optStringX(array,i);
for( int i = 0, ie = array.length() ; i < ie ; ++ i ){
String sv = Utils.optStringX( array, i );
dst_list.add( sv );
}
}
return dst_list;
}
public static <T> boolean equalsNullable(T a,T b){
public static < T > boolean equalsNullable( T a, T b ){
return a == null ? b == null : a.equals( b );
}
public static CharSequence dumpCodePoints(String str){
public static CharSequence dumpCodePoints( String str ){
StringBuilder sb = new StringBuilder();
for(int i=0,ie=str.length(),cp;i<ie;i+=Character.charCount(cp)){
for( int i = 0, ie = str.length(), cp ; i < ie ; i += Character.charCount( cp ) ){
cp = str.codePointAt( i );
sb.append( String.format( "0x%x,", cp ) );
}
return sb;
}
static final char[] hex = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
public static void addHex( StringBuilder sb, byte b ){
@ -304,6 +303,18 @@ public class Utils {
return null;
}
public static String digestSHA256( String src ){
if( src == null ) return null;
try{
MessageDigest md = MessageDigest.getInstance( "SHA-256" );
md.reset();
return encodeHex( md.digest( src.getBytes( "UTF-8" ) ) );
}catch( Throwable ex ){
ex.printStackTrace();
}
return null;
}
/////////////////////////////////////////////
static HashMap< Character, String > taisaku_map = new HashMap<>();
@ -555,11 +566,11 @@ public class Utils {
String s = context.getString( string_id );
int end = s.length();
int pos = s.indexOf( "%1$s" );
if( pos == -1 ) return s;
SpannableStringBuilder sb = new SpannableStringBuilder( );
if( pos > 0 ) sb.append(s.substring( 0,pos ));
sb.append( display_name);
if( pos +4 < end ) sb.append(s.substring( pos+4,end ));
if( pos == - 1 ) return s;
SpannableStringBuilder sb = new SpannableStringBuilder();
if( pos > 0 ) sb.append( s.substring( 0, pos ) );
sb.append( display_name );
if( pos + 4 < end ) sb.append( s.substring( pos + 4, end ) );
return sb;
}
@ -814,14 +825,13 @@ public class Utils {
return null;
}
public static Activity getActivityFromView( View view) {
public static Activity getActivityFromView( View view ){
Context context = view.getContext();
while (context instanceof ContextWrapper ) {
if (context instanceof Activity) {
return (Activity)context;
while( context instanceof ContextWrapper ){
if( context instanceof Activity ){
return (Activity) context;
}
context = ((ContextWrapper)context).getBaseContext();
context = ( (ContextWrapper) context ).getBaseContext();
}
return null;
}