diff options
author | tom79 <tschneider.ac@gmail.com> | 2019-04-19 17:46:24 +0200 |
---|---|---|
committer | tom79 <tschneider.ac@gmail.com> | 2019-04-19 17:46:24 +0200 |
commit | bdb6ac94be0e2d49e5a039982071a0fdde645b2b (patch) | |
tree | 82b6fa36b42715ccd7646a73c2ed7704c291cfd9 | |
parent | 3b3e1544979ff0867f2d1f2e91fa1477b7b7757d (diff) | |
parent | 2b4fc03db2c8f823eeae2d26875087a5e20ea7f1 (diff) |
Merge branch 'develop'
36 files changed, 652 insertions, 321 deletions
diff --git a/app/build.gradle b/app/build.gradle index 0e620ad50..883c79e7d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "fr.gouv.etalab.mastodon" minSdkVersion 16 targetSdkVersion 28 - versionCode 251 - versionName "1.78.0" + versionCode 253 + versionName "1.79.0" multiDexEnabled true } dexOptions { diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/BaseMainActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/BaseMainActivity.java index 58bf3c9a2..08eb50674 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/BaseMainActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/BaseMainActivity.java @@ -55,7 +55,6 @@ import android.support.v7.widget.Toolbar; import android.text.Editable; import android.text.InputFilter; import android.text.TextWatcher; -import android.util.Log; import android.util.Patterns; import android.util.SparseArray; import android.view.Gravity; @@ -2329,7 +2328,7 @@ public abstract class BaseMainActivity extends BaseActivity String filename = Helper.getFilePathFromURI(getApplicationContext(), data.getData()); importDB(BaseMainActivity.this, filename); - }else{ + }else if(requestCode == PICK_IMPORT ){ Toasty.error(getApplicationContext(),getString(R.string.toot_select_file_error),Toast.LENGTH_LONG).show(); } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java index a70036e0d..9c54bf3e9 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java @@ -141,6 +141,7 @@ public class LoginActivity extends BaseActivity { } } } + if( getIntent() != null && getIntent().getData() != null && getIntent().getData().toString().contains("mastalab://backtomastalab?code=")){ String url = getIntent().getData().toString(); String val[] = url.split("code="); @@ -284,6 +285,7 @@ public class LoginActivity extends BaseActivity { @Override public void run() { instanceNodeInfo = new API(LoginActivity.this).getNodeInfo(instance); + runOnUiThread(new Runnable() { public void run() { connect_button.setEnabled(true); @@ -292,6 +294,9 @@ public class LoginActivity extends BaseActivity { case "MASTODON": socialNetwork = UpdateAccountInfoAsyncTask.SOCIAL.MASTODON; break; + case "PIXELFED": + socialNetwork = UpdateAccountInfoAsyncTask.SOCIAL.PIXELFED; + break; case "PEERTUBE": socialNetwork = UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE; break; @@ -299,7 +304,7 @@ public class LoginActivity extends BaseActivity { socialNetwork = UpdateAccountInfoAsyncTask.SOCIAL.GNU; break; } - if( instanceNodeInfo.getName().equals("MASTODON")) { + if( instanceNodeInfo.getName().equals("MASTODON") || instanceNodeInfo.getName().equals("PIXELFED")) { client_id_for_webview = true; retrievesClientId(); }else { @@ -531,6 +536,12 @@ public class LoginActivity extends BaseActivity { }else { parameters.put(Helper.SCOPES, Helper.OAUTH_SCOPES_PEERTUBE); } + /*if(socialNetwork == UpdateAccountInfoAsyncTask.SOCIAL.PIXELFED){ + client_id = "8"; + client_secret = "rjnu93kmK1KbRBBMZflMi8rxKJxOjeGtnDUVEUNK"; + manageClient(client_id, client_secret, null); + return; + }*/ parameters.put(Helper.WEBSITE, Helper.WEBSITE_VALUE); new Thread(new Runnable(){ @@ -552,32 +563,19 @@ public class LoginActivity extends BaseActivity { String id = null; if( socialNetwork != UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE) id = resobj.get(Helper.ID).toString(); - SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.CLIENT_ID, client_id); - editor.putString(Helper.CLIENT_SECRET, client_secret); - editor.putString(Helper.ID, id); - editor.apply(); - connectionButton.setEnabled(true); - if( client_id_for_webview){ - boolean embedded_browser = sharedpreferences.getBoolean(Helper.SET_EMBEDDED_BROWSER, true); - if( embedded_browser) { - Intent i = new Intent(LoginActivity.this, WebviewConnectActivity.class); - i.putExtra("social", socialNetwork); - i.putExtra("instance", instance); - startActivity(i); - }else{ - String url = redirectUserToAuthorizeAndLogin(client_id, instance); - Helper.openBrowser(LoginActivity.this, url); - } - } - } catch (JSONException ignored) {ignored.printStackTrace();} + manageClient(client_id, client_secret, id); + } catch (JSONException e) { + e.printStackTrace(); + } } }); } catch (final Exception e) { e.printStackTrace(); + + runOnUiThread(new Runnable() { public void run() { + String message; if( e.getLocalizedMessage() != null && e.getLocalizedMessage().trim().length() > 0) message = e.getLocalizedMessage(); @@ -586,6 +584,7 @@ public class LoginActivity extends BaseActivity { else message = getString(R.string.client_error); Toasty.error(getApplicationContext(), message,Toast.LENGTH_LONG).show(); + } }); } @@ -625,6 +624,8 @@ public class LoginActivity extends BaseActivity { if( socialNetwork == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON) { parameters.put("scope", " read write follow"); oauthUrl = "/oauth/token"; + }else if( socialNetwork == UpdateAccountInfoAsyncTask.SOCIAL.PIXELFED) { + oauthUrl = "/oauth/token"; }else if( socialNetwork == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE) { parameters.put("scope", "user"); oauthUrl = "/api/v1/users/token"; @@ -740,6 +741,32 @@ public class LoginActivity extends BaseActivity { } + + private void manageClient(String client_id, String client_secret, String id){ + + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.CLIENT_ID, client_id); + editor.putString(Helper.CLIENT_SECRET, client_secret); + editor.putString(Helper.ID, id); + editor.apply(); + connectionButton.setEnabled(true); + if( client_id_for_webview){ + boolean embedded_browser = sharedpreferences.getBoolean(Helper.SET_EMBEDDED_BROWSER, true); + if( embedded_browser) { + Intent i = new Intent(LoginActivity.this, WebviewConnectActivity.class); + i.putExtra("social", socialNetwork); + i.putExtra("instance", instance); + startActivity(i); + }else{ + String url = redirectUserToAuthorizeAndLogin(socialNetwork, client_id, instance); + + + Helper.openBrowser(LoginActivity.this, url); + } + } + } + @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. @@ -829,11 +856,12 @@ public class LoginActivity extends BaseActivity { } - public static String redirectUserToAuthorizeAndLogin(String clientId, String instance) { + public static String redirectUserToAuthorizeAndLogin(UpdateAccountInfoAsyncTask.SOCIAL socialNetwork, String clientId, String instance) { String queryString = Helper.CLIENT_ID + "="+ clientId; queryString += "&" + Helper.REDIRECT_URI + "="+ Uri.encode(Helper.REDIRECT_CONTENT_WEB); queryString += "&" + Helper.RESPONSE_TYPE +"=code"; - queryString += "&" + Helper.SCOPE +"=" + Helper.OAUTH_SCOPES; + if( socialNetwork != UpdateAccountInfoAsyncTask.SOCIAL.PIXELFED ) + queryString += "&" + Helper.SCOPE +"=" + Helper.OAUTH_SCOPES; return Helper.instanceWithProtocol(instance) + Helper.EP_AUTHORIZE + "?" + queryString; } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MediaActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MediaActivity.java index ad76f7b3a..0489b8f7e 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MediaActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MediaActivity.java @@ -174,7 +174,7 @@ public class MediaActivity extends BaseActivity implements OnDownloadInterface { media_save.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - isSHaring = true; + isSHaring = false; if(attachment.getType().toLowerCase().equals("video") || attachment.getType().toLowerCase().equals("gifv") || attachment.getType().toLowerCase().equals("web")) { if( attachment != null ) { progress.setText("0 %"); @@ -319,14 +319,13 @@ public class MediaActivity extends BaseActivity implements OnDownloadInterface { if(deltaX < 0) { switchOnSwipe(MediaActivity.actionSwipe.LEFT_TO_RIGHT); return true; } if(deltaX > 0) { switchOnSwipe(MediaActivity.actionSwipe.RIGHT_TO_LEFT); return true; } }else if(downY > MIN_DISTANCE & (Math.abs(deltaY) > MIN_DISTANCE ) ){ - if(deltaY > 0) { finish(); return true; } - if(deltaY < 0) { finish(); return true; } + if(deltaY > 0 && canSwipe) { finish(); return true; } + if(deltaY < 0 && canSwipe) { finish(); return true; } } else { currentAction = MediaActivity.actionSwipe.POP; isControlElementShown = !isControlElementShown; if (thisControllShown) { if(event.getY() > action_bar_container.getHeight()) { - FullScreencall(thisControllShown); action_bar_container.setVisibility(View.GONE); if (media_description.getVisibility() == View.VISIBLE) { media_description.setVisibility(View.GONE); @@ -350,9 +349,6 @@ public class MediaActivity extends BaseActivity implements OnDownloadInterface { } } - - - return super.dispatchTouchEvent(event); } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java index 5d45a3673..1f296f8b2 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java @@ -55,6 +55,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; @@ -449,7 +450,7 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface, } } - + toot_content.requestFocus(); if( mentionAccount != null){ toot_content.setText(String.format("@%s\n", mentionAccount)); toot_content.setSelection(toot_content.getText().length()); @@ -2006,6 +2007,9 @@ public class TootActivity extends BaseActivity implements OnPostActionInterface, if(restored != -1){ SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); new StatusStoredDAO(getApplicationContext(), db).remove(restored); + }else if(currentToId != -1){ + SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + new StatusStoredDAO(getApplicationContext(), db).remove(currentToId); } //Clear the toot toot_content.setText(""); diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewConnectActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewConnectActivity.java index 66e58e24f..42642839f 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewConnectActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewConnectActivity.java @@ -147,7 +147,6 @@ public class WebviewConnectActivity extends BaseActivity { return false; } String code = val[1]; - final String action = "/oauth/token"; final HashMap<String, String> parameters = new HashMap<>(); parameters.put(Helper.CLIENT_ID, clientId); @@ -182,7 +181,7 @@ public class WebviewConnectActivity extends BaseActivity { } }); - webView.loadUrl(LoginActivity.redirectUserToAuthorizeAndLogin(clientId, instance)); + webView.loadUrl(LoginActivity.redirectUserToAuthorizeAndLogin(social, clientId, instance)); } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/ManagePollAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/ManagePollAsyncTask.java index 9df82ac9e..282617161 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/ManagePollAsyncTask.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/ManagePollAsyncTask.java @@ -53,10 +53,16 @@ public class ManagePollAsyncTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { - if( status.getPoll().getId() == null) + Poll _poll; + if( status.getReblog() != null) + _poll = status.getReblog().getPoll(); + else + _poll = status.getPoll(); + + if( _poll.getId() == null) return null; if (type == type_s.SUBMIT){ - poll = new API(contextReference.get()).submiteVote(status.getPoll().getId(),choices); + poll = new API(contextReference.get()).submiteVote(_poll.getId(),choices); }else if( type == type_s.REFRESH){ poll = new API(contextReference.get()).getPoll(status); } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java index b4e57ea0b..e68619e3b 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java @@ -67,7 +67,7 @@ public class UpdateAccountInfoAsyncTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... params) { Account account = null; - if( social == SOCIAL.MASTODON) { + if( social == SOCIAL.MASTODON || social == SOCIAL.PIXELFED) { account = new API(this.contextReference.get(), instance, null).verifyCredentials(); account.setSocial(account.getSocial()); }else if( social == SOCIAL.PEERTUBE) { diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java index 280bcb735..f748d04f6 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java @@ -20,7 +20,6 @@ import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; -import android.util.Log; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -204,11 +203,11 @@ public class API { response = new HttpsConnection(context).get(nodeInfo.getHref(), 30, null, null); JSONObject resobj = new JSONObject(response); JSONObject jsonObject = resobj.getJSONObject("software"); - String name = "MASTODON"; + String name = jsonObject.getString("name").toUpperCase(); if( jsonObject.getString("name") != null ){ switch (jsonObject.getString("name").toUpperCase()){ - case "PEERTUBE": - name = "PEERTUBE"; + case "PLEROMA": + name = "MASTODON"; break; case "HUBZILLA": case "REDMATRIX": @@ -374,40 +373,45 @@ public class API { isPleromaAdmin(account.getAcct()); } } catch (HttpsConnection.HttpsConnectionException e) { - SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - Account targetedAccount = new AccountDAO(context, db).getAccountByToken(prefKeyOauthTokenT); - HashMap<String, String> values = refreshToken(targetedAccount.getClient_id(), targetedAccount.getClient_secret(), targetedAccount.getRefresh_token()); - if( values.containsKey("access_token") && values.get("access_token") != null) { - targetedAccount.setToken(values.get("access_token")); - SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - String token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); - //This account is currently logged in, the token is updated - if( prefKeyOauthTokenT.equals(token)){ - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, targetedAccount.getToken()); - editor.apply(); - } - }if( values.containsKey("refresh_token") && values.get("refresh_token") != null) - targetedAccount.setRefresh_token(values.get("refresh_token")); - new AccountDAO(context, db).updateAccount(targetedAccount); - String response; - try { - response = new HttpsConnection(context).get(getAbsoluteUrl("/accounts/verify_credentials"), 60, null, targetedAccount.getToken()); - account = parseAccountResponse(context, new JSONObject(response)); - if( account.getSocial().equals("PLEROMA")){ - isPleromaAdmin(account.getAcct()); + e.printStackTrace(); + if( e.getStatusCode() == 401 || e.getStatusCode() == 403){ + SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + Account targetedAccount = new AccountDAO(context, db).getAccountByToken(prefKeyOauthTokenT); + if( targetedAccount == null) + return null; + HashMap<String, String> values = refreshToken(targetedAccount.getClient_id(), targetedAccount.getClient_secret(), targetedAccount.getRefresh_token()); + if( values.containsKey("access_token") && values.get("access_token") != null) { + targetedAccount.setToken(values.get("access_token")); + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); + //This account is currently logged in, the token is updated + if( prefKeyOauthTokenT.equals(token)){ + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, targetedAccount.getToken()); + editor.apply(); + } + }if( values.containsKey("refresh_token") && values.get("refresh_token") != null) + targetedAccount.setRefresh_token(values.get("refresh_token")); + new AccountDAO(context, db).updateAccount(targetedAccount); + String response; + try { + response = new HttpsConnection(context).get(getAbsoluteUrl("/accounts/verify_credentials"), 60, null, targetedAccount.getToken()); + account = parseAccountResponse(context, new JSONObject(response)); + if( account.getSocial().equals("PLEROMA")){ + isPleromaAdmin(account.getAcct()); + } + } catch (IOException e1) { + e1.printStackTrace(); + } catch (NoSuchAlgorithmException e1) { + e1.printStackTrace(); + } catch (KeyManagementException e1) { + e1.printStackTrace(); + } catch (JSONException e1) { + e1.printStackTrace(); + } catch (HttpsConnection.HttpsConnectionException e1) { + e1.printStackTrace(); + setError(e.getStatusCode(), e); } - } catch (IOException e1) { - e1.printStackTrace(); - } catch (NoSuchAlgorithmException e1) { - e1.printStackTrace(); - } catch (KeyManagementException e1) { - e1.printStackTrace(); - } catch (JSONException e1) { - e1.printStackTrace(); - } catch (HttpsConnection.HttpsConnectionException e1) { - e1.printStackTrace(); - setError(e.getStatusCode(), e); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); @@ -440,10 +444,6 @@ public class API { String token = resobj.get("access_token").toString(); if( resobj.has("refresh_token")) refresh_token = resobj.get("refresh_token").toString(); - SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token); - editor.apply(); newValues.put("access_token",token); newValues.put("refresh_token",refresh_token); @@ -2220,8 +2220,9 @@ public class API { */ public Poll getPoll(Status status){ try { + Poll _p = (status.getReblog() != null)?status.getReblog().getPoll():status.getPoll(); HttpsConnection httpsConnection = new HttpsConnection(context); - String response = httpsConnection.get(getAbsoluteUrl(String.format("/polls/%s", status.getPoll().getId())), 60, null, prefKeyOauthTokenT); + String response = httpsConnection.get(getAbsoluteUrl(String.format("/polls/%s", _p.getId())), 60, null, prefKeyOauthTokenT); Poll poll = parsePoll(context, new JSONObject(response)); Bundle b = new Bundle(); status.setPoll(poll); @@ -2425,24 +2426,35 @@ public class API { parameters.append("exclude_types[]=").append("follow").append("&"); parameters.append("exclude_types[]=").append("favourite").append("&"); parameters.append("exclude_types[]=").append("reblog").append("&"); + parameters.append("exclude_types[]=").append("poll").append("&"); parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(16)); params.put("exclude_types[]", parameters.toString()); }else if(type == DisplayNotificationsFragment.Type.FAVORITE){ parameters.append("exclude_types[]=").append("follow").append("&"); parameters.append("exclude_types[]=").append("mention").append("&"); parameters.append("exclude_types[]=").append("reblog").append("&"); + parameters.append("exclude_types[]=").append("poll").append("&"); parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(16)); params.put("exclude_types[]", parameters.toString()); }else if(type == DisplayNotificationsFragment.Type.BOOST){ parameters.append("exclude_types[]=").append("follow").append("&"); parameters.append("exclude_types[]=").append("mention").append("&"); parameters.append("exclude_types[]=").append("favourite").append("&"); + parameters.append("exclude_types[]=").append("poll").append("&"); + parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(16)); + params.put("exclude_types[]", parameters.toString()); + }else if(type == DisplayNotificationsFragment.Type.POOL){ + parameters.append("exclude_types[]=").append("reblog").append("&"); + parameters.append("exclude_types[]=").append("follow").append("&"); + parameters.append("exclude_types[]=").append("mention").append("&"); + parameters.append("exclude_types[]=").append("favourite").append("&"); parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(16)); params.put("exclude_types[]", parameters.toString()); }else if(type == DisplayNotificationsFragment.Type.FOLLOW){ parameters.append("exclude_types[]=").append("reblog").append("&"); parameters.append("exclude_types[]=").append("mention").append(" |