mirror of
https://github.com/bisq-network/bisq.git
synced 2025-01-19 05:44:05 +01:00
Display Enabled=PENDING in CLI 'createoffer' output
A newly created offer has no OpenOffer+State (AVAILABLE || DEACTIVATED) when displayed in the CLI's console. This change adds a 'bool isMyPendingOffer' to the OfferInfo proto + wrapper, and the CLI's console offer output formatter uses it to determine if it should display a new offer's Enabled column value as PENDING, instead of an ambiguous NO value.
This commit is contained in:
parent
6a4aceda7b
commit
71a61c63da
@ -39,6 +39,7 @@ import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
|
||||
@ -70,6 +71,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||
alicesBsqAcct.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("Sell BSQ (Buy BTC) OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
@ -86,6 +90,8 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
@ -112,6 +118,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||
alicesBsqAcct.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("SELL 20K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
@ -128,6 +137,8 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
@ -154,6 +165,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||
alicesBsqAcct.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("BUY 1-2K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
@ -170,6 +184,8 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
@ -196,6 +212,9 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||
alicesBsqAcct.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("SELL 5-10K BSQ OFFER:\n{}", formatOfferTable(singletonList(newOffer), BSQ));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
@ -212,6 +231,8 @@ public class CreateBSQOffersTest extends AbstractOfferTest {
|
||||
genBtcBlockAndWaitForOfferPreparation();
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
|
@ -35,6 +35,7 @@ import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
import static protobuf.OfferPayload.Direction.SELL;
|
||||
|
||||
@ -58,6 +59,9 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
audAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "AUD"));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
@ -72,6 +76,8 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
@ -98,6 +104,9 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
usdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "USD"));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
@ -112,6 +121,8 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
@ -138,6 +149,9 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
eurAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE);
|
||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "EUR"));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
@ -152,6 +166,8 @@ public class CreateOfferUsingFixedPriceTest extends AbstractOfferTest {
|
||||
assertFalse(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertFalse(newOffer.getUseMarketBasedPrice());
|
||||
|
@ -49,6 +49,7 @@ import static java.lang.String.format;
|
||||
import static java.math.RoundingMode.HALF_UP;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static protobuf.OfferPayload.Direction.BUY;
|
||||
@ -81,6 +82,9 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
MAKER_FEE_CURRENCY_CODE,
|
||||
NO_TRIGGER_PRICE);
|
||||
log.info("OFFER #1:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
@ -94,6 +98,8 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
@ -123,6 +129,9 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
MAKER_FEE_CURRENCY_CODE,
|
||||
NO_TRIGGER_PRICE);
|
||||
log.info("OFFER #2:\n{}", formatOfferTable(singletonList(newOffer), "nzd"));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
@ -136,6 +145,8 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(BUY.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
@ -165,6 +176,9 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
MAKER_FEE_CURRENCY_CODE,
|
||||
NO_TRIGGER_PRICE);
|
||||
log.info("OFFER #3:\n{}", formatOfferTable(singletonList(newOffer), "gbp"));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
@ -178,6 +192,8 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
@ -207,6 +223,9 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
MAKER_FEE_CURRENCY_CODE,
|
||||
NO_TRIGGER_PRICE);
|
||||
log.info("OFFER #4:\n{}", formatOfferTable(singletonList(newOffer), "brl"));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
String newOfferId = newOffer.getId();
|
||||
assertNotEquals("", newOfferId);
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
@ -220,6 +239,8 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
assertTrue(newOffer.getIsCurrencyForMakerFeeBtc());
|
||||
|
||||
newOffer = aliceClient.getMyOffer(newOfferId);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(newOfferId, newOffer.getId());
|
||||
assertEquals(SELL.name(), newOffer.getDirection());
|
||||
assertTrue(newOffer.getUseMarketBasedPrice());
|
||||
@ -252,9 +273,14 @@ public class CreateOfferUsingMarketPriceMarginTest extends AbstractOfferTest {
|
||||
usdAccount.getId(),
|
||||
MAKER_FEE_CURRENCY_CODE,
|
||||
triggerPriceAsLong);
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertTrue(newOffer.getIsMyPendingOffer());
|
||||
|
||||
genBtcBlocksThenWait(1, 4000); // give time to add to offer book
|
||||
newOffer = aliceClient.getMyOffer(newOffer.getId());
|
||||
log.info("OFFER #5:\n{}", formatOfferTable(singletonList(newOffer), "usd"));
|
||||
assertTrue(newOffer.getIsMyOffer());
|
||||
assertFalse(newOffer.getIsMyPendingOffer());
|
||||
assertEquals(triggerPriceAsLong, newOffer.getTriggerPrice());
|
||||
}
|
||||
|
||||
|
@ -509,7 +509,7 @@ public class CliMain {
|
||||
}
|
||||
var tradeId = opts.getTradeId();
|
||||
var address = opts.getAddress();
|
||||
// Multi-word memos must be double quoted.
|
||||
// Multi-word memos must be double-quoted.
|
||||
var memo = opts.getMemo();
|
||||
client.withdrawFunds(tradeId, address, memo);
|
||||
out.printf("trade %s funds sent to btc address %s%n", tradeId, address);
|
||||
|
@ -202,7 +202,7 @@ public class TableFormat {
|
||||
return headerLine
|
||||
+ offers.stream()
|
||||
.map(o -> format(colDataFormat,
|
||||
o.getIsActivated() ? "YES" : "NO",
|
||||
formatEnabled(o),
|
||||
o.getDirection(),
|
||||
formatPrice(o.getPrice()),
|
||||
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||
@ -300,7 +300,7 @@ public class TableFormat {
|
||||
return headerLine
|
||||
+ offers.stream()
|
||||
.map(o -> format(colDataFormat,
|
||||
o.getIsActivated() ? "YES" : "NO",
|
||||
formatEnabled(o),
|
||||
directionFormat.apply(o),
|
||||
formatCryptoCurrencyPrice(o.getPrice()),
|
||||
formatAmountRange(o.getMinAmount(), o.getAmount()),
|
||||
@ -324,6 +324,14 @@ public class TableFormat {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String formatEnabled(OfferInfo offerInfo) {
|
||||
if (offerInfo.getIsMyOffer() && offerInfo.getIsMyPendingOffer())
|
||||
return "PENDING";
|
||||
else
|
||||
return offerInfo.getIsActivated() ? "YES" : "NO";
|
||||
}
|
||||
|
||||
private static int getLongestPaymentMethodColWidth(List<OfferInfo> offers) {
|
||||
return getLongestColumnSize(
|
||||
COL_HEADER_PAYMENT_METHOD.length(),
|
||||
|
@ -63,7 +63,8 @@ public class OfferInfo implements Payload {
|
||||
private final long date;
|
||||
private final String state;
|
||||
private final boolean isActivated;
|
||||
private boolean isMyOffer;
|
||||
private boolean isMyOffer; // Not final -- may be re-set after instantiation.
|
||||
private final boolean isMyPendingOffer;
|
||||
|
||||
public OfferInfo(OfferInfoBuilder builder) {
|
||||
this.id = builder.id;
|
||||
@ -91,11 +92,12 @@ public class OfferInfo implements Payload {
|
||||
this.state = builder.state;
|
||||
this.isActivated = builder.isActivated;
|
||||
this.isMyOffer = builder.isMyOffer;
|
||||
this.isMyPendingOffer = builder.isMyPendingOffer;
|
||||
}
|
||||
|
||||
// Allow isMyOffer to be set on new offers' OfferInfo instances.
|
||||
public void setIsMyOffer(boolean myOffer) {
|
||||
isMyOffer = myOffer;
|
||||
// Allow isMyOffer to be set on a new offer's OfferInfo instance.
|
||||
public void setIsMyOffer(boolean isMyOffer) {
|
||||
this.isMyOffer = isMyOffer;
|
||||
}
|
||||
|
||||
public static OfferInfo toOfferInfo(Offer offer) {
|
||||
@ -104,6 +106,16 @@ public class OfferInfo implements Payload {
|
||||
return getOfferInfoBuilder(offer, false).build();
|
||||
}
|
||||
|
||||
public static OfferInfo toPendingOfferInfo(Offer myNewOffer) {
|
||||
// Use this to build an OfferInfo instance when a new OpenOffer is being
|
||||
// prepared, and no valid OpenOffer state (AVAILABLE, DEACTIVATED) exists.
|
||||
// It is needed for the CLI's 'createoffer' output, which has a boolean 'ENABLED'
|
||||
// column that will show a PENDING value when this.isMyPendingOffer = true.
|
||||
return getOfferInfoBuilder(myNewOffer, true)
|
||||
.withIsMyPendingOffer(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static OfferInfo toOfferInfo(OpenOffer openOffer) {
|
||||
// An OpenOffer is always my offer.
|
||||
return getOfferInfoBuilder(openOffer.getOffer(), true)
|
||||
@ -171,6 +183,7 @@ public class OfferInfo implements Payload {
|
||||
.setState(state)
|
||||
.setIsActivated(isActivated)
|
||||
.setIsMyOffer(isMyOffer)
|
||||
.setIsMyPendingOffer(isMyPendingOffer)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -202,6 +215,7 @@ public class OfferInfo implements Payload {
|
||||
.withState(proto.getState())
|
||||
.withIsActivated(proto.getIsActivated())
|
||||
.withIsMyOffer(proto.getIsMyOffer())
|
||||
.withIsMyPendingOffer(proto.getIsMyPendingOffer())
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -237,6 +251,7 @@ public class OfferInfo implements Payload {
|
||||
private String state;
|
||||
private boolean isActivated;
|
||||
private boolean isMyOffer;
|
||||
private boolean isMyPendingOffer;
|
||||
|
||||
public OfferInfoBuilder withId(String id) {
|
||||
this.id = id;
|
||||
@ -363,6 +378,11 @@ public class OfferInfo implements Payload {
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfoBuilder withIsMyPendingOffer(boolean isMyPendingOffer) {
|
||||
this.isMyPendingOffer = isMyPendingOffer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OfferInfo build() {
|
||||
return new OfferInfo(this);
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.core.api.model.OfferInfo.toOfferInfo;
|
||||
import static bisq.core.api.model.OfferInfo.toPendingOfferInfo;
|
||||
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
|
||||
import static bisq.proto.grpc.OffersGrpc.*;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
@ -160,8 +161,7 @@ class GrpcOffersService extends OffersImplBase {
|
||||
offer -> {
|
||||
// This result handling consumer's accept operation will return
|
||||
// the new offer to the gRPC client after async placement is done.
|
||||
OfferInfo offerInfo = toOfferInfo(offer);
|
||||
offerInfo.setIsMyOffer(true);
|
||||
OfferInfo offerInfo = toPendingOfferInfo(offer);
|
||||
CreateOfferReply reply = CreateOfferReply.newBuilder()
|
||||
.setOffer(offerInfo.toProtoMessage())
|
||||
.build();
|
||||
|
@ -192,6 +192,7 @@ message OfferInfo {
|
||||
uint64 makerFee = 23;
|
||||
bool isActivated = 24;
|
||||
bool isMyOffer = 25;
|
||||
bool isMyPendingOffer = 26;
|
||||
}
|
||||
|
||||
message AvailabilityResultWithDescription {
|
||||
|
Loading…
Reference in New Issue
Block a user