mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
15 Commits
integratio
...
beta-1.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51794424ce | ||
|
|
5db099bae6 | ||
|
|
a2faa3bd3b | ||
|
|
a3fb7b5680 | ||
|
|
264d110dbd | ||
|
|
839b871a45 | ||
|
|
2fb4825b8f | ||
|
|
3f9a66b1b6 | ||
|
|
d796916387 | ||
|
|
fe07b760ea | ||
|
|
e21e6267d7 | ||
|
|
d7afbdf690 | ||
|
|
c5d2661c1d | ||
|
|
b738bdd14e | ||
|
|
6b61725c6a |
@@ -8,11 +8,15 @@ android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
|
||||
packagingOptions {
|
||||
doNotStrip '**/*.so'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 10216
|
||||
versionName "1.2.16"
|
||||
versionCode 10218
|
||||
versionName "1.2.18"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -2,12 +2,11 @@ Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUV
|
||||
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
|
||||
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
|
||||
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
|
||||
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
|
||||
Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
|
||||
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
|
||||
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
|
||||
Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
|
||||
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
|
||||
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
|
||||
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
|
||||
|
||||
@@ -16,7 +16,7 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: '*.jar')
|
||||
implementation 'net.java.dev.jna:jna:4.5.2'
|
||||
implementation 'net.java.dev.jna:jna-platform:4.5.2'
|
||||
tor 'org.briarproject:tor:0.3.5.13@zip'
|
||||
tor 'org.briarproject:tor:0.3.5.13-1@zip'
|
||||
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
|
||||
|
||||
@@ -25,6 +25,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.OsUtils.isLinux;
|
||||
|
||||
@@ -96,8 +97,15 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
|
||||
String architecture = null;
|
||||
if (isLinux()) {
|
||||
String arch = System.getProperty("os.arch");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("System's os.arch is " + arch);
|
||||
}
|
||||
if (arch.equals("amd64")) {
|
||||
architecture = "linux-x86_64";
|
||||
} else if (arch.equals("aarch64")) {
|
||||
architecture = "linux-aarch64";
|
||||
} else if (arch.equals("arm")) {
|
||||
architecture = "linux-armhf";
|
||||
}
|
||||
}
|
||||
if (architecture == null) {
|
||||
@@ -105,6 +113,10 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("The selected architecture for Tor is " + architecture);
|
||||
}
|
||||
|
||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
|
||||
|
||||
@@ -24,7 +24,7 @@ dependencyVerification {
|
||||
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
|
||||
'org.briarproject:tor:0.3.5.13:tor-0.3.5.13.zip:1c5f0b821ee2aadb0ea04aa96caab3ca0a08370cce8de81c2dfe04d172f8a2a0',
|
||||
'org.briarproject:tor:0.3.5.13-1:tor-0.3.5.13-1.zip:ef35c16bf8dc1f4c75ed71d9f55e4514f383d124ec96b859aca647c990927c99',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
|
||||
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
||||
|
||||
@@ -19,11 +19,15 @@ android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
|
||||
packagingOptions {
|
||||
doNotStrip '**/*.so'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
versionCode 10216
|
||||
versionName "1.2.16"
|
||||
versionCode 10218
|
||||
versionName "1.2.18"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
@@ -33,7 +33,6 @@ public class DozeFragment extends SetupFragment
|
||||
private DozeView dozeView;
|
||||
private HuaweiView huaweiView;
|
||||
private Button next;
|
||||
private ProgressBar progressBar;
|
||||
private boolean secondAttempt = false;
|
||||
|
||||
public static DozeFragment newInstance() {
|
||||
@@ -58,11 +57,19 @@ public class DozeFragment extends SetupFragment
|
||||
huaweiView = v.findViewById(R.id.huaweiView);
|
||||
huaweiView.setOnCheckedChangedListener(this);
|
||||
next = v.findViewById(R.id.next);
|
||||
progressBar = v.findViewById(R.id.progress);
|
||||
ProgressBar progressBar = v.findViewById(R.id.progress);
|
||||
|
||||
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
|
||||
next.setOnClickListener(this);
|
||||
|
||||
viewModel.getIsCreatingAccount()
|
||||
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
|
||||
if (isCreatingAccount) {
|
||||
next.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -104,15 +111,6 @@ public class DozeFragment extends SetupFragment
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
setNextClicked();
|
||||
viewModel.dozeExceptionConfirmed();
|
||||
}
|
||||
|
||||
@Override
|
||||
void setNextClicked() {
|
||||
super.setNextClicked();
|
||||
|
||||
next.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
private TextInputEditText passwordConfirmation;
|
||||
private StrengthMeter strengthMeter;
|
||||
private Button nextButton;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
public static SetPasswordFragment newInstance() {
|
||||
return new SetPasswordFragment();
|
||||
@@ -64,7 +63,7 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
v.findViewById(R.id.password_confirm_wrapper);
|
||||
passwordConfirmation = v.findViewById(R.id.password_confirm);
|
||||
nextButton = v.findViewById(R.id.next);
|
||||
progressBar = v.findViewById(R.id.progress);
|
||||
ProgressBar progressBar = v.findViewById(R.id.progress);
|
||||
|
||||
passwordEntry.addTextChangedListener(this);
|
||||
passwordConfirmation.addTextChangedListener(this);
|
||||
@@ -75,6 +74,17 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
|
||||
}
|
||||
|
||||
viewModel.getIsCreatingAccount()
|
||||
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
|
||||
if (isCreatingAccount) {
|
||||
nextButton.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
// this also avoids the keyboard popping up
|
||||
passwordEntry.setFocusable(false);
|
||||
passwordConfirmation.setFocusable(false);
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -116,20 +126,6 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
IBinder token = passwordEntry.getWindowToken();
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||
|
||||
setNextClicked();
|
||||
viewModel.setPassword(passwordEntry.getText().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
void setNextClicked() {
|
||||
super.setNextClicked();
|
||||
|
||||
passwordEntry.setFocusable(false);
|
||||
passwordConfirmation.setFocusable(false);
|
||||
if (!viewModel.needToShowDozeFragment()) {
|
||||
nextButton.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
@@ -19,8 +18,6 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@@ -35,7 +32,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
||||
OnEditorActionListener, OnClickListener {
|
||||
|
||||
private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
|
||||
private boolean clicked = false;
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
@@ -48,27 +44,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
||||
.get(SetupViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
if (savedInstanceState != null) {
|
||||
clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED);
|
||||
}
|
||||
if (clicked) {
|
||||
setNextClicked();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(STATE_KEY_CLICKED, clicked);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void setNextClicked() {
|
||||
this.clicked = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.help_action, menu);
|
||||
@@ -114,5 +89,4 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
|
||||
public void afterTextChanged(Editable editable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
|
||||
@@ -36,6 +38,8 @@ class SetupViewModel extends AndroidViewModel {
|
||||
@Nullable
|
||||
private String authorName, password;
|
||||
private final MutableLiveEvent<State> state = new MutableLiveEvent<>();
|
||||
private final MutableLiveData<Boolean> isCreatingAccount =
|
||||
new MutableLiveData<>(false);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
private final Executor ioExecutor;
|
||||
@@ -67,6 +71,10 @@ class SetupViewModel extends AndroidViewModel {
|
||||
return state;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getIsCreatingAccount() {
|
||||
return isCreatingAccount;
|
||||
}
|
||||
|
||||
void setAuthorName(String authorName) {
|
||||
this.authorName = authorName;
|
||||
state.setEvent(SET_PASSWORD);
|
||||
@@ -97,6 +105,7 @@ class SetupViewModel extends AndroidViewModel {
|
||||
private void createAccount() {
|
||||
if (authorName == null) throw new IllegalStateException();
|
||||
if (password == null) throw new IllegalStateException();
|
||||
isCreatingAccount.setValue(true);
|
||||
ioExecutor.execute(() -> {
|
||||
if (accountManager.createAccount(authorName, password)) {
|
||||
LOG.info("Created account");
|
||||
|
||||
@@ -24,10 +24,7 @@ import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||
import org.briarproject.briar.android.view.PullDownLayout;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -293,13 +290,10 @@ public class ImageActivity extends BriarActivity
|
||||
|
||||
@RequiresApi(api = 19)
|
||||
private Intent getCreationIntent() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
|
||||
Locale.getDefault());
|
||||
String fileName = sdf.format(new Date());
|
||||
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
|
||||
intent.addCategory(CATEGORY_OPENABLE);
|
||||
intent.setType(getVisibleAttachment().getMimeType());
|
||||
intent.putExtra(EXTRA_TITLE, fileName);
|
||||
intent.putExtra(EXTRA_TITLE, viewModel.getFileName());
|
||||
return intent;
|
||||
}
|
||||
|
||||
|
||||
@@ -225,8 +225,8 @@ public class ImageViewModel extends DbViewModel implements EventListener {
|
||||
});
|
||||
}
|
||||
|
||||
private String getFileName() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
|
||||
String getFileName() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd",
|
||||
Locale.getDefault());
|
||||
return sdf.format(new Date());
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ The REST API peer comes as a `jar` file
|
||||
and needs a Java Runtime Environment (JRE) that supports at least Java 8.
|
||||
It currently works only on GNU/Linux operating systems.
|
||||
|
||||
To build the `jar` file, you can do this:
|
||||
To build the `jar` file, you need to specify the combination of architecture and platform:
|
||||
|
||||
$ ./gradlew --configure-on-demand briar-headless:jar
|
||||
$ ./gradlew --configure-on-demand briar-headless:x86LinuxJar
|
||||
$ ./gradlew --configure-on-demand briar-headless:aarch64LinuxJar
|
||||
$ ./gradlew --configure-on-demand briar-headless:armhfLinuxJar
|
||||
|
||||
You can start the peer (and its API server) like this:
|
||||
|
||||
|
||||
@@ -44,18 +44,32 @@ dependencies {
|
||||
kaptTest "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
void jarFactory(Jar jarTask, jarArchitecture) {
|
||||
jarTask.doFirst {
|
||||
println 'Building ' + jarArchitecture + ' version has started'
|
||||
}
|
||||
jarTask.manifest {
|
||||
attributes(
|
||||
'Main-Class': 'org.briarproject.briar.headless.MainKt'
|
||||
)
|
||||
}
|
||||
from {
|
||||
jarTask.setArchiveClassifier(jarArchitecture)
|
||||
jarTask.from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
doLast() {
|
||||
{
|
||||
String[] architectures = ["linux-aarch64", "linux-armhf", "linux-x86_64"]
|
||||
for (String arch : architectures) {
|
||||
if (arch != jarArchitecture) {
|
||||
exclude "obfs4proxy_" + arch + ".zip"
|
||||
exclude "tor_" + arch + ".zip"
|
||||
}
|
||||
}
|
||||
}
|
||||
jarTask.with jar
|
||||
jarTask.doLast {
|
||||
// Rename the original jar
|
||||
File jar = project.jar.archivePath
|
||||
File jar = jarTask.archivePath
|
||||
String srcPath = jar.toString().replaceFirst('\\.jar$', '.unsorted.jar')
|
||||
File srcFile = new File(srcPath)
|
||||
jar.renameTo(srcFile)
|
||||
@@ -80,9 +94,22 @@ jar {
|
||||
}
|
||||
destStream.close()
|
||||
srcJarFile.close()
|
||||
println 'Building ' + jarArchitecture + ' version has finished'
|
||||
}
|
||||
}
|
||||
|
||||
task aarch64LinuxJar(type: Jar) {
|
||||
jarFactory(it, 'linux-aarch64')
|
||||
}
|
||||
|
||||
task armhfLinuxJar(type: Jar) {
|
||||
jarFactory(it, 'linux-armhf')
|
||||
}
|
||||
|
||||
task x86LinuxJar(type: Jar) {
|
||||
jarFactory(it, 'linux-x86_64')
|
||||
}
|
||||
|
||||
// At the moment for non-Android projects we need to explicitly mark the code generated by kapt
|
||||
// as 'generated source code' for correct highlighting and resolve in IDE.
|
||||
idea {
|
||||
|
||||
Reference in New Issue
Block a user